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">
|
||||
<mat-header-cell *matHeaderCellDef class="center">Action</mat-header-cell>
|
||||
<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>
|
||||
</button> -->
|
||||
</button>
|
||||
<button mat-icon-button tabindex="-1" color="warn" (click)="deleteRow(row)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
|
||||
@ -20,6 +20,7 @@ import { SaleCategory } from '../../core/sale-category';
|
||||
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
||||
import { ProductService } from '../product.service';
|
||||
import { ProductDetailDatasource } from './product-detail-datasource';
|
||||
import { ProductDetailDialogComponent } from './product-detail-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-product-detail',
|
||||
@ -207,23 +208,30 @@ export class ProductDetailComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
|
||||
editRow(row: StockKeepingUnit) {
|
||||
// const dialogRef = this.dialog.open(ProductDetailDialogComponent, {
|
||||
// width: '750px',
|
||||
// data: {
|
||||
// item: { ...row },
|
||||
// isSold: this.item.isSold,
|
||||
// isPurchased: this.item.isPurchased,
|
||||
// },
|
||||
// });
|
||||
// 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();
|
||||
// });
|
||||
const dialogRef = this.dialog.open(ProductDetailDialogComponent, {
|
||||
width: '750px',
|
||||
data: {
|
||||
item: JSON.parse(JSON.stringify(row)) as StockKeepingUnit,
|
||||
units: row.units,
|
||||
fraction: row.fraction,
|
||||
productYield: row.productYield,
|
||||
costPrice: row.costPrice,
|
||||
salePrice: row.salePrice,
|
||||
menuCategory: row.menuCategory?.id ?? '',
|
||||
hasHappyHour: row.hasHappyHour,
|
||||
isNotAvailable: row.isNotAvailable,
|
||||
menuCategories: this.menuCategories,
|
||||
},
|
||||
});
|
||||
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) {
|
||||
|
||||
@ -51,23 +51,28 @@ export class ProductListDataSource extends DataSource<Product> {
|
||||
disconnect() {}
|
||||
|
||||
private getFilteredData(data: Product[], search: string, menuCategory: string): Product[] {
|
||||
return data;
|
||||
// return data
|
||||
// .filter(
|
||||
// (x: Product) =>
|
||||
// search === null ||
|
||||
// search === undefined ||
|
||||
// search === '' ||
|
||||
// `${x.name} ${x.units} ${x.saleCategory?.name} ${x.menuCategory?.name}`
|
||||
// .toLowerCase()
|
||||
// .indexOf(search.toLowerCase()) !== -1,
|
||||
// )
|
||||
// .filter(
|
||||
// (x) =>
|
||||
// menuCategory === null ||
|
||||
// menuCategory === undefined ||
|
||||
// menuCategory === '' ||
|
||||
// (x.menuCategory as MenuCategory).id === menuCategory,
|
||||
// );
|
||||
const tokens = (search ?? '').toLowerCase().split(/\s+/).filter(Boolean);
|
||||
return data.filter((product: Product) => {
|
||||
search = search.toLowerCase();
|
||||
|
||||
const skus = product.skus ?? [];
|
||||
|
||||
// 1) Search: match ANY product/sku fields
|
||||
const matchesSearch =
|
||||
tokens.length === 0 ||
|
||||
tokens.every(
|
||||
(token) =>
|
||||
`${product.name ?? ''} ${product.fractionUnits ?? ''} ${product.saleCategory?.name ?? ''}`
|
||||
.toLowerCase()
|
||||
.includes(token) ||
|
||||
skus.some((k) => {
|
||||
const hay = `${k.units ?? ''} ${k.menuCategory?.name ?? ''}`.toLowerCase();
|
||||
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 { Component, OnInit, inject } from '@angular/core';
|
||||
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 { MatTableModule } from '@angular/material/table';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
import { MenuCategory } from '../../core/menu-category';
|
||||
@ -46,7 +46,7 @@ export class ProductListComponent implements OnInit {
|
||||
private toCsv = inject(ToCsvService);
|
||||
private ser = inject(ProductService);
|
||||
|
||||
searchFilter = new Observable<string>();
|
||||
searchFilter = new BehaviorSubject<string>('');
|
||||
menuCategoryFilter = new BehaviorSubject<string>('');
|
||||
data: BehaviorSubject<Product[]> = new BehaviorSubject<Product[]>([]);
|
||||
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.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) {
|
||||
@ -102,16 +110,16 @@ export class ProductListComponent implements OnInit {
|
||||
}
|
||||
|
||||
dropTable(event: CdkDragDrop<ProductListDataSource>) {
|
||||
// const prevIndex = this.dataSource.filteredData.indexOf(event.item.data);
|
||||
// moveItemInArray(this.dataSource.filteredData, prevIndex, event.currentIndex);
|
||||
// if (this.dataSource.menuCategory === undefined) {
|
||||
// this.list = this.dataSource.filteredData;
|
||||
// } else {
|
||||
// this.list = this.list
|
||||
// .filter((x) => (x.menuCategory as MenuCategory).id !== this.dataSource.menuCategory)
|
||||
// .concat(this.dataSource.filteredData);
|
||||
// }
|
||||
// this.data.next(this.list);
|
||||
const prevIndex = this.dataSource.filteredData.indexOf(event.item.data);
|
||||
moveItemInArray(this.dataSource.filteredData, prevIndex, event.currentIndex);
|
||||
if (this.dataSource.menuCategory === undefined) {
|
||||
this.list = this.dataSource.filteredData;
|
||||
} else {
|
||||
this.list = this.list
|
||||
.filter((x) => !x.skus.some((k) => k.menuCategory?.id === this.dataSource.menuCategory))
|
||||
.concat(this.dataSource.filteredData);
|
||||
}
|
||||
this.data.next(this.list);
|
||||
}
|
||||
|
||||
exportCsv() {
|
||||
|
||||
Reference in New Issue
Block a user