import { AfterViewInit, Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { BaseComp } from '@app/shared/base/base.component'; import { ActivatedRoute } from '@angular/router'; import { createNewCustomerSetting, CustomerInvoiceSetting } from '@app/invoices/models/customer-invoice-setting.model'; import { allowedLogoFormats, globals, maxLogoSize, allowedLogoFileExt, RoleIds } from '@app/shared/global'; import { select } from '@ngrx/store'; import * as fromClients from '@app/client/reducers'; import * as SettingActions from '@app/invoices/actions/setting.actions'; import { SelectItem } from 'primeng/api'; import { InvoiceService } from '@app/domain/services/invoice.service'; import { InputNumber } from 'primeng/inputnumber'; import { GAService } from '@app/shared/ga.service'; @Component({ selector: 'agm-invoices-customer-settings', templateUrl: './customer-settings.component.html', styleUrls: ['./customer-settings.component.css'] }) export class CustomerSettingsComponent extends BaseComp implements OnInit, AfterViewInit, OnDestroy { readonly globals = globals; previewLogo; errorLogoFormat = false; errorLogoSize = false; maxLogoSize = maxLogoSize; allowedLogoFileExt = allowedLogoFileExt.join(', '); currClient; routeId; canClear = false; private _isNew: boolean; get isNew() { return this._isNew; } set isNew(isNew) { this._isNew = isNew; } private _setting: CustomerInvoiceSetting; get setting(): CustomerInvoiceSetting { return this._setting; } set setting(setting: CustomerInvoiceSetting) { this._setting = setting; } paymentTermOpts; private _paymentTerm; get paymentTerm() { return this._paymentTerm; } set paymentTerm(paymentTerm) { this._paymentTerm = paymentTerm; } addingNewTerm = false; customTerm; @ViewChild('name') name: ElementRef; @ViewChild('term') termEl: InputNumber; @ViewChild('taxValue') taxValue: InputNumber; @ViewChild('discount') discount: InputNumber; formData: FormData = new FormData(); get requiredFieldTrimmed() { return this.invoiceSvc.requiredSettingFieldTrimmed(this.setting); } constructor( private readonly route: ActivatedRoute, private readonly invoiceSvc: InvoiceService ) { super(); } ngOnInit(): void { this.sub$ = this.route.data.subscribe((data) => { const setting = data[0] as CustomerInvoiceSetting || null; if (setting) { this.isNew = (setting._id === '0'); this.canClear = !(setting._id === '0'); if (setting._id === '0') { setting.companyName = this.invoiceSvc.defaultSetting.companyName; setting.address = this.invoiceSvc.defaultSetting.address; setting.taxValue = this.invoiceSvc.defaultSetting.taxValue; setting.discount = this.invoiceSvc.defaultSetting.discount; setting.paymentTerm = this.invoiceSvc.defaultSetting.paymentTerm; setting.note = this.invoiceSvc.defaultSetting.note; setting.termOpts = this.invoiceSvc.defaultSetting.termOpts; } this.setting = { ...setting, }; this.paymentTermOpts = this.setting.termOpts.map(t => { label: $localize`:@@numOfDays:#day# days`.replace('#day#', t), value: t }); this.previewLogo = setting.logo; const paymentTerm = this.paymentTermOpts.find(i => i.value == setting.paymentTerm); if (paymentTerm) { this.paymentTerm = paymentTerm.value; } else { this.paymentTermOpts .push({ label: $localize`:@@numOfDays:#day# days`.replace('#day#', `${setting.paymentTerm}`), value: setting.paymentTerm }); this.paymentTermOpts = this.paymentTermOpts.sort((a, b) => a.value - b.value); this.paymentTerm = setting.paymentTerm; } } }); this.sub$.add(this.store.pipe(select(fromClients.getAllClients)).subscribe(clients => { if (clients) { this.route.params.subscribe(params => { this.routeId = params['id']; }); this.currClient = clients.find(client => client._id == this.routeId); } })); } ngAfterViewInit() { this.focusName(); } focusName() { const timer = setInterval(() => { if (this.setting) { if (this.name.nativeElement) { this.name.nativeElement.focus(); clearInterval(timer); } } else { clearInterval(timer); } }, 500); setTimeout(() => { clearInterval(timer); }, 1500); } onInputTerm(evt) { const memo = this.customTerm; if (evt.value > 9999) { this.customTerm = memo; evt.originalEvent.stopPropagation(); } else { this.customTerm = evt.value; } } addNewTerm() { this.addingNewTerm = true; const timer = setInterval(() => { if (this.termEl.input.nativeElement) { this.termEl.input.nativeElement.focus(); clearInterval(timer); } else { clearInterval(timer); } }, 300); setTimeout(() => { clearInterval(timer); }, 1500); } cancelNewTerm() { this.customTerm = null; this.addingNewTerm = false; } saveNewTerm() { const newTerm = this.customTerm; if (newTerm > 1 && !this.paymentTermOpts.map(i => i.value).includes(newTerm)) { this.paymentTermOpts.push({ label: $localize`:@@numOfDayss:#day# days`.replace('#day#', String(newTerm)), value: newTerm }); this.paymentTermOpts = this.paymentTermOpts.sort((a, b) => a.value - b.value); if (!this.isNew) { this.invoiceSvc.setTermOptions(this.setting._id, { termOpts: [...this.paymentTermOpts.map(o => o.value)] }) .subscribe(() => { }, () => { this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', globals.create).replace('#thing#', $localize`:@@Terms:Terms`)); }); } this.paymentTerm = newTerm; } this.customTerm = null; this.addingNewTerm = false; } removeTermOpt(value) { this.paymentTermOpts = this.paymentTermOpts.filter(i => i.value != value); this.paymentTerm = this.paymentTermOpts[0].value; this.invoiceSvc.setTermOptions(this.setting._id, { termOpts: [...this.paymentTermOpts.map(o => o.value)] }) .subscribe(() => { }, () => { this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', globals.delete).replace('#thing#', $localize`:@@Terms:Terms`)); }); } onFileSelected(event: any) { const file: File = event.target.files[0]; if (!file) { return; } if (!allowedLogoFormats.includes(file.type)) { this.errorLogoFormat = true; return; } if ((file.size / (1024 * 1024)) > this.maxLogoSize) { this.errorLogoSize = true; return; } this.setting.logo = file; const reader = new FileReader(); reader.onload = (e: any) => { this.previewLogo = e.target.result; this.errorLogoFormat = false; this.errorLogoSize = false; }; reader.readAsDataURL(file); } saveSetting() { const payload = { ...this.setting, companyName: this.setting?.companyName?.trim(), address: this.setting?.address?.trim(), }; payload.paymentTerm = this.paymentTerm; // Track settings changes const settingsModified = this.getModifiedSettings(payload); this.gaSvc.trackCustomerInvoiceSettingsUpdated({ client_id: this.currClient?._id || 'unknown', settings_modified: settingsModified, automation_enabled: this.hasAutomationEnabled(payload), payment_terms_changed: this.hasPaymentTermsChanged(payload), billing_preferences_updated: this.hasBillingPreferencesUpdated(payload), user_id: this.authSvc.user?._id, user_role: this.getUserRole(), platform: 'web' }); if (this.isNew) { this.store.dispatch(new SettingActions.Create(payload)); } else { this.store.dispatch(new SettingActions.Update(payload)); } this.goBack(); } goBack() { this.router.navigate(['../'], { relativeTo: this.route }); } delete() { this.confirmSvc.confirm({ message: globals.confirmDeleteThing.replace('#thing#', globals.invoiceSetting), accept: () => { this.store.dispatch(new SettingActions.Delete(this.setting)); this.setting = createNewCustomerSetting(this.currClient); this.previewLogo = ''; } }); } ngOnDestroy(): void { super.ngOnDestroy(); } private getModifiedSettings(payload: CustomerInvoiceSetting): string[] { const modified: string[] = []; // Compare with original setting or default values const original = this.isNew ? this.invoiceSvc.defaultSetting : this.setting; if (payload.companyName !== original.companyName) modified.push('company_name'); if (payload.address !== original.address) modified.push('address'); if (payload.taxValue !== original.taxValue) modified.push('tax_value'); if (payload.discount !== original.discount) modified.push('discount'); if (payload.paymentTerm !== original.paymentTerm) modified.push('payment_term'); if (payload.note !== original.note) modified.push('note'); if (payload.logo !== original.logo) modified.push('logo'); return modified; } private hasAutomationEnabled(payload: CustomerInvoiceSetting): boolean { // Check if any automation features are enabled return payload.taxValue > 0 || payload.discount > 0 || payload.paymentTerm > 0; } private hasPaymentTermsChanged(payload: CustomerInvoiceSetting): boolean { const original = this.isNew ? this.invoiceSvc.defaultSetting : this.setting; return payload.paymentTerm !== original.paymentTerm; } private hasBillingPreferencesUpdated(payload: CustomerInvoiceSetting): boolean { const original = this.isNew ? this.invoiceSvc.defaultSetting : this.setting; return payload.companyName !== original.companyName || payload.address !== original.address || payload.logo !== original.logo; } private getUserRole(): 'admin' | 'applicator' | 'office_admin' | 'client' | 'officer' | 'pilot' | 'inspector' | 'aircraft' { const roles = this.authSvc.user?.roles || []; if (roles.includes(RoleIds.ADMIN)) return 'admin'; if (roles.includes(RoleIds.APP)) return 'applicator'; if (roles.includes(RoleIds.APP_ADM)) return 'office_admin'; if (roles.includes(RoleIds.PILOT)) return 'pilot'; if (roles.includes(RoleIds.OFFICER)) return 'officer'; if (roles.includes(RoleIds.INSPECTOR)) return 'inspector'; if (roles.includes(RoleIds.DEVICE)) return 'aircraft'; return 'client'; } }