import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { debounceTime, distinctUntilChanged, map, switchMap } from 'rxjs/operators'; import { Period } from 'src/app/period/period'; import { Product } from '../../core/product'; import { ProductSku } from '../../core/product-sku'; import { ToasterService } from '../../core/toaster.service'; import { ProductService } from '../../product/product.service'; import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component'; import { MathService } from '../../shared/math.service'; import { Recipe } from '../recipe'; import { RecipeItem } from '../recipe-item'; import { RecipeService } from '../recipe.service'; import { RecipeDetailDatasource } from './recipe-detail-datasource'; @Component({ selector: 'app-product-group-detail', templateUrl: './recipe-detail.component.html', styleUrls: ['./recipe-detail.component.css'], }) export class RecipeDetailComponent implements OnInit, AfterViewInit { @ViewChild('productElement', { static: true }) productElement?: ElementRef; @ViewChild('ingredientElement', { static: true }) ingredientElement?: ElementRef; public itemsObservable = new BehaviorSubject([]); dataSource: RecipeDetailDatasource = new RecipeDetailDatasource(this.itemsObservable); form: FormGroup<{ date: FormControl; source: FormControl; recipeYield: FormControl; product: FormControl; addRow: FormGroup<{ ingredient: FormControl; quantity: FormControl; description: FormControl; rate: FormControl; }>; instructions: FormControl; garnishing: FormControl; plating: FormControl; }>; periods: Period[] = []; product: ProductSku | null; products: Observable; ingredient: ProductSku | null; ingredients: Observable; item: Recipe = new Recipe(); displayedColumns = ['product', 'quantity', 'action']; constructor( private route: ActivatedRoute, private router: Router, private dialog: MatDialog, private toaster: ToasterService, private math: MathService, private ser: RecipeService, private productSer: ProductService, ) { this.product = null; this.ingredient = null; this.form = new FormGroup({ date: new FormControl(new Date(), { nonNullable: true }), source: new FormControl('', { nonNullable: true }), recipeYield: new FormControl(null), product: new FormControl(new ProductSku()), addRow: new FormGroup({ ingredient: new FormControl(''), quantity: new FormControl('', { nonNullable: true }), description: new FormControl('', { nonNullable: true }), rate: new FormControl('', { nonNullable: true }), }), instructions: new FormControl('', { nonNullable: true }), garnishing: new FormControl('', { nonNullable: true }), plating: new FormControl('', { nonNullable: true }), }); // Setup Product Autocomplete this.products = this.form.controls.product.valueChanges.pipe( map((x) => ((x as ProductSku).name !== undefined ? (x as ProductSku).name : (x as string))), map((x) => (x !== null && x.length >= 1 ? x : null)), debounceTime(150), distinctUntilChanged(), switchMap((x) => (x === null ? observableOf([]) : this.productSer.autocompleteSku(x, false))), ); // Setup Product Autocomplete this.ingredients = this.form.controls.addRow.controls.ingredient.valueChanges.pipe( map((x) => (x !== null && x.length >= 1 ? x : null)), debounceTime(150), distinctUntilChanged(), switchMap((x) => (x === null ? observableOf([]) : this.productSer.autocompleteProduct(x, null))), ); } ngOnInit() { this.route.data.subscribe((value) => { const data = value as { item: Recipe; periods: Period[] }; this.periods = data.periods; this.showItem(data.item); this.updateView(); }); } showItem(item: Recipe) { this.item = item; this.form.setValue({ date: moment(item.date, 'DD-MMM-YYYY').toDate(), source: item.source, recipeYield: `${item.recipeYield}`, product: item.sku, addRow: { ingredient: null, quantity: '', description: '', rate: '', }, instructions: item.instructions, garnishing: item.garnishing, plating: item.plating, }); this.dataSource = new RecipeDetailDatasource(this.itemsObservable); } ngAfterViewInit() { setTimeout(() => { if (this.productElement) { this.productElement.nativeElement.focus(); } }, 0); } displayIngredient(product?: Product): string { return product ? `${product.name} (${product.fractionUnits})` : ''; } displayProduct(product?: Product): string { return product?.name || ''; } productSelected(event: MatAutocompleteSelectedEvent): void { const product: ProductSku = event.option.value; this.item.sku = product; this.form.controls.product.setValue(product); } ingredientSelected(event: MatAutocompleteSelectedEvent): void { const ingredient: ProductSku = event.option.value; this.ingredient = ingredient; } addRow() { const formValue = this.form.value.addRow; if (formValue === undefined) { return; } const quantity = this.math.parseAmount(formValue.quantity, 2); const rate = this.math.parseAmount(formValue.rate, 2); if (this.ingredient === null || quantity <= 0 || rate <= 0) { return; } const oldFiltered = this.item.items.filter((x) => x.product.id === (this.ingredient as ProductSku).id); if (oldFiltered.length) { this.toaster.show('Danger', 'Product already added'); return; } this.item.items.push( new RecipeItem({ product: this.ingredient, quantity, description: formValue.description ?? '', }), ); this.resetAddRow(); this.updateView(); } resetAddRow() { this.form.controls.addRow.reset(); this.ingredient = null; setTimeout(() => { if (this.ingredientElement) { this.ingredientElement.nativeElement.focus(); } }, 0); } updateView() { this.itemsObservable.next(this.item.items); } deleteRow(row: RecipeItem) { this.item.items.splice(this.item.items.indexOf(row), 1); this.updateView(); } save() { this.ser.saveOrUpdate(this.getItem()).subscribe( () => { this.toaster.show('Success', ''); this.router.navigateByUrl('/recipes'); }, (error) => { this.toaster.show('Danger', error); }, ); } delete() { this.ser.delete(this.item.id as string).subscribe( () => { this.toaster.show('Success', ''); this.router.navigate(['/recipes']); }, (error) => { this.toaster.show('Danger', error); }, ); } confirmDelete(): void { const dialogRef = this.dialog.open(ConfirmDialogComponent, { width: '250px', data: { title: 'Delete Recipe?', content: 'Are you sure? This cannot be undone.' }, }); dialogRef.afterClosed().subscribe((result: boolean) => { if (result) { this.delete(); } }); } getItem(): Recipe { const formModel = this.form.value; this.item.date = moment(formModel.date).format('DD-MMM-YYYY'); this.item.source = formModel.source ?? ''; this.item.recipeYield = this.math.parseAmount(formModel.recipeYield ?? '1', 2); this.item.instructions = formModel.instructions ?? ''; this.item.garnishing = formModel.garnishing ?? ''; this.item.plating = formModel.plating ?? ''; return this.item; } }