Removed Recipe Template as it was not needed.

Moved host listeners to the component declaration.
Form submit on Return fixed.
This commit is contained in:
2026-08-26 06:00:13 +00:00
parent fe5e4a2308
commit e3719faddd
79 changed files with 737 additions and 990 deletions
@@ -7,7 +7,7 @@
</mat-form-field>
<mat-form-field class="flex-auto basis-30">
<mat-label>Product Group</mat-label>
<mat-select [formField]="form.productGroup" (selectionChange)="filterOn($event.value)">
<mat-select [formField]="form.productGroup">
<mat-option>-- All Products --</mat-option>
@for (pg of productGroups(); track pg) {
<mat-option [value]="pg.id">
@@ -18,10 +18,10 @@
</mat-form-field>
</div>
</form>
@if (resource.isLoading()) {
@if (listResource.isLoading()) {
<app-skeleton-loader type="table" [count]="1" />
} @else if (resource.error()) {
<app-error-state [message]="'Failed to load data.'" (retryAction)="resource.reload()" />
} @else if (listResource.error()) {
<app-error-state [message]="'Failed to load data.'" (retryAction)="listResource.reload()" />
} @else {
<mat-table [dataSource]="dataSource()">
<!-- Name Column -->
@@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
import { Component, inject, computed, linkedSignal } from '@angular/core';
import { Component, inject, computed, linkedSignal, input, debounced, effect } from '@angular/core';
import { FormField, FormRoot, form as createForm } from '@angular/forms/signals';
import { MatOptionModule } from '@angular/material/core';
import { MatFormFieldModule } from '@angular/material/form-field';
@@ -7,12 +7,13 @@ import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatTableModule } from '@angular/material/table';
import { RouterLink } from '@angular/router';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { Product, StockKeepingUnit } from '../../core/product';
import { ProductGroupService } from '../../product-group/product-group.service';
import { ErrorStateComponent } from '../../shared/error-state/error-state.component';
import { SkeletonLoaderComponent } from '../../shared/skeleton-loader/skeleton-loader.component';
import { TemporalProduct } from '../temporal-product';
import { TemporalProductService } from '../temporal-product.service';
export interface TemporalProductListFormData {
@@ -42,48 +43,84 @@ export interface TemporalProductListFormData {
],
})
export class TemporalProductListComponent {
private router = inject(Router);
private route = inject(ActivatedRoute);
private ser = inject(TemporalProductService);
private productGroupSer = inject(ProductGroupService);
q = input('', { transform: (v: string | null | undefined) => v ?? '' });
g = input('', { transform: (v: string | null | undefined) => v ?? '' });
productGroupsResource = this.productGroupSer.list();
productGroups = computed(() => this.productGroupsResource.value() ?? []);
listResource = this.ser.list();
list = computed(() => this.listResource.value() ?? []);
formModel = linkedSignal({
source: () => null,
computation: (): TemporalProductListFormData => ({
filter: '',
productGroup: '',
source: () => ({ q: this.q(), g: this.g() }),
computation: (v: { q: string; g: string }): TemporalProductListFormData => ({
filter: v.q,
productGroup: v.g,
}),
});
form = createForm(this.formModel);
filterSignal = computed(() => this.formModel().filter);
debouncedFilter = debounced(this.filterSignal, 150);
filteredList = computed(() => {
const data = this.list();
const search = this.debouncedFilter.value() ?? '';
const productGroup = this.formModel().productGroup;
const groupId =
typeof productGroup === 'string'
? productGroup
: productGroup && 'id' in productGroup
? (productGroup.id ?? '')
: '';
productGroupsResource = this.productGroupSer.list();
productGroups = computed(() => this.productGroupsResource.value() ?? []);
const tokens = search.trim().toLowerCase().split(/\s+/).filter(Boolean);
resource = this.ser.list();
return data.filter((tp: TemporalProduct) => {
const products = tp.products ?? [];
const skus = tp.skus ?? [];
dataSource = computed(() => {
let data = this.resource.value() ?? [];
const filterValue = (this.formModel().filter ?? '').trim().toLowerCase();
const groupValue = this.formModel().productGroup;
const matchesSearch =
tokens.length === 0 ||
tokens.every(
(token) =>
products.some((p) => {
const hay = `${p.name ?? ''} ${p.fractionUnits ?? ''} ${p.productGroup?.name ?? ''}`.toLowerCase();
return hay.includes(token);
}) ||
skus.some((k) => {
const hay = `${k.units ?? ''}`.toLowerCase();
return hay.includes(token);
}),
);
if (filterValue || groupValue) {
data = data.filter((d) => {
let match = true;
if (filterValue) {
match = match && d.products[0].name.toLowerCase().includes(filterValue);
}
if (groupValue) {
match = match && d.products[0].productGroup?.id === groupValue;
}
return match;
});
}
return data;
const matchesProductGroup = !groupId || products.some((k) => (k.productGroup?.id ?? '') === groupId);
return matchesSearch && matchesProductGroup;
});
});
dataSource = computed(() => this.filteredList());
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
displayedColumns: string[] = ['product', 'sku', 'info'];
filterOn(val: string) {
this.formModel.update((m) => ({ ...m, productGroup: val }));
constructor() {
effect(() => {
const q = this.debouncedFilter.value() ?? '';
const g = this.formModel().productGroup;
this.router.navigate([], {
relativeTo: this.route,
queryParams: {
q: q || null,
g: g || null,
},
queryParamsHandling: 'merge',
replaceUrl: true,
});
});
}
}