(apps/ecommerce/inventory) Replaced the mat-table with a custom grid for better mobile experience and better performance, improved the mobile experience

This commit is contained in:
sercan 2021-06-14 12:03:17 +03:00
parent 5962c80e8d
commit 214116e10d
3 changed files with 484 additions and 545 deletions

View File

@ -12,7 +12,6 @@ import { MatRippleModule } from '@angular/material/core';
import { MatSortModule } from '@angular/material/sort';
import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { SharedModule } from 'app/shared/shared.module';
import { InventoryComponent } from 'app/modules/admin/apps/ecommerce/inventory/inventory.component';
@ -38,7 +37,6 @@ import { ecommerceRoutes } from 'app/modules/admin/apps/ecommerce/ecommerce.rout
MatSortModule,
MatSelectModule,
MatSlideToggleModule,
MatTableModule,
MatTooltipModule,
SharedModule
]

View File

@ -1,4 +1,4 @@
<div class="absolute inset-0 flex flex-col min-w-0 overflow-hidden bg-card dark:bg-transparent">
<div class="sm:absolute sm:inset-0 flex flex-col flex-auto min-w-0 sm:overflow-hidden bg-card dark:bg-transparent">
<!-- Header -->
<div class="relative flex flex-col sm:flex-row flex-0 sm:items-center sm:justify-between py-8 px-6 md:px-8 border-b">
@ -39,170 +39,114 @@
<div class="flex flex-auto overflow-hidden">
<!-- Products list -->
<div class="flex flex-col flex-auto sm:mb-18 overflow-hidden">
<ng-container *ngIf="productsCount > 0; else noProducts">
<!-- Table wrapper -->
<div class="flex flex-col flex-auto sm:mb-18 overflow-hidden sm:overflow-y-auto">
<ng-container *ngIf="(products$ | async) as products">
<ng-container *ngIf="products.length > 0; else noProducts">
<div class="grid">
<!-- Header -->
<div
class="overflow-x-auto sm:overflow-y-auto"
cdkScrollable>
<!-- Table -->
<table
class="w-full min-w-320 table-fixed bg-transparent"
[ngClass]="{'pointer-events-none': isLoading}"
mat-table
class="inventory-grid z-10 sticky top-0 grid gap-4 py-4 px-6 md:px-8 shadow text-md font-semibold text-secondary bg-gray-50 dark:bg-black dark:bg-opacity-5"
matSort
[matSortActive]="'name'"
[matSortDisableClear]="true"
[matSortDirection]="'asc'"
[multiTemplateDataRows]="true"
[dataSource]="products$"
[trackBy]="trackByFn">
<!-- SKU -->
<ng-container matColumnDef="sku">
<th
class="w-56 pl-26 bg-gray-50 dark:bg-black dark:bg-opacity-5"
mat-header-cell
*matHeaderCellDef
mat-sort-header
disableClear>
matSortDisableClear>
<div></div>
<div
class="hidden md:block"
[mat-sort-header]="'sku'">
SKU
</th>
<td
class="px-8"
mat-cell
*matCellDef="let product">
</div>
<div [mat-sort-header]="'name'">Name</div>
<div
class="hidden sm:block"
[mat-sort-header]="'price'">
Price
</div>
<div
class="hidden lg:block"
[mat-sort-header]="'stock'">
Stock
</div>
<div
class="hidden lg:block"
[mat-sort-header]="'active'">
Active
</div>
<div class="hidden sm:block">Details</div>
</div>
<!-- Rows -->
<ng-container *ngIf="(products$ | async) as products">
<ng-container *ngFor="let product of products; trackBy: trackByFn">
<div class="inventory-grid grid items-center gap-4 py-3 px-6 md:px-8 border-b">
<!-- Image -->
<div class="flex items-center">
<span class="relative flex flex-0 items-center justify-center w-12 h-12 mr-6 rounded overflow-hidden border">
<div class="relative flex flex-0 items-center justify-center w-12 h-12 mr-6 rounded overflow-hidden border">
<img
class="w-8"
*ngIf="product.thumbnail"
[alt]="'Product thumbnail image'"
[src]="product.thumbnail">
<span
<div
class="flex items-center justify-center w-full h-full text-xs font-semibold leading-none text-center uppercase"
*ngIf="!product.thumbnail">
No Image
</span>
</span>
<span class="truncate">{{product.sku}}</span>
NO THUMB
</div>
</div>
</div>
<!-- SKU -->
<div class="hidden md:block truncate">
{{product.sku}}
</div>
</td>
</ng-container>
<!-- Name -->
<ng-container matColumnDef="name">
<th
class="bg-gray-50 dark:bg-black dark:bg-opacity-5"
mat-header-cell
*matHeaderCellDef
mat-sort-header
disableClear>
Name
</th>
<td
class="pr-8 truncate"
mat-cell
*matCellDef="let product">
<div class="truncate">
{{product.name}}
</td>
</ng-container>
</div>
<!-- Price -->
<ng-container matColumnDef="price">
<th
class="w-40 bg-gray-50 dark:bg-black dark:bg-opacity-5"
mat-header-cell
*matHeaderCellDef
mat-sort-header
disableClear>
Price
</th>
<td
class="pr-4"
mat-cell
*matCellDef="let product">
<div class="hidden sm:block">
{{product.price | currency:'USD':'symbol':'1.2-2'}}
</td>
</ng-container>
</div>
<!-- Stock -->
<ng-container matColumnDef="stock">
<th
class="w-24 bg-gray-50 dark:bg-black dark:bg-opacity-5"
mat-header-cell
*matHeaderCellDef
mat-sort-header
disableClear>
Stock
</th>
<td
class="pr-4"
mat-cell
*matCellDef="let product">
<span class="flex items-center">
<span class="min-w-4">{{product.stock}}</span>
<div class="hidden lg:flex items-center">
<div class="min-w-4">{{product.stock}}</div>
<!-- Low stock -->
<span
<div
class="flex items-end ml-2 w-1 h-4 bg-red-200 rounded overflow-hidden"
*ngIf="product.stock < 20">
<span class="flex w-full h-1/3 bg-red-600"></span>
</span>
<div class="flex w-full h-1/3 bg-red-600"></div>
</div>
<!-- Medium stock -->
<span
<div
class="flex items-end ml-2 w-1 h-4 bg-orange-200 rounded overflow-hidden"
*ngIf="product.stock >= 20 && product.stock < 30">
<span class="flex w-full h-2/4 bg-orange-400"></span>
</span>
<div class="flex w-full h-2/4 bg-orange-400"></div>
</div>
<!-- High stock -->
<span
<div
class="flex items-end ml-2 w-1 h-4 bg-green-100 rounded overflow-hidden"
*ngIf="product.stock >= 30">
<span class="flex w-full h-full bg-green-400"></span>
</span>
</span>
</td>
</ng-container>
<div class="flex w-full h-full bg-green-400"></div>
</div>
</div>
<!-- Active -->
<ng-container matColumnDef="active">
<th
class="w-24 bg-gray-50 dark:bg-black dark:bg-opacity-5"
mat-header-cell
*matHeaderCellDef
mat-sort-header
disableClear>
Active
</th>
<td
class="pr-4"
mat-cell
*matCellDef="let product">
<div class="hidden lg:block">
<ng-container *ngIf="product.active">
<mat-icon
class="text-green-400 icon-size-5"
*ngIf="product.active"
[svgIcon]="'heroicons_solid:check'"></mat-icon>
</ng-container>
<ng-container *ngIf="!product.active">
<mat-icon
class="text-gray-400 icon-size-5"
*ngIf="!product.active"
[svgIcon]="'heroicons_solid:x'"></mat-icon>
</td>
</ng-container>
</div>
<!-- Details -->
<ng-container matColumnDef="details">
<th
class="w-24 pr-8 bg-gray-50 dark:bg-black dark:bg-opacity-5"
mat-header-cell
*matHeaderCellDef>
Details
</th>
<td
class="pr-8"
mat-cell
*matCellDef="let product">
<!-- Details button -->
<div>
<button
class="min-w-10 min-h-7 h-7 px-2 leading-6"
mat-stroked-button
@ -211,37 +155,43 @@
class="icon-size-5"
[svgIcon]="selectedProduct?.id === product.id ? 'heroicons_solid:chevron-up' : 'heroicons_solid:chevron-down'"></mat-icon>
</button>
</td>
</ng-container>
<!-- Product details row -->
<ng-container matColumnDef="productDetails">
<td
class="p-0 border-b-0"
mat-cell
*matCellDef="let product"
[attr.colspan]="productsTableColumns.length">
</div>
</div>
<div class="grid">
<ng-container *ngIf="selectedProduct?.id === product.id">
<ng-container *ngTemplateOutlet="rowDetailsTemplate; context: {$implicit: product}"></ng-container>
</ng-container>
</td>
</div>
</ng-container>
</ng-container>
</div>
<mat-paginator
class="sm:absolute sm:inset-x-0 sm:bottom-0 border-b sm:border-t sm:border-b-0 z-10 bg-gray-50 dark:bg-transparent"
[ngClass]="{'pointer-events-none': isLoading}"
[length]="pagination.length"
[pageIndex]="pagination.page"
[pageSize]="pagination.size"
[pageSizeOptions]="[5, 10, 25, 100]"
[showFirstLastButtons]="true"></mat-paginator>
</ng-container>
</ng-container>
<ng-template
#rowDetailsTemplate
let-product>
<div
class="shadow-lg overflow-hidden"
[@expandCollapse]="selectedProduct?.id === product.id ? 'expanded' : 'collapsed'">
<div class="shadow-lg overflow-hidden">
<div class="flex border-b">
<!-- Selected product form -->
<form
class="flex flex-col w-full"
[formGroup]="selectedProductForm">
<div class="flex p-8">
<div class="flex flex-col sm:flex-row p-8">
<!-- Product images and status -->
<div class="flex flex-col">
<div class="flex flex-col items-center sm:items-start">
<div class="flex flex-col items-center">
<div class="p-3 border rounded">
<ng-container *ngIf="selectedProductForm.get('images').value.length; else noImage">
@ -285,8 +235,9 @@
</div>
</div>
<div class="flex flex-auto">
<div class="flex flex-col w-2/4 pl-8">
<div class="flex flex-auto flex-wrap">
<!-- Name, SKU & etc. -->
<div class="flex flex-col w-full lg:w-2/4 sm:pl-8">
<!-- Name -->
<mat-form-field class="w-full">
@ -366,7 +317,7 @@
</div>
<!-- Cost, Base price, Tax & Price -->
<div class="flex flex-col w-1/4 pl-8">
<div class="flex flex-col w-full lg:w-1/4 sm:pl-8">
<mat-form-field class="w-full">
<mat-label>Cost</mat-label>
<span matPrefix>$</span>
@ -399,7 +350,7 @@
</div>
<!-- Weight & Tags -->
<div class="flex flex-col w-1/4 pl-8">
<div class="flex flex-col w-full lg:w-1/4 sm:pl-8">
<mat-form-field class="w-full">
<mat-label>Weight</mat-label>
<span matSuffix>lbs.</span>
@ -409,11 +360,10 @@
</mat-form-field>
<!-- Tags -->
<ng-container *ngIf="selectedProduct && selectedProduct.tags.length">
<span class="font-semibold">Tags</span>
<div class="mt-1 rounded-md border border-gray-300 shadow-sm overflow-hidden">
<span class="mb-px font-medium leading-tight">Tags</span>
<div class="mt-1.5 rounded-md border border-gray-300 shadow-sm overflow-hidden">
<!-- Header -->
<div class="flex items-center my-2 mx-3">
<div class="flex items-center -my-px py-2 px-3">
<div class="flex items-center flex-auto min-w-0">
<mat-icon
class="icon-size-5"
@ -442,7 +392,7 @@
</button>
</div>
<!-- Available tags -->
<div class="max-h-40 leading-none overflow-y-auto border-t">
<div class="h-44 leading-none overflow-y-auto border-t">
<!-- Tags -->
<ng-container *ngIf="!tagsEditMode">
<ng-container *ngFor="let tag of filteredTags; trackBy: trackByFn">
@ -476,7 +426,6 @@
</ng-container>
</div>
</ng-container>
</div>
<div
class="flex items-center h-10 min-h-10 -ml-0.5 pl-4 pr-3 leading-none cursor-pointer border-t hover:bg-gray-50 dark:hover:bg-hover"
*ngIf="shouldShowCreateTagButton(newTagInput.value)"
@ -488,7 +437,7 @@
<div class="break-all">Create "<b>{{newTagInput.value}}</b>"</div>
</div>
</div>
</ng-container>
</div>
</div>
@ -534,35 +483,6 @@
</div>
</div>
</ng-template>
</ng-container>
<tr
class="shadow"
mat-header-row
*matHeaderRowDef="productsTableColumns; sticky: true"></tr>
<tr
class="h-18 hover:bg-gray-100 dark:hover:bg-hover"
mat-row
*matRowDef="let product; columns: productsTableColumns;"></tr>
<tr
class="h-0"
mat-row
*matRowDef="let row; columns: ['productDetails']"></tr>
</table>
</div>
<mat-paginator
class="sm:absolute sm:inset-x-0 sm:bottom-0 border-b sm:border-t sm:border-b-0 z-10 bg-gray-50 dark:bg-transparent"
[ngClass]="{'pointer-events-none': isLoading}"
[length]="pagination.length"
[pageIndex]="pagination.page"
[pageSize]="pagination.size"
[pageSizeOptions]="[5, 10, 25, 100]"
[showFirstLastButtons]="true"></mat-paginator>
</ng-container>
<ng-template #noProducts>
<div class="p-8 sm:p-16 border-t text-4xl font-semibold tracking-tight text-center">There are no products!</div>

View File

@ -12,6 +12,26 @@ import { InventoryService } from 'app/modules/admin/apps/ecommerce/inventory/inv
@Component({
selector : 'inventory-list',
templateUrl : './inventory.component.html',
styles : [
/* language=SCSS */
`
.inventory-grid {
grid-template-columns: 48px auto 40px;
@screen sm {
grid-template-columns: 48px auto 112px 72px;
}
@screen md {
grid-template-columns: 48px 112px auto 112px 72px;
}
@screen lg {
grid-template-columns: 48px 112px auto 112px 96px 96px 72px;
}
}
`
],
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
animations : fuseAnimations
@ -29,8 +49,6 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
flashMessage: 'success' | 'error' | null = null;
isLoading: boolean = false;
pagination: InventoryPagination;
productsCount: number = 0;
productsTableColumns: string[] = ['sku', 'name', 'price', 'stock', 'active', 'details'];
searchInputControl: FormControl = new FormControl();
selectedProduct: InventoryProduct | null = null;
selectedProductForm: FormGroup;
@ -121,16 +139,6 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
// Get the products
this.products$ = this._inventoryService.products$;
this._inventoryService.products$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((products: InventoryProduct[]) => {
// Update the counts
this.productsCount = products.length;
// Mark for check
this._changeDetectorRef.markForCheck();
});
// Get the tags
this._inventoryService.tags$
@ -179,6 +187,18 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
*/
ngAfterViewInit(): void
{
if ( this._sort && this._paginator )
{
// Set the initial sort
this._sort.sort({
id : 'name',
start : 'asc',
disableClear: true
});
// Mark for check
this._changeDetectorRef.markForCheck();
// If the user changes the sort order...
this._sort.sortChange
.pipe(takeUntil(this._unsubscribeAll))
@ -202,6 +222,7 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
})
).subscribe();
}
}
/**
* On destroy