Product list and detail fully working. only thing to check is the update sort order route
This commit is contained in:
@ -0,0 +1,50 @@
|
|||||||
|
<h1 mat-dialog-title>Edit Product SKU</h1>
|
||||||
|
<div mat-dialog-content>
|
||||||
|
<form [formGroup]="form">
|
||||||
|
<div class="flex flex-row flex-wrap justify-around content-start items-start sm:max-lg:flex-col">
|
||||||
|
<mat-form-field class="flex-auto">
|
||||||
|
<mat-label>Units</mat-label>
|
||||||
|
<input matInput formControlName="units" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-auto">
|
||||||
|
<mat-label>Fraction</mat-label>
|
||||||
|
<input matInput type="number" formControlName="fraction" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-auto">
|
||||||
|
<mat-label>Yield</mat-label>
|
||||||
|
<input matInput type="number" formControlName="productYield" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-auto">
|
||||||
|
<mat-label>Cost Price</mat-label>
|
||||||
|
<input matInput type="number" formControlName="costPrice" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-auto">
|
||||||
|
<mat-label>Sale Price</mat-label>
|
||||||
|
<input matInput type="number" formControlName="salePrice" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-auto">
|
||||||
|
<mat-label>Menu Category</mat-label>
|
||||||
|
<mat-select formControlName="menuCategory">
|
||||||
|
@for (mc of menuCategories; track mc) {
|
||||||
|
<mat-option [value]="mc.id">
|
||||||
|
{{ mc.name }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-checkbox formControlName="hasHappyHour" class="flex-auto">Has Happy Hour?</mat-checkbox>
|
||||||
|
<mat-checkbox formControlName="isNotAvailable" class="flex-auto">Not Available?</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button mat-raised-button color="warn" [mat-dialog-close]="false" cdkFocusInitial>Cancel</button>
|
||||||
|
<button mat-raised-button color="primary" (click)="accept()">Ok</button>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ProductDetailDialogComponent } from './product-detail-dialog.component';
|
||||||
|
|
||||||
|
describe('ProductDetailDialogComponent', () => {
|
||||||
|
let component: ProductDetailDialogComponent;
|
||||||
|
let fixture: ComponentFixture<ProductDetailDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ProductDetailDialogComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ProductDetailDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
import { CdkScrollableModule } from '@angular/cdk/scrolling';
|
||||||
|
import { Component, inject, OnInit } from '@angular/core';
|
||||||
|
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { MenuCategory } from 'src/app/core/menu-category';
|
||||||
|
|
||||||
|
import { StockKeepingUnit } from '../../core/product';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-product-detail-dialog',
|
||||||
|
templateUrl: './product-detail-dialog.component.html',
|
||||||
|
styleUrls: ['./product-detail-dialog.component.css'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CdkScrollableModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatCheckboxModule,
|
||||||
|
MatDialogModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatInputModule,
|
||||||
|
MatSelectModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ProductDetailDialogComponent implements OnInit {
|
||||||
|
private readonly dialogRef = inject<MatDialogRef<ProductDetailDialogComponent>>(MatDialogRef);
|
||||||
|
menuCategories: MenuCategory[] = [];
|
||||||
|
data = inject<{
|
||||||
|
item: StockKeepingUnit;
|
||||||
|
units: string;
|
||||||
|
fraction: number;
|
||||||
|
productYield: number;
|
||||||
|
costPrice: number;
|
||||||
|
salePrice: number;
|
||||||
|
menuCategory: string;
|
||||||
|
hasHappyHour: boolean;
|
||||||
|
isNotAvailable: boolean;
|
||||||
|
menuCategories: MenuCategory[];
|
||||||
|
}>(MAT_DIALOG_DATA);
|
||||||
|
|
||||||
|
form: FormGroup<{
|
||||||
|
units: FormControl<string>;
|
||||||
|
fraction: FormControl<number>;
|
||||||
|
productYield: FormControl<number>;
|
||||||
|
costPrice: FormControl<number>;
|
||||||
|
salePrice: FormControl<number>;
|
||||||
|
menuCategory: FormControl<string>;
|
||||||
|
hasHappyHour: FormControl<boolean>;
|
||||||
|
isNotAvailable: FormControl<boolean>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.form = new FormGroup({
|
||||||
|
units: new FormControl<string>('', { nonNullable: true }),
|
||||||
|
fraction: new FormControl<number>(1, { nonNullable: true }),
|
||||||
|
productYield: new FormControl<number>(1, { nonNullable: true }),
|
||||||
|
costPrice: new FormControl<number>(0, { nonNullable: true }),
|
||||||
|
salePrice: new FormControl<number>(0, { nonNullable: true }),
|
||||||
|
menuCategory: new FormControl<string>('', { nonNullable: true }),
|
||||||
|
hasHappyHour: new FormControl<boolean>(false, { nonNullable: true }),
|
||||||
|
isNotAvailable: new FormControl<boolean>(false, { nonNullable: true }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
// Populate from the row being edited
|
||||||
|
this.form.setValue({
|
||||||
|
units: this.data.item.units ?? '',
|
||||||
|
fraction: this.data.item.fraction ?? 1,
|
||||||
|
productYield: this.data.item.productYield ?? 1,
|
||||||
|
costPrice: this.data.item.costPrice ?? 0,
|
||||||
|
salePrice: this.data.item.salePrice ?? 0,
|
||||||
|
menuCategory: this.data.item.menuCategory?.id ?? '',
|
||||||
|
hasHappyHour: this.data.item.hasHappyHour ?? false,
|
||||||
|
isNotAvailable: this.data.item.isNotAvailable ?? false,
|
||||||
|
});
|
||||||
|
this.menuCategories = this.data.menuCategories;
|
||||||
|
}
|
||||||
|
|
||||||
|
accept(): void {
|
||||||
|
const formValue = this.form.value;
|
||||||
|
const fraction = formValue.fraction ?? 0;
|
||||||
|
if (fraction < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const productYield = formValue.productYield ?? 0;
|
||||||
|
if (productYield < 0 || productYield > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const costPrice = formValue.costPrice ?? 0;
|
||||||
|
if (costPrice < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const salePrice = formValue.salePrice ?? 0;
|
||||||
|
if (salePrice < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.data.item.units = (formValue.units ?? '').trim();
|
||||||
|
this.data.item.fraction = fraction;
|
||||||
|
this.data.item.productYield = productYield;
|
||||||
|
this.data.item.costPrice = costPrice;
|
||||||
|
this.data.item.salePrice = salePrice;
|
||||||
|
this.data.item.hasHappyHour = formValue.hasHappyHour ?? false;
|
||||||
|
this.data.item.isNotAvailable = formValue.isNotAvailable ?? false;
|
||||||
|
if (this.data.item.menuCategory === null || this.data.item.menuCategory === undefined) {
|
||||||
|
this.data.item.menuCategory = new MenuCategory();
|
||||||
|
}
|
||||||
|
this.data.item.menuCategory.id = formValue.menuCategory ?? '';
|
||||||
|
this.data.item.menuCategory.name = this.menuCategories.find((mc) => mc.id === formValue.menuCategory)?.name ?? '';
|
||||||
|
this.dialogRef.close(this.data.item);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -130,9 +130,9 @@
|
|||||||
<ng-container matColumnDef="action">
|
<ng-container matColumnDef="action">
|
||||||
<mat-header-cell *matHeaderCellDef class="center">Action</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef class="center">Action</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row" class="center">
|
<mat-cell *matCellDef="let row" class="center">
|
||||||
<!-- <button mat-icon-button tabindex="-1" (click)="editRow(row)">
|
<button mat-icon-button tabindex="-1" (click)="editRow(row)">
|
||||||
<mat-icon>edit</mat-icon>
|
<mat-icon>edit</mat-icon>
|
||||||
</button> -->
|
</button>
|
||||||
<button mat-icon-button tabindex="-1" color="warn" (click)="deleteRow(row)">
|
<button mat-icon-button tabindex="-1" color="warn" (click)="deleteRow(row)">
|
||||||
<mat-icon>delete</mat-icon>
|
<mat-icon>delete</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { SaleCategory } from '../../core/sale-category';
|
|||||||
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
||||||
import { ProductService } from '../product.service';
|
import { ProductService } from '../product.service';
|
||||||
import { ProductDetailDatasource } from './product-detail-datasource';
|
import { ProductDetailDatasource } from './product-detail-datasource';
|
||||||
|
import { ProductDetailDialogComponent } from './product-detail-dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-product-detail',
|
selector: 'app-product-detail',
|
||||||
@ -207,23 +208,30 @@ export class ProductDetailComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
editRow(row: StockKeepingUnit) {
|
editRow(row: StockKeepingUnit) {
|
||||||
// const dialogRef = this.dialog.open(ProductDetailDialogComponent, {
|
const dialogRef = this.dialog.open(ProductDetailDialogComponent, {
|
||||||
// width: '750px',
|
width: '750px',
|
||||||
// data: {
|
data: {
|
||||||
// item: { ...row },
|
item: JSON.parse(JSON.stringify(row)) as StockKeepingUnit,
|
||||||
// isSold: this.item.isSold,
|
units: row.units,
|
||||||
// isPurchased: this.item.isPurchased,
|
fraction: row.fraction,
|
||||||
// },
|
productYield: row.productYield,
|
||||||
// });
|
costPrice: row.costPrice,
|
||||||
// dialogRef.afterClosed().subscribe((result: boolean | StockKeepingUnit) => {
|
salePrice: row.salePrice,
|
||||||
// if (!result) {
|
menuCategory: row.menuCategory?.id ?? '',
|
||||||
// return;
|
hasHappyHour: row.hasHappyHour,
|
||||||
// }
|
isNotAvailable: row.isNotAvailable,
|
||||||
// const j = result as StockKeepingUnit;
|
menuCategories: this.menuCategories,
|
||||||
// Object.assign(row, j);
|
},
|
||||||
// this.skus.next(this.item.skus);
|
});
|
||||||
// this.resetAddRow();
|
dialogRef.afterClosed().subscribe((result: boolean | StockKeepingUnit) => {
|
||||||
// });
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const j = result as StockKeepingUnit;
|
||||||
|
Object.assign(row, j);
|
||||||
|
this.skus.next(this.item.skus);
|
||||||
|
this.resetAddRow();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteRow(row: StockKeepingUnit) {
|
deleteRow(row: StockKeepingUnit) {
|
||||||
|
|||||||
@ -51,23 +51,28 @@ export class ProductListDataSource extends DataSource<Product> {
|
|||||||
disconnect() {}
|
disconnect() {}
|
||||||
|
|
||||||
private getFilteredData(data: Product[], search: string, menuCategory: string): Product[] {
|
private getFilteredData(data: Product[], search: string, menuCategory: string): Product[] {
|
||||||
return data;
|
const tokens = (search ?? '').toLowerCase().split(/\s+/).filter(Boolean);
|
||||||
// return data
|
return data.filter((product: Product) => {
|
||||||
// .filter(
|
search = search.toLowerCase();
|
||||||
// (x: Product) =>
|
|
||||||
// search === null ||
|
const skus = product.skus ?? [];
|
||||||
// search === undefined ||
|
|
||||||
// search === '' ||
|
// 1) Search: match ANY product/sku fields
|
||||||
// `${x.name} ${x.units} ${x.saleCategory?.name} ${x.menuCategory?.name}`
|
const matchesSearch =
|
||||||
// .toLowerCase()
|
tokens.length === 0 ||
|
||||||
// .indexOf(search.toLowerCase()) !== -1,
|
tokens.every(
|
||||||
// )
|
(token) =>
|
||||||
// .filter(
|
`${product.name ?? ''} ${product.fractionUnits ?? ''} ${product.saleCategory?.name ?? ''}`
|
||||||
// (x) =>
|
.toLowerCase()
|
||||||
// menuCategory === null ||
|
.includes(token) ||
|
||||||
// menuCategory === undefined ||
|
skus.some((k) => {
|
||||||
// menuCategory === '' ||
|
const hay = `${k.units ?? ''} ${k.menuCategory?.name ?? ''}`.toLowerCase();
|
||||||
// (x.menuCategory as MenuCategory).id === menuCategory,
|
return hay.includes(token);
|
||||||
// );
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const matchesMenuCategory = menuCategory === '' || skus.some((k) => (k.menuCategory?.id ?? '') === menuCategory);
|
||||||
|
return matchesSearch && matchesMenuCategory;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { CdkDragDrop, DragDropModule } from '@angular/cdk/drag-drop';
|
import { CdkDragDrop, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||||
import { AsyncPipe, DecimalPipe, CurrencyPipe } from '@angular/common';
|
import { AsyncPipe, DecimalPipe, CurrencyPipe } from '@angular/common';
|
||||||
import { Component, OnInit, inject } from '@angular/core';
|
import { Component, OnInit, inject } from '@angular/core';
|
||||||
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||||
@ -11,7 +11,7 @@ import { MatSelectModule } from '@angular/material/select';
|
|||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||||
|
|
||||||
import { MenuCategory } from '../../core/menu-category';
|
import { MenuCategory } from '../../core/menu-category';
|
||||||
@ -46,7 +46,7 @@ export class ProductListComponent implements OnInit {
|
|||||||
private toCsv = inject(ToCsvService);
|
private toCsv = inject(ToCsvService);
|
||||||
private ser = inject(ProductService);
|
private ser = inject(ProductService);
|
||||||
|
|
||||||
searchFilter = new Observable<string>();
|
searchFilter = new BehaviorSubject<string>('');
|
||||||
menuCategoryFilter = new BehaviorSubject<string>('');
|
menuCategoryFilter = new BehaviorSubject<string>('');
|
||||||
data: BehaviorSubject<Product[]> = new BehaviorSubject<Product[]>([]);
|
data: BehaviorSubject<Product[]> = new BehaviorSubject<Product[]>([]);
|
||||||
dataSource: ProductListDataSource = new ProductListDataSource(this.searchFilter, this.menuCategoryFilter, this.data);
|
dataSource: ProductListDataSource = new ProductListDataSource(this.searchFilter, this.menuCategoryFilter, this.data);
|
||||||
@ -69,7 +69,15 @@ export class ProductListComponent implements OnInit {
|
|||||||
this.data.subscribe((data: Product[]) => {
|
this.data.subscribe((data: Product[]) => {
|
||||||
this.list = data;
|
this.list = data;
|
||||||
});
|
});
|
||||||
this.searchFilter = this.form.controls.filter.valueChanges.pipe(debounceTime(150), distinctUntilChanged());
|
this.form.controls.filter.valueChanges.pipe(debounceTime(150), distinctUntilChanged()).subscribe((value) => {
|
||||||
|
this.searchFilter.next(value ?? '');
|
||||||
|
});
|
||||||
|
this.menuCategoryFilter.subscribe((val) => {
|
||||||
|
console.log('Menu category filter changed to ', val);
|
||||||
|
});
|
||||||
|
this.searchFilter.subscribe((val) => {
|
||||||
|
console.log('Search filter changed to ', val);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
filterOn(val: string) {
|
filterOn(val: string) {
|
||||||
@ -102,16 +110,16 @@ export class ProductListComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dropTable(event: CdkDragDrop<ProductListDataSource>) {
|
dropTable(event: CdkDragDrop<ProductListDataSource>) {
|
||||||
// const prevIndex = this.dataSource.filteredData.indexOf(event.item.data);
|
const prevIndex = this.dataSource.filteredData.indexOf(event.item.data);
|
||||||
// moveItemInArray(this.dataSource.filteredData, prevIndex, event.currentIndex);
|
moveItemInArray(this.dataSource.filteredData, prevIndex, event.currentIndex);
|
||||||
// if (this.dataSource.menuCategory === undefined) {
|
if (this.dataSource.menuCategory === undefined) {
|
||||||
// this.list = this.dataSource.filteredData;
|
this.list = this.dataSource.filteredData;
|
||||||
// } else {
|
} else {
|
||||||
// this.list = this.list
|
this.list = this.list
|
||||||
// .filter((x) => (x.menuCategory as MenuCategory).id !== this.dataSource.menuCategory)
|
.filter((x) => !x.skus.some((k) => k.menuCategory?.id === this.dataSource.menuCategory))
|
||||||
// .concat(this.dataSource.filteredData);
|
.concat(this.dataSource.filteredData);
|
||||||
// }
|
}
|
||||||
// this.data.next(this.list);
|
this.data.next(this.list);
|
||||||
}
|
}
|
||||||
|
|
||||||
exportCsv() {
|
exportCsv() {
|
||||||
|
|||||||
Reference in New Issue
Block a user