diff --git a/bookie/src/app/product/product-detail/product-detail-dialog.component.css b/bookie/src/app/product/product-detail/product-detail-dialog.component.css
new file mode 100644
index 00000000..e69de29b
diff --git a/bookie/src/app/product/product-detail/product-detail-dialog.component.html b/bookie/src/app/product/product-detail/product-detail-dialog.component.html
new file mode 100644
index 00000000..3efa9c22
--- /dev/null
+++ b/bookie/src/app/product/product-detail/product-detail-dialog.component.html
@@ -0,0 +1,50 @@
+
Edit Product SKU
+
+
+
+
+
+
diff --git a/bookie/src/app/product/product-detail/product-detail-dialog.component.spec.ts b/bookie/src/app/product/product-detail/product-detail-dialog.component.spec.ts
new file mode 100644
index 00000000..401e44da
--- /dev/null
+++ b/bookie/src/app/product/product-detail/product-detail-dialog.component.spec.ts
@@ -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;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ProductDetailDialogComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ProductDetailDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/bookie/src/app/product/product-detail/product-detail-dialog.component.ts b/bookie/src/app/product/product-detail/product-detail-dialog.component.ts
new file mode 100644
index 00000000..e22a2cf8
--- /dev/null
+++ b/bookie/src/app/product/product-detail/product-detail-dialog.component.ts
@@ -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);
+ 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;
+ fraction: FormControl;
+ productYield: FormControl;
+ costPrice: FormControl;
+ salePrice: FormControl;
+ menuCategory: FormControl;
+ hasHappyHour: FormControl;
+ isNotAvailable: FormControl;
+ }>;
+
+ constructor() {
+ this.form = new FormGroup({
+ units: new FormControl('', { nonNullable: true }),
+ fraction: new FormControl(1, { nonNullable: true }),
+ productYield: new FormControl(1, { nonNullable: true }),
+ costPrice: new FormControl(0, { nonNullable: true }),
+ salePrice: new FormControl(0, { nonNullable: true }),
+ menuCategory: new FormControl('', { nonNullable: true }),
+ hasHappyHour: new FormControl(false, { nonNullable: true }),
+ isNotAvailable: new FormControl(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);
+ }
+}
diff --git a/bookie/src/app/product/product-detail/product-detail.component.html b/bookie/src/app/product/product-detail/product-detail.component.html
index 9deafb35..d4c96961 100644
--- a/bookie/src/app/product/product-detail/product-detail.component.html
+++ b/bookie/src/app/product/product-detail/product-detail.component.html
@@ -130,9 +130,9 @@
Action
-
+
diff --git a/bookie/src/app/product/product-detail/product-detail.component.ts b/bookie/src/app/product/product-detail/product-detail.component.ts
index a8d87d62..aafefa06 100644
--- a/bookie/src/app/product/product-detail/product-detail.component.ts
+++ b/bookie/src/app/product/product-detail/product-detail.component.ts
@@ -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) {
diff --git a/bookie/src/app/product/product-list/product-list-datasource.ts b/bookie/src/app/product/product-list/product-list-datasource.ts
index f96b03d6..d75e9c01 100644
--- a/bookie/src/app/product/product-list/product-list-datasource.ts
+++ b/bookie/src/app/product/product-list/product-list-datasource.ts
@@ -51,23 +51,28 @@ export class ProductListDataSource extends DataSource {
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;
+ });
}
}
diff --git a/bookie/src/app/product/product-list/product-list.component.ts b/bookie/src/app/product/product-list/product-list.component.ts
index 8517dcc6..28aa31dc 100644
--- a/bookie/src/app/product/product-list/product-list.component.ts
+++ b/bookie/src/app/product/product-list/product-list.component.ts
@@ -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();
+ searchFilter = new BehaviorSubject('');
menuCategoryFilter = new BehaviorSubject('');
data: BehaviorSubject = new BehaviorSubject([]);
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) {
- // 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() {