import { AsyncPipe, CurrencyPipe } from '@angular/common'; import { AfterViewInit, Component, ElementRef, HostListener, inject, OnInit, ViewChild } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatButtonModule } from '@angular/material/button'; import { MatOptionModule } from '@angular/material/core'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatDialog } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; 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 moment from 'moment'; import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators'; import { Account } from '../../core/account'; import { AccountService } from '../../core/account.service'; import { Product } from '../../core/product'; import { ProductSku } from '../../core/product-sku'; import { ProductService } from '../../product/product.service'; import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component'; import { MathService } from '../../shared/math.service'; import { RateContract } from '../rate-contract'; import { RateContractItem } from '../rate-contract-item'; import { RateContractService } from '../rate-contract.service'; import { RateContractDetailDatasource } from './rate-contract-detail-datasource'; @Component({ selector: 'app-rate-contract-detail', templateUrl: './rate-contract-detail.component.html', styleUrls: ['./rate-contract-detail.component.css'], imports: [ ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatDatepickerModule, MatAutocompleteModule, MatOptionModule, MatButtonModule, MatTableModule, MatSortModule, MatIconModule, AsyncPipe, CurrencyPipe, ], }) export class RateContractDetailComponent implements OnInit, AfterViewInit { private route = inject(ActivatedRoute); private router = inject(Router); private snackBar = inject(MatSnackBar); private dialog = inject(MatDialog); private math = inject(MathService); private ser = inject(RateContractService); private productSer = inject(ProductService); private accountSer = inject(AccountService); @ViewChild('accountElement', { static: true }) accountElement!: ElementRef; @ViewChild('productElement', { static: true }) productElement!: ElementRef; @ViewChild('dateElement', { static: true }) dateElement!: ElementRef; public itemsObservable = new BehaviorSubject([]); dataSource: RateContractDetailDatasource = new RateContractDetailDatasource(this.itemsObservable); form: FormGroup<{ date: FormControl; account: FormControl; validFrom: FormControl; validTill: FormControl; addRow: FormGroup<{ product: FormControl; price: FormControl; }>; narration: FormControl; }>; item: RateContract = new RateContract(); account: Account | null = null; product: Product | null = null; displayedColumns = ['product', 'price', 'action']; accounts: Observable; products: Observable; @HostListener('window:keydown.f2', ['$event']) focusDate(event: Event) { event.preventDefault(); this.dateElement.nativeElement.focus(); this.dateElement.nativeElement.select(); } constructor() { this.form = new FormGroup({ date: new FormControl(moment(new Date()), { nonNullable: true }), account: new FormControl(null), validFrom: new FormControl(moment(new Date()), { nonNullable: true }), validTill: new FormControl(moment(new Date()), { nonNullable: true }), addRow: new FormGroup({ product: new FormControl(''), price: new FormControl('', { nonNullable: true }), }), narration: new FormControl('', { nonNullable: true }), }); this.accounts = this.form.controls.account.valueChanges.pipe( debounceTime(150), distinctUntilChanged(), switchMap((x) => (x === null ? observableOf([]) : this.accountSer.autocomplete(x))), ); // Listen to Product Autocomplete Change this.products = this.form.controls.addRow.controls.product.valueChanges.pipe( debounceTime(150), distinctUntilChanged(), switchMap((x) => (x === null ? observableOf([]) : this.productSer.autocompleteSku(x, true))), ); } ngOnInit() { this.route.data.subscribe((value) => { const data = value as { item: RateContract }; this.loadItem(data.item); }); } loadItem(item: RateContract) { this.item = item; this.form.setValue({ date: moment(this.item.date, 'DD-MMM-YYYY'), validFrom: moment(this.item.validFrom, 'DD-MMM-YYYY'), validTill: moment(this.item.validTill, 'DD-MMM-YYYY'), account: this.item.vendor?.name ?? '', addRow: { product: '', price: '', }, narration: this.item.narration, }); this.account = this.item.vendor || null; this.dataSource = new RateContractDetailDatasource(this.itemsObservable); this.updateView(); } ngAfterViewInit() { setTimeout(() => { this.accountElement.nativeElement.focus(); }, 0); } addRow() { const formValue = this.form.value.addRow; if (formValue === undefined) { return; } const price = this.math.parseAmount(formValue.price, 2); if (this.product === null || price <= 0) { return; } const oldFiltered = this.item.items.filter((x) => x.sku.id === (this.product as Product).id); if (oldFiltered.length) { this.snackBar.open('Product already added', 'Danger'); return; } this.item.items.push( new RateContractItem({ price, sku: this.product, }), ); this.resetAddRow(); this.updateView(); } resetAddRow() { this.form.controls.addRow.reset(); this.product = null; setTimeout(() => { this.productElement.nativeElement.focus(); }, 0); } displayFn(item?: Account | Product | string): string { return !item ? '' : typeof item === 'string' ? item : item.name; } updateView() { this.itemsObservable.next(this.item.items); } accountSelected(event: MatAutocompleteSelectedEvent): void { this.account = event.option.value; } productSelected(event: MatAutocompleteSelectedEvent): void { this.product = event.option.value; } deleteRow(row: RateContractItem) { this.item.items.splice(this.item.items.indexOf(row), 1); this.updateView(); } save() { this.ser.saveOrUpdate(this.getItem()).subscribe({ next: () => { this.snackBar.open('', 'Success'); this.router.navigateByUrl('/rate-contracts'); }, error: (error) => { this.snackBar.open(error, 'Danger'); }, }); } delete() { this.ser.delete(this.item.id as string).subscribe({ next: () => { this.snackBar.open('', 'Success'); this.router.navigateByUrl('/rate-contracts'); }, error: (error) => { this.snackBar.open(error, 'Danger'); }, }); } confirmDelete(): void { const dialogRef = this.dialog.open(ConfirmDialogComponent, { width: '250px', data: { title: 'Delete RateContract?', content: 'Are you sure? This cannot be undone.' }, }); dialogRef.afterClosed().subscribe((result: boolean) => { if (result) { this.delete(); } }); } getItem(): RateContract { const formModel = this.form.value; this.item.date = moment(formModel.date).format('DD-MMM-YYYY'); this.item.validFrom = moment(formModel.validFrom).format('DD-MMM-YYYY'); this.item.validTill = moment(formModel.validTill).format('DD-MMM-YYYY'); this.item.vendor = this.account ?? new Account(); this.item.narration = formModel.narration ?? ''; return this.item; } }