diff --git a/bookie/src/app/menu-engineering-report/menu-engineering-report.component.ts b/bookie/src/app/menu-engineering-report/menu-engineering-report.component.ts index a448c2b..b10a089 100644 --- a/bookie/src/app/menu-engineering-report/menu-engineering-report.component.ts +++ b/bookie/src/app/menu-engineering-report/menu-engineering-report.component.ts @@ -1,6 +1,5 @@ import { DecimalPipe, PercentPipe } from '@angular/common'; -import { Component, inject, computed, linkedSignal, input, afterNextRender } from '@angular/core'; -import { toObservable } from '@angular/core/rxjs-interop'; +import { Component, inject, computed, debounced, linkedSignal, input, afterNextRender } from '@angular/core'; import { form, FormField } from '@angular/forms/signals'; import { MatButtonModule } from '@angular/material/button'; import { MatDatepickerModule } from '@angular/material/datepicker'; @@ -11,13 +10,13 @@ import { MatPaginatorModule } from '@angular/material/paginator'; import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSortModule } from '@angular/material/sort'; import { MatTableModule } from '@angular/material/table'; -import { ActivatedRoute, Router } from '@angular/router'; +import { Router } from '@angular/router'; +import { equal, larger, largerEq, smaller, smallerEq } from 'mathjs'; import moment from 'moment'; -import { Observable } from 'rxjs'; -import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { ToCsvService } from '../shared/to-csv.service'; import { MenuEngineeringReport } from './menu-engineering-report'; +import { MenuEngineeringReportItem } from './menu-engineering-report-item'; import { MenuEngineeringReportService } from './menu-engineering-report.service'; export interface MenuEngineeringReportFormData { @@ -45,7 +44,6 @@ export interface MenuEngineeringReportFormData { ], }) export class MenuEngineeringReportComponent { - private route = inject(ActivatedRoute); private router = inject(Router); private toCsv = inject(ToCsvService); private snackBar = inject(MatSnackBar); @@ -56,8 +54,7 @@ export class MenuEngineeringReportComponent { infoResource = this.ser.get(this.startDate, this.finishDate); info = computed(() => this.infoResource.value() ?? new MenuEngineeringReport()); - filter: Observable; - dataSource = computed(() => this.info().amounts); + formModel = linkedSignal({ source: this.info, computation: (infoVal): MenuEngineeringReportFormData => { @@ -78,14 +75,51 @@ export class MenuEngineeringReportComponent { form = form(this.formModel); + filterSignal = computed(() => this.formModel().filter ?? ''); + debouncedFilter = debounced(this.filterSignal, 150); + + dataSource = computed(() => { + const data = this.info().amounts ?? []; + const filterValue = (this.debouncedFilter.value() ?? '').trim().toLowerCase(); + if (!filterValue) { + return data; + } + const filtered = filterValue.split(/\s+/).reduce( + (p: MenuEngineeringReportItem[], c: string) => + p.filter((x) => { + if (c.startsWith('n:')) { + return x.name.toLowerCase().includes(c.substring(2)); + } + if (c.startsWith('sc:')) { + return x.saleCategory.toLowerCase().includes(c.substring(3)); + } + if (c.startsWith('mc:')) { + return x.menuCategory.toLowerCase().includes(c.substring(3)); + } + if (c.startsWith('q:')) { + const result = c.match(/^q:(?=|<=|>=|<|>)(?\d+(\.\d+)?)$/); + if (result && result.groups) { + return math_it_up(result.groups['sign'])(x.quantity, +result.groups['amount']); + } + } + if (c.startsWith('s:')) { + const result = c.match(/^s:(?=|<=|>=|<|>)(?\d+(\.\d+)?)$/); + if (result && result.groups) { + return math_it_up(result.groups['sign'])(x.sales, +result.groups['amount']); + } + } + const itemString = `${x.name} ${x.saleCategory} ${x.menuCategory}`.toLowerCase(); + return itemString.includes(c); + }), + [...data], + ); + return recalculate(filtered); + }); + /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ['name', 'price', 'saleCategory', 'menuCategory', 'quantity', 'sales']; constructor() { - // Create form - // Listen to Filter Change - const filterSignal = computed(() => this.formModel().filter); - this.filter = toObservable(filterSignal).pipe(debounceTime(150), distinctUntilChanged()); afterNextRender(() => { this.form().focusBoundControl(); }); @@ -142,3 +176,39 @@ export class MenuEngineeringReportComponent { document.body.removeChild(link); } } + +function math_it_up(sign: string) { + switch (sign) { + case '=': + return (x: number, y: number) => Boolean(equal(x, y)); + case '>': + return (x: number, y: number) => Boolean(larger(x, y)); + case '<': + return (x: number, y: number) => Boolean(smaller(x, y)); + case '>=': + return (x: number, y: number) => Boolean(largerEq(x, y)); + case '<=': + return (x: number, y: number) => Boolean(smallerEq(x, y)); + default: + return (x: number, y: number) => Boolean(equal(x, y)); + } +} + +function recalculate(data: MenuEngineeringReportItem[]): MenuEngineeringReportItem[] { + const gr = data.reduce((res: Record, value: MenuEngineeringReportItem) => { + if (!res[value.saleCategory]) { + res[value.saleCategory] = { q: 0, s: 0 }; + } + res[value.saleCategory].q += value.quantity; + res[value.saleCategory].s += value.sales; + return res; + }, {}); + return data.map( + (v) => + new MenuEngineeringReportItem({ + ...v, + quantityPercent: gr[v.saleCategory]?.q ? v.quantity / gr[v.saleCategory].q : 0, + salesPercent: gr[v.saleCategory]?.s ? v.sales / gr[v.saleCategory].s : 0, + }), + ); +} diff --git a/bookie/src/app/sales/home/sales-home.component.ts b/bookie/src/app/sales/home/sales-home.component.ts index c6e555f..61cda2b 100644 --- a/bookie/src/app/sales/home/sales-home.component.ts +++ b/bookie/src/app/sales/home/sales-home.component.ts @@ -1,9 +1,9 @@ -import { Component, inject, computed, signal } from '@angular/core'; +import { Component, inject, computed, signal, input } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; import { MatRippleModule } from '@angular/material/core'; import { MatDialog } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; -import { ActivatedRoute, Router, RouterLink } from '@angular/router'; +import { Router, RouterLink } from '@angular/router'; import { Observable, of as observableOf } from 'rxjs'; import { map, switchMap, tap } from 'rxjs/operators'; @@ -31,7 +31,6 @@ import { TablesDialogComponent } from '../tables-dialog/tables-dialog.component' imports: [MatRippleModule, RouterLink, MatCardModule], }) export class SalesHomeComponent { - private route = inject(ActivatedRoute); private router = inject(Router); private dialog = inject(MatDialog); private auth = inject(AuthService); @@ -40,6 +39,7 @@ export class SalesHomeComponent { private tableService = inject(TableService); private customerDiscountsService = inject(CustomerDiscountsService); private bs = inject(BillService); + guest = input(null, { transform: (v: string | null | undefined) => v ?? null }); saleCategoriesResource = this.saleCategoryService.list(); splitBillCategories = computed(() => { @@ -64,11 +64,7 @@ export class SalesHomeComponent { if (!this.printKotAllowed()) { return; } - let guestBookId = null; - if (this.route.snapshot.queryParamMap.has('guest')) { - guestBookId = this.route.snapshot.queryParamMap.get('guest'); - } - this.bs.printKot(guestBookId).subscribe({ + this.bs.printKot(this.guest()).subscribe({ next: () => { this.snackBar.open('', 'Success'); this.router.navigate(['/sales']); @@ -149,10 +145,6 @@ export class SalesHomeComponent { if (!this.printBillAllowed()) { return; } - let guestBookId: string | null = null; - if (this.route.snapshot.queryParamMap.has('guest')) { - guestBookId = this.route.snapshot.queryParamMap.get('guest'); - } this.showDiscount() .pipe( tap((result: boolean | { id: string; name: string; discount: number }[]) => { @@ -169,7 +161,7 @@ export class SalesHomeComponent { } throw new Error('No Bill Type Chosen'); }), - switchMap((x: VoucherType) => this.bs.printBill(guestBookId, x as VoucherType)), + switchMap((x: VoucherType) => this.bs.printBill(this.guest(), x as VoucherType)), ) .subscribe({ next: () => {