(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 { MatSortModule } from '@angular/material/sort';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { SharedModule } from 'app/shared/shared.module'; import { SharedModule } from 'app/shared/shared.module';
import { InventoryComponent } from 'app/modules/admin/apps/ecommerce/inventory/inventory.component'; 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, MatSortModule,
MatSelectModule, MatSelectModule,
MatSlideToggleModule, MatSlideToggleModule,
MatTableModule,
MatTooltipModule, MatTooltipModule,
SharedModule 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 --> <!-- 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"> <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"> <div class="flex flex-auto overflow-hidden">
<!-- Products list --> <!-- Products list -->
<div class="flex flex-col flex-auto sm:mb-18 overflow-hidden"> <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="productsCount > 0; else noProducts"> <ng-container *ngIf="products.length > 0; else noProducts">
<div class="grid">
<!-- Table wrapper --> <!-- Header -->
<div <div
class="overflow-x-auto sm:overflow-y-auto" 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"
cdkScrollable>
<!-- Table -->
<table
class="w-full min-w-320 table-fixed bg-transparent"
[ngClass]="{'pointer-events-none': isLoading}"
mat-table
matSort matSort
[matSortActive]="'name'" matSortDisableClear>
[matSortDisableClear]="true" <div></div>
[matSortDirection]="'asc'" <div
[multiTemplateDataRows]="true" class="hidden md:block"
[dataSource]="products$" [mat-sort-header]="'sku'">
[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>
SKU SKU
</th> </div>
<td <div [mat-sort-header]="'name'">Name</div>
class="px-8" <div
mat-cell class="hidden sm:block"
*matCellDef="let product"> [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"> <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 <img
class="w-8" class="w-8"
*ngIf="product.thumbnail" *ngIf="product.thumbnail"
[alt]="'Product thumbnail image'"
[src]="product.thumbnail"> [src]="product.thumbnail">
<span <div
class="flex items-center justify-center w-full h-full text-xs font-semibold leading-none text-center uppercase" class="flex items-center justify-center w-full h-full text-xs font-semibold leading-none text-center uppercase"
*ngIf="!product.thumbnail"> *ngIf="!product.thumbnail">
No Image NO THUMB
</span> </div>
</span> </div>
<span class="truncate">{{product.sku}}</span> </div>
<!-- SKU -->
<div class="hidden md:block truncate">
{{product.sku}}
</div> </div>
</td>
</ng-container>
<!-- Name --> <!-- Name -->
<ng-container matColumnDef="name"> <div class="truncate">
<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">
{{product.name}} {{product.name}}
</td> </div>
</ng-container>
<!-- Price --> <!-- Price -->
<ng-container matColumnDef="price"> <div class="hidden sm:block">
<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">
{{product.price | currency:'USD':'symbol':'1.2-2'}} {{product.price | currency:'USD':'symbol':'1.2-2'}}
</td> </div>
</ng-container>
<!-- Stock --> <!-- Stock -->
<ng-container matColumnDef="stock"> <div class="hidden lg:flex items-center">
<th <div class="min-w-4">{{product.stock}}</div>
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>
<!-- Low stock --> <!-- Low stock -->
<span <div
class="flex items-end ml-2 w-1 h-4 bg-red-200 rounded overflow-hidden" class="flex items-end ml-2 w-1 h-4 bg-red-200 rounded overflow-hidden"
*ngIf="product.stock < 20"> *ngIf="product.stock < 20">
<span class="flex w-full h-1/3 bg-red-600"></span> <div class="flex w-full h-1/3 bg-red-600"></div>
</span> </div>
<!-- Medium stock --> <!-- Medium stock -->
<span <div
class="flex items-end ml-2 w-1 h-4 bg-orange-200 rounded overflow-hidden" class="flex items-end ml-2 w-1 h-4 bg-orange-200 rounded overflow-hidden"
*ngIf="product.stock >= 20 && product.stock < 30"> *ngIf="product.stock >= 20 && product.stock < 30">
<span class="flex w-full h-2/4 bg-orange-400"></span> <div class="flex w-full h-2/4 bg-orange-400"></div>
</span> </div>
<!-- High stock --> <!-- High stock -->
<span <div
class="flex items-end ml-2 w-1 h-4 bg-green-100 rounded overflow-hidden" class="flex items-end ml-2 w-1 h-4 bg-green-100 rounded overflow-hidden"
*ngIf="product.stock >= 30"> *ngIf="product.stock >= 30">
<span class="flex w-full h-full bg-green-400"></span> <div class="flex w-full h-full bg-green-400"></div>
</span> </div>
</span> </div>
</td>
</ng-container>
<!-- Active --> <!-- Active -->
<ng-container matColumnDef="active"> <div class="hidden lg:block">
<th <ng-container *ngIf="product.active">
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">
<mat-icon <mat-icon
class="text-green-400 icon-size-5" class="text-green-400 icon-size-5"
*ngIf="product.active"
[svgIcon]="'heroicons_solid:check'"></mat-icon> [svgIcon]="'heroicons_solid:check'"></mat-icon>
</ng-container>
<ng-container *ngIf="!product.active">
<mat-icon <mat-icon
class="text-gray-400 icon-size-5" class="text-gray-400 icon-size-5"
*ngIf="!product.active"
[svgIcon]="'heroicons_solid:x'"></mat-icon> [svgIcon]="'heroicons_solid:x'"></mat-icon>
</td>
</ng-container> </ng-container>
</div>
<!-- Details --> <!-- Details button -->
<ng-container matColumnDef="details"> <div>
<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">
<button <button
class="min-w-10 min-h-7 h-7 px-2 leading-6" class="min-w-10 min-h-7 h-7 px-2 leading-6"
mat-stroked-button mat-stroked-button
@ -211,37 +155,43 @@
class="icon-size-5" class="icon-size-5"
[svgIcon]="selectedProduct?.id === product.id ? 'heroicons_solid:chevron-up' : 'heroicons_solid:chevron-down'"></mat-icon> [svgIcon]="selectedProduct?.id === product.id ? 'heroicons_solid:chevron-up' : 'heroicons_solid:chevron-down'"></mat-icon>
</button> </button>
</td> </div>
</ng-container> </div>
<div class="grid">
<!-- Product details row -->
<ng-container matColumnDef="productDetails">
<td
class="p-0 border-b-0"
mat-cell
*matCellDef="let product"
[attr.colspan]="productsTableColumns.length">
<ng-container *ngIf="selectedProduct?.id === product.id"> <ng-container *ngIf="selectedProduct?.id === product.id">
<ng-container *ngTemplateOutlet="rowDetailsTemplate; context: {$implicit: product}"></ng-container> <ng-container *ngTemplateOutlet="rowDetailsTemplate; context: {$implicit: product}"></ng-container>
</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 <ng-template
#rowDetailsTemplate #rowDetailsTemplate
let-product> let-product>
<div <div class="shadow-lg overflow-hidden">
class="shadow-lg overflow-hidden"
[@expandCollapse]="selectedProduct?.id === product.id ? 'expanded' : 'collapsed'">
<div class="flex border-b"> <div class="flex border-b">
<!-- Selected product form --> <!-- Selected product form -->
<form <form
class="flex flex-col w-full" class="flex flex-col w-full"
[formGroup]="selectedProductForm"> [formGroup]="selectedProductForm">
<div class="flex p-8"> <div class="flex flex-col sm:flex-row p-8">
<!-- Product images and status --> <!-- 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="flex flex-col items-center">
<div class="p-3 border rounded"> <div class="p-3 border rounded">
<ng-container *ngIf="selectedProductForm.get('images').value.length; else noImage"> <ng-container *ngIf="selectedProductForm.get('images').value.length; else noImage">
@ -285,8 +235,9 @@
</div> </div>
</div> </div>
<div class="flex flex-auto"> <div class="flex flex-auto flex-wrap">
<div class="flex flex-col w-2/4 pl-8"> <!-- Name, SKU & etc. -->
<div class="flex flex-col w-full lg:w-2/4 sm:pl-8">
<!-- Name --> <!-- Name -->
<mat-form-field class="w-full"> <mat-form-field class="w-full">
@ -366,7 +317,7 @@
</div> </div>
<!-- Cost, Base price, Tax & Price --> <!-- 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-form-field class="w-full">
<mat-label>Cost</mat-label> <mat-label>Cost</mat-label>
<span matPrefix>$</span> <span matPrefix>$</span>
@ -399,7 +350,7 @@
</div> </div>
<!-- Weight & Tags --> <!-- 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-form-field class="w-full">
<mat-label>Weight</mat-label> <mat-label>Weight</mat-label>
<span matSuffix>lbs.</span> <span matSuffix>lbs.</span>
@ -409,11 +360,10 @@
</mat-form-field> </mat-form-field>
<!-- Tags --> <!-- Tags -->
<ng-container *ngIf="selectedProduct && selectedProduct.tags.length"> <span class="mb-px font-medium leading-tight">Tags</span>
<span class="font-semibold">Tags</span> <div class="mt-1.5 rounded-md border border-gray-300 shadow-sm overflow-hidden">
<div class="mt-1 rounded-md border border-gray-300 shadow-sm overflow-hidden">
<!-- Header --> <!-- 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"> <div class="flex items-center flex-auto min-w-0">
<mat-icon <mat-icon
class="icon-size-5" class="icon-size-5"
@ -442,7 +392,7 @@
</button> </button>
</div> </div>
<!-- Available tags --> <!-- 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 --> <!-- Tags -->
<ng-container *ngIf="!tagsEditMode"> <ng-container *ngIf="!tagsEditMode">
<ng-container *ngFor="let tag of filteredTags; trackBy: trackByFn"> <ng-container *ngFor="let tag of filteredTags; trackBy: trackByFn">
@ -476,7 +426,6 @@
</ng-container> </ng-container>
</div> </div>
</ng-container> </ng-container>
</div>
<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" 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)" *ngIf="shouldShowCreateTagButton(newTagInput.value)"
@ -488,7 +437,7 @@
<div class="break-all">Create "<b>{{newTagInput.value}}</b>"</div> <div class="break-all">Create "<b>{{newTagInput.value}}</b>"</div>
</div> </div>
</div> </div>
</ng-container> </div>
</div> </div>
@ -534,35 +483,6 @@
</div> </div>
</div> </div>
</ng-template> </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> <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> <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({ @Component({
selector : 'inventory-list', selector : 'inventory-list',
templateUrl : './inventory.component.html', 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, encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
animations : fuseAnimations animations : fuseAnimations
@ -29,8 +49,6 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
flashMessage: 'success' | 'error' | null = null; flashMessage: 'success' | 'error' | null = null;
isLoading: boolean = false; isLoading: boolean = false;
pagination: InventoryPagination; pagination: InventoryPagination;
productsCount: number = 0;
productsTableColumns: string[] = ['sku', 'name', 'price', 'stock', 'active', 'details'];
searchInputControl: FormControl = new FormControl(); searchInputControl: FormControl = new FormControl();
selectedProduct: InventoryProduct | null = null; selectedProduct: InventoryProduct | null = null;
selectedProductForm: FormGroup; selectedProductForm: FormGroup;
@ -121,16 +139,6 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
// Get the products // Get the products
this.products$ = this._inventoryService.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 // Get the tags
this._inventoryService.tags$ this._inventoryService.tags$
@ -179,6 +187,18 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
*/ */
ngAfterViewInit(): void 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... // If the user changes the sort order...
this._sort.sortChange this._sort.sortChange
.pipe(takeUntil(this._unsubscribeAll)) .pipe(takeUntil(this._unsubscribeAll))
@ -202,6 +222,7 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
}) })
).subscribe(); ).subscribe();
} }
}
/** /**
* On destroy * On destroy