feedback updates

This commit is contained in:
Cyril Joseph 2025-08-02 22:32:20 -03:00
parent d7318a0316
commit 3d07921cec
24 changed files with 259 additions and 174 deletions

View File

@ -41,8 +41,7 @@
<mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail"> <mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Shipping & Payment</ng-template> <ng-template matStepLabel>Shipping & Payment</ng-template>
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid" <app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid"
[applicationName]="applicationName" [enableSubmitButton]="allSectionsCompleted" [applicationName]="applicationName" [enableSubmitButton]="allSectionsCompleted">
[refreshShippingData]="refreshShippingData">
</app-shipping> </app-shipping>
</mat-step> </mat-step>

View File

@ -1,4 +1,4 @@
import { Component, inject } from '@angular/core'; import { Component, inject, ViewChild } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { AngularMaterialModule } from '../../shared/module/angular-material.module'; import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
@ -27,7 +27,6 @@ export class AddCarnetComponent {
headerid: number = 0; headerid: number = 0;
applicationName: string = ''; applicationName: string = '';
userPreferences: UserPreferences; userPreferences: UserPreferences;
refreshShippingData: boolean = false;
allSectionsCompleted: boolean = false; allSectionsCompleted: boolean = false;
// Track completion of each step // Track completion of each step
@ -39,6 +38,9 @@ export class AddCarnetComponent {
shipping: false shipping: false
}; };
@ViewChild(ShippingComponent, { static: false })
private shippingComponent!: ShippingComponent;
constructor(userPrefenceService: UserPreferencesService) { constructor(userPrefenceService: UserPreferencesService) {
this.userPreferences = userPrefenceService.getPreferences(); this.userPreferences = userPrefenceService.getPreferences();
} }
@ -60,7 +62,9 @@ export class AddCarnetComponent {
} }
onHolderSelectionUpdated(updated: boolean): void { onHolderSelectionUpdated(updated: boolean): void {
this.refreshShippingData = updated; if (updated) {
this.shippingComponent?.refreshShippingData();
}
} }
onGoodsSectionSaved(completed: boolean): void { onGoodsSectionSaved(completed: boolean): void {

View File

@ -41,8 +41,7 @@
<mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail"> <mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Shipping & Payment</ng-template> <ng-template matStepLabel>Shipping & Payment</ng-template>
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid" [isEditMode]="isEditMode" <app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid" [isEditMode]="isEditMode"
[applicationName]="applicationName" [enableSubmitButton]="allSectionsCompleted" [applicationName]="applicationName" [enableSubmitButton]="allSectionsCompleted">
[refreshShippingData]="refreshShippingData">
</app-shipping> </app-shipping>
</mat-step> </mat-step>

View File

@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, inject } from '@angular/core'; import { Component, inject, ViewChild } from '@angular/core';
import { AngularMaterialModule } from '../../shared/module/angular-material.module'; import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { ApplicationComponent } from '../application/application.component'; import { ApplicationComponent } from '../application/application.component';
@ -27,9 +27,11 @@ export class EditCarnetComponent {
headerid: number = 0; headerid: number = 0;
userPreferences: UserPreferences; userPreferences: UserPreferences;
applicationName: string = ''; applicationName: string = '';
refreshShippingData: boolean = false;
allSectionsCompleted: boolean = false; allSectionsCompleted: boolean = false;
@ViewChild(ShippingComponent, { static: false })
private shippingComponent!: ShippingComponent;
// Track completion of each step // Track completion of each step
stepsCompleted = { stepsCompleted = {
applicationDetail: true, applicationDetail: true,
@ -75,7 +77,10 @@ export class EditCarnetComponent {
} }
onHolderSelectionUpdated(updated: boolean): void { onHolderSelectionUpdated(updated: boolean): void {
this.refreshShippingData = updated; if (updated) {
this.shippingComponent?.refreshShippingData();
}
this.isAllSectionsCompleted(); this.isAllSectionsCompleted();
} }

View File

@ -219,7 +219,8 @@
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button mat-raised-button color="primary" type="submit" [disabled]="itemForm.invalid"> <button mat-raised-button color="primary" type="submit"
[disabled]="itemForm.invalid || changeInProgress">
Save Save
</button> </button>
<button mat-button type="button" (click)="cancelEdit()">Cancel</button> <button mat-button type="button" (click)="cancelEdit()">Cancel</button>
@ -228,7 +229,8 @@
</div> </div>
<div *ngIf="!showItemForm" class="form-actions"> <div *ngIf="!showItemForm" class="form-actions">
<button mat-raised-button color="primary" (click)="onSubmit()" [disabled]="goodsForm.invalid"> <button mat-raised-button color="primary" (click)="onSubmit()"
[disabled]="goodsForm.invalid || changeInProgress">
Save Save
</button> </button>
</div> </div>

View File

@ -16,7 +16,7 @@ import { ApiErrorHandlerService } from '../../core/services/common/api-error-han
import { Country } from '../../core/models/country'; import { Country } from '../../core/models/country';
import { UnitOfMeasure } from '../../core/models/unitofmeasure'; import { UnitOfMeasure } from '../../core/models/unitofmeasure';
import { CommonService } from '../../core/services/common/common.service'; import { CommonService } from '../../core/services/common/common.service';
import { Subject, takeUntil } from 'rxjs'; import { finalize, Subject, takeUntil } from 'rxjs';
import { Goods, GoodsItem } from '../../core/models/carnet/goods'; import { Goods, GoodsItem } from '../../core/models/carnet/goods';
@Component({ @Component({
@ -47,6 +47,7 @@ export class GoodsComponent {
isEditing = false; isEditing = false;
currentItem: GoodsItem | null = null; currentItem: GoodsItem | null = null;
isLoading = false; isLoading = false;
changeInProgress = false;
showItemForm = false; showItemForm = false;
fileToUpload: File | null = null; fileToUpload: File | null = null;
isProcessing = false; isProcessing = false;
@ -84,7 +85,7 @@ export class GoodsComponent {
weight: [0, [Validators.required, Validators.min(1), Validators.pattern(/^\d+(\.\d{1,4})?$/)]], weight: [0, [Validators.required, Validators.min(1), Validators.pattern(/^\d+(\.\d{1,4})?$/)]],
unitOfMeasure: ['', Validators.required], unitOfMeasure: ['', Validators.required],
value: [0, [Validators.required, Validators.min(1), Validators.pattern(/^\d+(\.\d{1,2})?$/)]], value: [0, [Validators.required, Validators.min(1), Validators.pattern(/^\d+(\.\d{1,2})?$/)]],
countryOfOrigin: ['', Validators.required] countryOfOrigin: ['US', Validators.required]
}); });
} }
@ -98,18 +99,19 @@ export class GoodsComponent {
this.loadUnitOfMeasures(); this.loadUnitOfMeasures();
if (this.headerid > 0) { if (this.headerid > 0) {
this.goodsService.getGoodDetailsByHeaderId(this.headerid).subscribe({ this.isLoading = true;
this.goodsService.getGoodDetailsByHeaderId(this.headerid).pipe(finalize(() => {
this.isLoading = false;
})).subscribe({
next: (goodsDetail: Goods) => { next: (goodsDetail: Goods) => {
if (goodsDetail) { if (goodsDetail) {
this.patchFormData(goodsDetail); this.patchFormData(goodsDetail);
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0); this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
} }
this.isLoading = false;
}, },
error: (error: any) => { error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load good details'); let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load good details');
this.notificationService.showError(errorMessage); this.notificationService.showError(errorMessage);
this.isLoading = false;
console.error('Error loading good details:', error); console.error('Error loading good details:', error);
} }
}); });
@ -121,23 +123,23 @@ export class GoodsComponent {
loadGoodsItems(): void { loadGoodsItems(): void {
this.isLoading = true; this.isLoading = true;
this.goodsService.getGoodsItemsByHeaderId(this.headerid).subscribe({ this.goodsService.getGoodsItemsByHeaderId(this.headerid).pipe(finalize(() => {
this.isLoading = false;
})).subscribe({
next: (items: GoodsItem[]) => { next: (items: GoodsItem[]) => {
this.dataSource.data = items; this.dataSource.data = items;
this.isLoading = false;
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0); this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
}, },
error: (error: any) => { error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load goods items'); let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load goods items');
this.notificationService.showError(errorMessage); this.notificationService.showError(errorMessage);
this.isLoading = false;
console.error('Error loading goods items:', error); console.error('Error loading goods items:', error);
} }
}); });
} }
loadCountries(): void { loadCountries(): void {
this.commonService.getCountries(0) this.commonService.getCountries()
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe({ .subscribe({
next: (countries) => { next: (countries) => {
@ -145,7 +147,6 @@ export class GoodsComponent {
}, },
error: (error) => { error: (error) => {
console.error('Failed to load countries', error); console.error('Failed to load countries', error);
this.isLoading = false;
} }
}); });
} }
@ -156,11 +157,9 @@ export class GoodsComponent {
.subscribe({ .subscribe({
next: (units) => { next: (units) => {
this.unitsOfMeasures = units; this.unitsOfMeasures = units;
this.isLoading = false;
}, },
error: (error) => { error: (error) => {
console.error('Failed to load unit of measures', error); console.error('Failed to load unit of measures', error);
this.isLoading = false;
} }
}); });
} }
@ -184,7 +183,8 @@ export class GoodsComponent {
this.itemForm.reset({ this.itemForm.reset({
pieces: 0, pieces: 0,
weight: 0, weight: 0,
value: 0 value: 0,
countryOfOrigin: 'US',
}); });
this.itemForm.markAsUntouched(); this.itemForm.markAsUntouched();
} }
@ -220,11 +220,15 @@ export class GoodsComponent {
let itemDataArray: GoodsItem[] = []; let itemDataArray: GoodsItem[] = [];
itemDataArray.push(itemData); itemDataArray.push(itemData);
this.changeInProgress = true;
const saveObservable = this.isEditing && this.currentItem const saveObservable = this.isEditing && this.currentItem
? this.goodsService.updateGoodsItem(this.headerid, itemDataArray) ? this.goodsService.updateGoodsItem(this.headerid, itemDataArray)
: this.goodsService.addGoodsItem(this.headerid, itemDataArray); : this.goodsService.addGoodsItem(this.headerid, itemDataArray);
saveObservable.subscribe({ saveObservable.pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: () => { next: () => {
this.notificationService.showSuccess(`Goods ${this.isEditing ? 'updated' : 'added'} successfully`); this.notificationService.showSuccess(`Goods ${this.isEditing ? 'updated' : 'added'} successfully`);
this.loadGoodsItems(); this.loadGoodsItems();
@ -275,10 +279,10 @@ export class GoodsComponent {
const file: File = event.target.files[0]; const file: File = event.target.files[0];
this.processExcelFile(file) this.processExcelFile(file)
.then(response => { // .then(response => {
console.log('File uploaded successfully:', response); // // console.log('File uploaded successfully:', response);
// Handle success (e.g., update UI) // // Handle success (e.g., update UI)
}) // })
.catch(error => { .catch(error => {
console.error('Error processing file:', error); console.error('Error processing file:', error);
this.notificationService.showError('Failed to upload file'); this.notificationService.showError('Failed to upload file');
@ -319,7 +323,10 @@ export class GoodsComponent {
// item.unitOfMeasure = this.getUnitOfMeasureValue(item.unitOfMeasure); // item.unitOfMeasure = this.getUnitOfMeasureValue(item.unitOfMeasure);
// }); // });
this.goodsService.addGoodsItem(this.headerid, items).subscribe({ this.changeInProgress = true;
this.goodsService.addGoodsItem(this.headerid, items).pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: () => { next: () => {
this.notificationService.showSuccess(`Goods uploaded successfully`); this.notificationService.showSuccess(`Goods uploaded successfully`);
this.loadGoodsItems(); this.loadGoodsItems();
@ -390,9 +397,11 @@ export class GoodsComponent {
} }
const formData = this.goodsForm.value; const formData = this.goodsForm.value;
this.isLoading = true; this.changeInProgress = true;
this.goodsService.saveGoodsData(this.headerid, formData).subscribe({ this.goodsService.saveGoodsData(this.headerid, formData).pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: () => { next: () => {
this.notificationService.showSuccess('Goods information saved successfully'); this.notificationService.showSuccess('Goods information saved successfully');
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0); this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
@ -401,9 +410,6 @@ export class GoodsComponent {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to save goods information'); let errorMessage = this.errorHandler.handleApiError(error, 'Failed to save goods information');
this.notificationService.showError(errorMessage); this.notificationService.showError(errorMessage);
console.error('Error saving goods:', error); console.error('Error saving goods:', error);
},
complete: () => {
this.isLoading = false;
} }
}); });
} }

View File

@ -1,4 +1,8 @@
<div class="shipping-container"> <div class="shipping-container">
<div class="loading-shade" *ngIf="isLoading">
<mat-spinner diameter="50"></mat-spinner>
</div>
<form [formGroup]="shippingForm" (ngSubmit)="onSubmit()"> <form [formGroup]="shippingForm" (ngSubmit)="onSubmit()">
<!-- Insurance Section --> <!-- Insurance Section -->
<div class="section"> <div class="section">
@ -344,9 +348,9 @@
<span>Submit Application</span> <span>Submit Application</span>
</button> </button>
<button mat-raised-button color="primary" type="submit" [disabled]="shippingForm.invalid || isLoading"> <button mat-raised-button color="primary" type="submit"
<span *ngIf="!isLoading">Save</span> [disabled]="shippingForm.invalid || changeInProgress">
<mat-spinner *ngIf="isLoading" diameter="24"></mat-spinner> Save
</button> </button>
</div> </div>
</form> </form>

View File

@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, EventEmitter, inject, Input, Output, SimpleChanges } from '@angular/core'; import { Component, EventEmitter, inject, Input, Output } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NotificationService } from '../../core/services/common/notification.service'; import { NotificationService } from '../../core/services/common/notification.service';
import { AngularMaterialModule } from '../../shared/module/angular-material.module'; import { AngularMaterialModule } from '../../shared/module/angular-material.module';
@ -34,7 +34,6 @@ import { FormOfSecurity } from '../../core/models/formofsecurity';
export class ShippingComponent { export class ShippingComponent {
@Input() headerid: number = 0; @Input() headerid: number = 0;
@Input() isEditMode = false; @Input() isEditMode = false;
@Input() refreshShippingData = false;
@Input() enableSubmitButton = false; @Input() enableSubmitButton = false;
@Input() applicationName: string = ''; @Input() applicationName: string = '';
@ -53,6 +52,7 @@ export class ShippingComponent {
shippingForm: FormGroup; shippingForm: FormGroup;
isLoading = false; isLoading = false;
changeInProgress = false;
showAddressForm = false; showAddressForm = false;
showContactForm = false; showContactForm = false;
deliveryEstimate: string = ''; deliveryEstimate: string = '';
@ -91,7 +91,7 @@ export class ShippingComponent {
address2: [''], address2: [''],
city: [''], city: [''],
state: [''], state: [''],
country: [''], country: ['US'],
zip: [''], zip: [''],
}), }),
contact: this.fb.group({ contact: this.fb.group({
@ -142,7 +142,7 @@ export class ShippingComponent {
return; return;
} }
this.isLoading = true; this.changeInProgress = true;
const shippingData: Shipping = this.shippingForm.value; const shippingData: Shipping = this.shippingForm.value;
if (shippingData.shipTo !== '3RDPARTY') { if (shippingData.shipTo !== '3RDPARTY') {
@ -150,16 +150,16 @@ export class ShippingComponent {
shippingData.contact = shippingData.shipTo === 'PREPARER' ? this.preparerContact ?? undefined : this.holderContact ?? undefined; shippingData.contact = shippingData.shipTo === 'PREPARER' ? this.preparerContact ?? undefined : this.holderContact ?? undefined;
} }
this.shippingService.saveShippingDetails(this.headerid, shippingData).subscribe({ this.shippingService.saveShippingDetails(this.headerid, shippingData).pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: () => { next: () => {
this.notificationService.showSuccess('Shipping & payments information saved successfully'); this.notificationService.showSuccess('Shipping & payments information saved successfully');
this.completed.emit(true); this.completed.emit(true);
this.isLoading = false;
}, },
error: (error) => { error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to save shipping and payment information'); let errorMessage = this.errorHandler.handleApiError(error, 'Failed to save shipping and payment information');
this.notificationService.showError(errorMessage); this.notificationService.showError(errorMessage);
this.isLoading = false;
} }
}); });
} }
@ -188,8 +188,8 @@ export class ShippingComponent {
this.shippingForm.get('address.zip')?.updateValueAndValidity(); this.shippingForm.get('address.zip')?.updateValueAndValidity();
} }
ngOnChanges(changes: SimpleChanges) { public refreshShippingData(): void {
if (changes['refreshShippingData'] && this.headerid > 0) { if (this.headerid > 0) {
this.loadShippingData(); this.loadShippingData();
} }
} }
@ -209,7 +209,8 @@ export class ShippingComponent {
cancelEditAddressForm(): void { cancelEditAddressForm(): void {
this.showAddressForm = false; this.showAddressForm = false;
this.shippingForm.get('address')?.reset(); this.shippingForm.get('address')?.reset({ country: 'US' });
this.loadStates('US');
} }
onShipToChange(event: any): void { onShipToChange(event: any): void {
@ -221,14 +222,15 @@ export class ShippingComponent {
if (shipTo === '3RDPARTY') { if (shipTo === '3RDPARTY') {
this.shippingForm.get('contact')?.reset(); this.shippingForm.get('contact')?.reset();
this.shippingForm.get('address')?.reset(); this.shippingForm.get('address')?.reset({ country: 'US' });
this.loadStates('US');
this.showAddressForm = true; this.showAddressForm = true;
this.showContactForm = true; this.showContactForm = true;
} }
} }
loadCountries(): void { loadCountries(): void {
this.commonService.getCountries(0) this.commonService.getCountries()
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe({ .subscribe({
next: (countries) => { next: (countries) => {
@ -236,13 +238,12 @@ export class ShippingComponent {
}, },
error: (error) => { error: (error) => {
console.error('Failed to load countries', error); console.error('Failed to load countries', error);
this.isLoading = false;
} }
}); });
} }
loadDeliveryTypes(): void { loadDeliveryTypes(): void {
this.commonService.getDeliveryTypes(0) this.commonService.getDeliveryTypes()
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe({ .subscribe({
next: (deliveryTypes) => { next: (deliveryTypes) => {
@ -250,13 +251,12 @@ export class ShippingComponent {
}, },
error: (error) => { error: (error) => {
console.error('Failed to load delivery types', error); console.error('Failed to load delivery types', error);
this.isLoading = false;
} }
}); });
} }
loadDeliveryMethods(): void { loadDeliveryMethods(): void {
this.commonService.getDeliveryMethods(0) this.commonService.getDeliveryMethods()
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe({ .subscribe({
next: (deliveryMethods) => { next: (deliveryMethods) => {
@ -264,13 +264,12 @@ export class ShippingComponent {
}, },
error: (error) => { error: (error) => {
console.error('Failed to load delivery methods', error); console.error('Failed to load delivery methods', error);
this.isLoading = false;
} }
}); });
} }
loadPaymentTypes(): void { loadPaymentTypes(): void {
this.commonService.getPaymentTypes(0) this.commonService.getPaymentTypes()
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe({ .subscribe({
next: (paymentTypes) => { next: (paymentTypes) => {
@ -278,7 +277,6 @@ export class ShippingComponent {
}, },
error: (error) => { error: (error) => {
console.error('Failed to load payment types', error); console.error('Failed to load payment types', error);
this.isLoading = false;
} }
}); });
} }
@ -286,29 +284,29 @@ export class ShippingComponent {
loadStates(country: string): void { loadStates(country: string): void {
this.isLoading = true; this.isLoading = true;
country = this.countriesHasStates.includes(country) ? country : 'FN'; country = this.countriesHasStates.includes(country) ? country : 'FN';
this.commonService.getStates(country, 0) this.commonService.getStates(country)
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$),
.subscribe({ finalize(() => {
next: (states) => { this.isLoading = false;
this.states = states; })).subscribe({
const stateControl = this.shippingForm.get('contact.state'); next: (states) => {
if (this.countriesHasStates.includes(country)) { this.states = states;
stateControl?.enable(); const stateControl = this.shippingForm.get('contact.state');
} else { if (this.countriesHasStates.includes(country)) {
stateControl?.disable(); stateControl?.enable();
stateControl?.setValue('FN'); } else {
stateControl?.disable();
stateControl?.setValue('FN');
}
},
error: (error) => {
console.error('Failed to load states', error);
} }
this.isLoading = false; });
},
error: (error) => {
console.error('Failed to load states', error);
this.isLoading = false;
}
});
} }
loadFormOfSecurities(): void { loadFormOfSecurities(): void {
this.commonService.getFormOfSecurities(0) this.commonService.getFormOfSecurities()
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe({ .subscribe({
next: (fos) => { next: (fos) => {
@ -483,11 +481,11 @@ export class ShippingComponent {
} else if (key === 'title') { } else if (key === 'title') {
contactGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(100)]); contactGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(100)]);
} else if (key === 'phone') { } else if (key === 'phone') {
contactGroup.get(key)?.setValidators([Validators.required, Validators.pattern(/^[0-9]{10,15}$/)]); contactGroup.get(key)?.setValidators([Validators.required, Validators.pattern(/^[0-9\-\(\)]{10,15}$/)]);
} else if (key === 'mobile') { } else if (key === 'mobile') {
contactGroup.get(key)?.setValidators([Validators.required, Validators.pattern(/^[0-9]{10,15}$/)]); contactGroup.get(key)?.setValidators([Validators.required, Validators.pattern(/^[0-9\-\(\)]{10,15}$/)]);
} else if (key === 'fax') { } else if (key === 'fax') {
contactGroup.get(key)?.setValidators([Validators.pattern(/^[0-9]{10,15}$/)]); contactGroup.get(key)?.setValidators([Validators.pattern(/^[0-9\-\(\)]{10,15}$/)]);
} else if (key === 'email') { } else if (key === 'email') {
contactGroup.get(key)?.setValidators([Validators.required, Validators.email, Validators.maxLength(100)]); contactGroup.get(key)?.setValidators([Validators.required, Validators.email, Validators.maxLength(100)]);
} }

View File

@ -102,7 +102,8 @@
<mat-divider></mat-divider> <mat-divider></mat-divider>
<mat-card-actions align="end"> <mat-card-actions align="end">
<button mat-raised-button color="primary" (click)="onAccept()" [disabled]="!hasReadAllTerms"> <button mat-raised-button color="primary" (click)="onAccept()"
[disabled]="!hasReadAllTerms || changeInProgress">
Accept Accept
</button> </button>
<button mat-button (click)="onDecline()">Decline</button> <button mat-button (click)="onDecline()">Decline</button>

View File

@ -12,6 +12,7 @@ import { NotificationService } from '../../core/services/common/notification.ser
import { StorageService } from '../../core/services/common/storage.service'; import { StorageService } from '../../core/services/common/storage.service';
import { CarnetService } from '../../core/services/carnet/carnet.service'; import { CarnetService } from '../../core/services/carnet/carnet.service';
import { Fees } from '../../core/models/carnet/fee'; import { Fees } from '../../core/models/carnet/fee';
import { finalize } from 'rxjs';
@Component({ @Component({
selector: 'app-terms-conditions', selector: 'app-terms-conditions',
@ -29,7 +30,7 @@ export class TermsConditionsComponent {
currentDate = new Date(); currentDate = new Date();
hasReadAllTerms = false; hasReadAllTerms = false;
currentApplicationDetails: { headerid: number, applicationName: string } | null = null; currentApplicationDetails: { headerid: number, applicationName: string } | null = null;
isLoading: boolean = false; changeInProgress: boolean = false;
estimatedFees: Fees = {}; estimatedFees: Fees = {};
private notificationService = inject(NotificationService); private notificationService = inject(NotificationService);
@ -61,19 +62,19 @@ export class TermsConditionsComponent {
onAccept(): void { onAccept(): void {
this.isLoading = true; this.changeInProgress = true;
this.carnetService.submitApplication(this.currentApplicationDetails?.headerid!).subscribe({ this.carnetService.submitApplication(this.currentApplicationDetails?.headerid!).pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: () => { next: () => {
this.notificationService.showSuccess('Application submitted successfully'); this.notificationService.showSuccess('Application submitted successfully');
this.storageService.removeItem('currentapplication'); this.storageService.removeItem('currentapplication');
this.navigationService.navigate(["home"]); this.navigationService.navigate(["home"]);
this.isLoading = false;
}, },
error: (error) => { error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to submit application'); let errorMessage = this.errorHandler.handleApiError(error, 'Failed to submit application');
this.notificationService.showError(errorMessage); this.notificationService.showError(errorMessage);
console.error('Error submitting the application', error); console.error('Error submitting the application', error);
this.isLoading = false;
} }
}); });
} }

View File

@ -1,4 +1,7 @@
<div class="travel-plan-container"> <div class="travel-plan-container">
<div class="loading-shade" *ngIf="isLoading">
<mat-spinner diameter="50"></mat-spinner>
</div>
<form [formGroup]="travelForm" (ngSubmit)="onSubmit()"> <form [formGroup]="travelForm" (ngSubmit)="onSubmit()">
<!-- USA Entries --> <!-- USA Entries -->
<div class="form-row"> <div class="form-row">
@ -139,9 +142,8 @@
</div> </div>
</div> </div>
<button mat-raised-button color="primary" type="submit" [disabled]="isLoading"> <button mat-raised-button color="primary" type="submit" [disabled]="changeInProgress">
<span *ngIf="!isLoading">Save</span> Save
<mat-spinner *ngIf="isLoading" diameter="24"></mat-spinner>
</button> </button>
</div> </div>

View File

@ -1,6 +1,6 @@
import { Component, EventEmitter, inject, Input, Output, SimpleChanges } from '@angular/core'; import { Component, EventEmitter, inject, Input, Output, SimpleChanges } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { forkJoin } from 'rxjs'; import { finalize, forkJoin } from 'rxjs';
import { TravelPlanService } from '../../core/services/carnet/travel-plan.service'; import { TravelPlanService } from '../../core/services/carnet/travel-plan.service';
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service'; import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
import { NotificationService } from '../../core/services/common/notification.service'; import { NotificationService } from '../../core/services/common/notification.service';
@ -30,6 +30,7 @@ export class TravelPlanComponent {
travelForm: FormGroup; travelForm: FormGroup;
isLoading = false; isLoading = false;
changeInProgress = false;
visitsCount = 1; visitsCount = 1;
transitsCount = 1; transitsCount = 1;
@ -253,17 +254,17 @@ export class TravelPlanComponent {
forkJoin({ forkJoin({
countries: this.travelPlanService.getCountriesAndMessages(), countries: this.travelPlanService.getCountriesAndMessages(),
travelPlanData: this.travelPlanService.getTravelPlan(this.headerid) travelPlanData: this.travelPlanService.getTravelPlan(this.headerid)
}).subscribe({ }).pipe(finalize(() => {
this.isLoading = false;
})).subscribe({
next: (results) => { next: (results) => {
this.countries = results.countries as Country[]; this.countries = results.countries as Country[];
this.patchTravelPlanData(results.travelPlanData); this.patchTravelPlanData(results.travelPlanData);
this.isLoading = false;
}, },
error: (error) => { error: (error) => {
console.error('Error loading travel plan data', error); console.error('Error loading travel plan data', error);
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load travel plan data'); let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load travel plan data');
this.notificationService.showError(errorMessage); this.notificationService.showError(errorMessage);
this.isLoading = false;
} }
}); });
} }
@ -295,18 +296,19 @@ export class TravelPlanComponent {
transits: this.selectedTransitCountries transits: this.selectedTransitCountries
}; };
this.isLoading = true; this.changeInProgress = true;
this.travelPlanService.saveTravelPlan(this.headerid, travelPlan).subscribe({
this.travelPlanService.saveTravelPlan(this.headerid, travelPlan).pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: () => { next: () => {
this.notificationService.showSuccess('Travel plan saved successfully'); this.notificationService.showSuccess('Travel plan saved successfully');
this.completed.emit(true); this.completed.emit(true);
this.isLoading = false;
}, },
error: (error) => { error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to save travel plan'); let errorMessage = this.errorHandler.handleApiError(error, 'Failed to save travel plan');
this.notificationService.showError(errorMessage); this.notificationService.showError(errorMessage);
console.error('Error saving travel plan data : ', error); console.error('Error saving travel plan data : ', error);
this.isLoading = false;
} }
}); });
} }

View File

@ -7,7 +7,7 @@
</div> </div>
<div class="copyright"> <div class="copyright">
&copy; {{ currentYear }} USCIB Carnet Portal. All rights reserved. &copy; {{ currentYear }} {{currentServiceProviderName}}. All rights reserved.
</div> </div>
</div> </div>
</footer> </footer>

View File

@ -1,6 +1,8 @@
import { Component, Input } from '@angular/core'; import { Component, effect, inject, Input } from '@angular/core';
import { AngularMaterialModule } from '../../shared/module/angular-material.module'; import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { User } from '../../core/models/user';
import { UserService } from '../../core/services/common/user.service';
@Component({ @Component({
selector: 'app-footer', selector: 'app-footer',
@ -10,5 +12,20 @@ import { CommonModule } from '@angular/common';
}) })
export class FooterComponent { export class FooterComponent {
currentYear = new Date().getFullYear(); currentYear = new Date().getFullYear();
currentServiceProviderName: string = '';
userDetails: User | null = {};
@Input() isUserLoggedIn = false; @Input() isUserLoggedIn = false;
private userService = inject(UserService);
constructor(
) {
effect(() => {
this.userDetails = this.userService.userDetailsSignal();
if (this.userDetails?.userDetails) {
this.currentServiceProviderName = this.userDetails.userDetails.serviceProviderName || '';
}
});
}
} }

View File

@ -6,18 +6,7 @@
<div class="navbar-menu"> <div class="navbar-menu">
<button mat-button (click)="navigateTo('home')">Home</button> <button mat-button (click)="navigateTo('home')">Home</button>
<button mat-button (click)="navigateTo('usersettings')">User Settings</button>
<!--
<button mat-icon-button [matMenuTriggerFor]="profile">
<mat-icon>account_circle</mat-icon>
</button>
<mat-menu #profile="matMenu">
<button mat-menu-item (click)="logout()">
<mat-icon>logout</mat-icon>
<span>Logout</span>
</button>
</mat-menu> -->
<div class="profile-container"> <div class="profile-container">
<button mat-icon-button (click)="toggleProfileMenu()" class="profile-button"> <button mat-icon-button (click)="toggleProfileMenu()" class="profile-button">
<mat-icon>account_circle</mat-icon> <mat-icon>account_circle</mat-icon>
@ -29,6 +18,11 @@
<span>{{ userEmail }}</span> <span>{{ userEmail }}</span>
</div> </div>
<button mat-menu-item (click)="navigateTo('/usersettings')">
<mat-icon>settings</mat-icon>
<span>User Preferences</span>
</button>
<button mat-menu-item (click)="logout()"> <button mat-menu-item (click)="logout()">
<mat-icon>logout</mat-icon> <mat-icon>logout</mat-icon>
<span>Logout</span> <span>Logout</span>

View File

@ -17,4 +17,5 @@ export interface UserDetail {
urlKey: string; urlKey: string;
logoName: string; logoName: string;
themeName: string; themeName: string;
serviceProviderName: string;
} }

View File

@ -16,6 +16,7 @@ import { UnitOfMeasure } from '../../models/unitofmeasure';
import { DeliveryMethod } from '../../models/delivery-method'; import { DeliveryMethod } from '../../models/delivery-method';
import { PaymentType } from '../../models/payment-type'; import { PaymentType } from '../../models/payment-type';
import { FormOfSecurity } from '../../models/formofsecurity'; import { FormOfSecurity } from '../../models/formofsecurity';
import { UserService } from './user.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -25,9 +26,10 @@ export class CommonService {
private apiDb = environment.apiDb; private apiDb = environment.apiDb;
private http = inject(HttpClient); private http = inject(HttpClient);
private userService = inject(UserService);
getCountries(spid: number = 0): Observable<Country[]> { getCountries(): Observable<Country[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=002&P_SPID=0`).pipe( return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=002&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) => map((response) =>
response.map((item) => ({ response.map((item) => ({
name: item.PARAMDESC, name: item.PARAMDESC,
@ -38,8 +40,8 @@ export class CommonService {
); );
} }
getStates(country: string, spid: number = 0): Observable<State[]> { getStates(country: string): Observable<State[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=001&P_SPID=0`).pipe( return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=001&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) => map((response) =>
response.map((item) => ({ response.map((item) => ({
name: item.PARAMDESC, name: item.PARAMDESC,
@ -67,8 +69,8 @@ export class CommonService {
); );
} }
getDeliveryTypes(spid: number = 0): Observable<DeliveryType[]> { getDeliveryTypes(): Observable<DeliveryType[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=006&P_SPID=0`).pipe( return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=006&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) => map((response) =>
response.map((item) => ({ response.map((item) => ({
name: item.PARAMDESC, name: item.PARAMDESC,
@ -81,8 +83,8 @@ export class CommonService {
); );
} }
getTimezones(spid: number = 0): Observable<TimeZone[]> { getTimezones(): Observable<TimeZone[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=010&P_SPID=0`).pipe( return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=010&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) => map((response) =>
response.map((item) => ({ response.map((item) => ({
name: item.PARAMDESC, name: item.PARAMDESC,
@ -93,8 +95,8 @@ export class CommonService {
); );
} }
getFeeTypes(spid: number = 0): Observable<FeeType[]> { getFeeTypes(): Observable<FeeType[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=009&P_SPID=0`).pipe( return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=009&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) => map((response) =>
response.map((item) => ({ response.map((item) => ({
name: item.PARAMDESC, name: item.PARAMDESC,
@ -105,8 +107,8 @@ export class CommonService {
); );
} }
getBondSuretys(spid: number = 0): Observable<BondSurety[]> { getBondSuretys(): Observable<BondSurety[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=003&P_SPID=0`).pipe( return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=003&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) => map((response) =>
response.map((item) => ({ response.map((item) => ({
name: item.PARAMDESC, name: item.PARAMDESC,
@ -117,8 +119,8 @@ export class CommonService {
); );
} }
getCargoPolicies(spid: number = 0): Observable<CargoPolicy[]> { getCargoPolicies(): Observable<CargoPolicy[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=004&P_SPID=0`).pipe( return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=004&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) => map((response) =>
response.map((item) => ({ response.map((item) => ({
name: item.PARAMDESC, name: item.PARAMDESC,
@ -129,8 +131,8 @@ export class CommonService {
); );
} }
getCargoSuretys(spid: number = 0): Observable<CargoSurety[]> { getCargoSuretys(): Observable<CargoSurety[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=005&P_SPID=0`).pipe( return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=005&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) => map((response) =>
response.map((item) => ({ response.map((item) => ({
name: item.PARAMDESC, name: item.PARAMDESC,
@ -141,8 +143,8 @@ export class CommonService {
); );
} }
getCarnetStatuses(spid: number = 0): Observable<CarnetStatus[]> { getCarnetStatuses(): Observable<CarnetStatus[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=011&P_SPID=0`).pipe( return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=011&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) => map((response) =>
response.map((item) => ({ response.map((item) => ({
name: item.PARAMDESC, name: item.PARAMDESC,
@ -154,8 +156,8 @@ export class CommonService {
); );
} }
getUnitOfMeasures(_spid: number = 0): Observable<UnitOfMeasure[]> { getUnitOfMeasures(): Observable<UnitOfMeasure[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=013&P_SPID=0`).pipe( return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=013&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) => map((response) =>
response.map((item) => ({ response.map((item) => ({
name: item.PARAMDESC, name: item.PARAMDESC,
@ -166,8 +168,8 @@ export class CommonService {
); );
} }
getDeliveryMethods(_spid: number = 0): Observable<DeliveryMethod[]> { getDeliveryMethods(): Observable<DeliveryMethod[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=007&P_SPID=0`).pipe( return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=007&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) => map((response) =>
response.map((item) => ({ response.map((item) => ({
name: item.PARAMDESC, name: item.PARAMDESC,
@ -178,8 +180,8 @@ export class CommonService {
); );
} }
getPaymentTypes(_spid: number = 0): Observable<PaymentType[]> { getPaymentTypes(): Observable<PaymentType[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=008&P_SPID=0`).pipe( return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=008&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) => map((response) =>
response.map((item) => ({ response.map((item) => ({
name: item.PARAMDESC, name: item.PARAMDESC,
@ -190,8 +192,8 @@ export class CommonService {
); );
} }
getFormOfSecurities(spid: number = 0): Observable<FormOfSecurity[]> { getFormOfSecurities(): Observable<FormOfSecurity[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=016&P_SPID=0`).pipe( return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=016&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) => map((response) =>
response.map((item) => ({ response.map((item) => ({
name: item.PARAMDESC, name: item.PARAMDESC,

View File

@ -136,7 +136,8 @@ export class UserService {
locationid: userDetails.LOCATIONID, locationid: userDetails.LOCATIONID,
urlKey: userDetails.ENCURLKEY, urlKey: userDetails.ENCURLKEY,
logoName: userDetails.LOGONAME, logoName: userDetails.LOGONAME,
themeName: userDetails.THEMENAME themeName: userDetails.THEMENAME,
serviceProviderName: userDetails.SPNAME
}; };
} }

View File

@ -148,7 +148,7 @@
<mat-icon>chevron_left</mat-icon> Back to application <mat-icon>chevron_left</mat-icon> Back to application
</button> </button>
<button mat-raised-button color="primary" type="submit" <button mat-raised-button color="primary" type="submit"
[disabled]="basicDetailsForm.invalid || !basicDetailsForm.dirty"> [disabled]="basicDetailsForm.invalid || !basicDetailsForm.dirty || changeInProgress">
{{ isEditMode ? 'Update' : 'Save' }} {{ isEditMode ? 'Update' : 'Save' }}
</button> </button>
</div> </div>

View File

@ -8,7 +8,7 @@ import { NotificationService } from '../../core/services/common/notification.ser
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service'; import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
import { ZipCodeValidator } from '../../shared/validators/zipcode-validator'; import { ZipCodeValidator } from '../../shared/validators/zipcode-validator';
import { CommonService } from '../../core/services/common/common.service'; import { CommonService } from '../../core/services/common/common.service';
import { Subject, takeUntil } from 'rxjs'; import { finalize, Subject, takeUntil } from 'rxjs';
import { Country } from '../../core/models/country'; import { Country } from '../../core/models/country';
import { BasicDetailService } from '../../core/services/holder/basic-detail.service'; import { BasicDetailService } from '../../core/services/holder/basic-detail.service';
import { BasicDetail } from '../../core/models/holder/basic-detail'; import { BasicDetail } from '../../core/models/holder/basic-detail';
@ -27,7 +27,6 @@ export class BasicDetailComponent implements OnInit, OnDestroy {
@Input() holderid: number = 0; @Input() holderid: number = 0;
@Output() holderIdCreated = new EventEmitter<number>(); @Output() holderIdCreated = new EventEmitter<number>();
// @Output() holderName = new EventEmitter<string>();
basicDetailsForm: FormGroup; basicDetailsForm: FormGroup;
countries: Country[] = []; countries: Country[] = [];
@ -36,6 +35,7 @@ export class BasicDetailComponent implements OnInit, OnDestroy {
currentApplicationDetails: { headerid: number, applicationName: string } | null = null; currentApplicationDetails: { headerid: number, applicationName: string } | null = null;
isLoading = false; isLoading = false;
changeInProgress = false;
countriesHasStates = ['US', 'CA', 'MX']; countriesHasStates = ['US', 'CA', 'MX'];
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
@ -60,12 +60,12 @@ export class BasicDetailComponent implements OnInit, OnDestroy {
holderNumber: ['', [Validators.required, Validators.maxLength(20)]], holderNumber: ['', [Validators.required, Validators.maxLength(20)]],
holderType: ['', [Validators.required, Validators.maxLength(20)]], holderType: ['', [Validators.required, Validators.maxLength(20)]],
uscibMember: [false, [Validators.required]], uscibMember: [false, [Validators.required]],
// govAgency: [false, [Validators.required]], // govAgency: [false, [Validators.required]],
address1: ['', [Validators.required, Validators.maxLength(100)]], address1: ['', [Validators.required, Validators.maxLength(100)]],
address2: ['', Validators.maxLength(100)], address2: ['', Validators.maxLength(100)],
city: ['', [Validators.required, Validators.maxLength(50)]], city: ['', [Validators.required, Validators.maxLength(50)]],
state: ['', Validators.required], state: ['', Validators.required],
country: ['', Validators.required], country: ['US', Validators.required],
zip: ['', [Validators.required, ZipCodeValidator('country')]] zip: ['', [Validators.required, ZipCodeValidator('country')]]
}); });
} }
@ -75,19 +75,21 @@ export class BasicDetailComponent implements OnInit, OnDestroy {
if (this.holderid > 0) { if (this.holderid > 0) {
this.isLoading = true; this.isLoading = true;
this.basicDetailService.getBasicDetailByHolderId(this.holderid).subscribe({ this.basicDetailService.getBasicDetailByHolderId(this.holderid).pipe(finalize(() => {
this.isLoading = false;
})).subscribe({
next: (basicDetail: BasicDetail) => { next: (basicDetail: BasicDetail) => {
this.patchFormData(basicDetail); this.patchFormData(basicDetail);
// this.holderName.emit(basicDetail.holderName); // this.holderName.emit(basicDetail.holderName);
this.isLoading = false;
}, },
error: (error: any) => { error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load basic details'); let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load basic details');
this.notificationService.showError(errorMessage); this.notificationService.showError(errorMessage);
this.isLoading = false;
console.error('Error loading basic details:', error); console.error('Error loading basic details:', error);
} }
}); });
} else {
this.loadStates('US'); // Load states for default country
} }
} }
@ -118,12 +120,20 @@ export class BasicDetailComponent implements OnInit, OnDestroy {
? this.basicDetailService.updateBasicDetails(this.holderid, basicDetailData) ? this.basicDetailService.updateBasicDetails(this.holderid, basicDetailData)
: this.basicDetailService.createBasicDetail(basicDetailData); : this.basicDetailService.createBasicDetail(basicDetailData);
saveObservable.subscribe({ this.changeInProgress = true;
saveObservable.pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: (basicData: any) => { next: (basicData: any) => {
this.notificationService.showSuccess(`Basic details ${this.isEditMode ? 'updated' : 'added'} successfully`); this.notificationService.showSuccess(`Basic details ${this.isEditMode ? 'updated' : 'added'} successfully`);
if (!this.isEditMode) { if (!this.isEditMode) {
this.holderIdCreated.emit(basicData.HOLDERID); this.holderIdCreated.emit(basicData.HOLDERID);
// change to edit mode after creation
this.isEditMode = true;
this.holderid = basicData.HOLDERID;
} }
// if (this.isEditMode) { // if (this.isEditMode) {
@ -156,7 +166,7 @@ export class BasicDetailComponent implements OnInit, OnDestroy {
holderNumber: data.holderNumber, holderNumber: data.holderNumber,
holderType: data.holderType, holderType: data.holderType,
uscibMember: data.uscibMember, uscibMember: data.uscibMember,
// govAgency: data.govAgency, // govAgency: data.govAgency,
address1: data.address1, address1: data.address1,
address2: data.address2, address2: data.address2,
city: data.city, city: data.city,
@ -176,34 +186,29 @@ export class BasicDetailComponent implements OnInit, OnDestroy {
loadLookupData(): void { loadLookupData(): void {
this.commonService.getCountries(0) this.commonService.getCountries()
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe({ .subscribe({
next: (countries) => { next: (countries) => {
this.countries = countries; this.countries = countries;
this.isLoading = false;
}, },
error: (error) => { error: (error) => {
console.error('Failed to load countries', error); console.error('Failed to load countries', error);
this.isLoading = false;
} }
}); });
} }
loadStates(country: string): void { loadStates(country: string): void {
this.isLoading = true;
country = this.countriesHasStates.includes(country) ? country : 'FN'; country = this.countriesHasStates.includes(country) ? country : 'FN';
this.commonService.getStates(country, this.holderid) this.commonService.getStates(country)
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe({ .subscribe({
next: (states) => { next: (states) => {
this.states = states; this.states = states;
this.updateStateControl('state', country); this.updateStateControl('state', country);
this.isLoading = false;
}, },
error: (error) => { error: (error) => {
console.error('Failed to load states', error); console.error('Failed to load states', error);
this.isLoading = false;
} }
}); });
} }

View File

@ -256,7 +256,7 @@
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button mat-raised-button color="primary" type="submit" [disabled]="contactForm.invalid"> <button mat-raised-button color="primary" type="submit" [disabled]="contactForm.invalid || changeInProgress">
{{ isEditing ? 'Update' : 'Save' }} {{ isEditing ? 'Update' : 'Save' }}
</button> </button>
<button mat-button type="button" (click)="cancelEdit()">Cancel</button> <button mat-button type="button" (click)="cancelEdit()">Cancel</button>

View File

@ -16,6 +16,7 @@ import { MatDialog } from '@angular/material/dialog';
import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component'; import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component';
import { NavigationService } from '../../core/services/common/navigation.service'; import { NavigationService } from '../../core/services/common/navigation.service';
import { StorageService } from '../../core/services/common/storage.service'; import { StorageService } from '../../core/services/common/storage.service';
import { finalize } from 'rxjs';
@Component({ @Component({
selector: 'app-contacts', selector: 'app-contacts',
@ -41,6 +42,7 @@ export class ContactsComponent {
isEditing = false; isEditing = false;
currentContactId: number | null = null; currentContactId: number | null = null;
isLoading = false; isLoading = false;
changeInProgress = false;
showForm = false; showForm = false;
showInactiveContacts = false; showInactiveContacts = false;
contacts: Contact[] = []; contacts: Contact[] = [];
@ -67,9 +69,9 @@ export class ContactsComponent {
lastName: ['', [Validators.required, Validators.maxLength(50)]], lastName: ['', [Validators.required, Validators.maxLength(50)]],
middleInitial: ['', [Validators.maxLength(1)]], middleInitial: ['', [Validators.maxLength(1)]],
title: ['', [Validators.required, Validators.maxLength(100)]], title: ['', [Validators.required, Validators.maxLength(100)]],
phone: ['', [Validators.required, Validators.pattern(/^[0-9]{10,15}$/)]], phone: ['', [Validators.required, Validators.pattern(/^[0-9\-\(\)]{10,15}$/)]],
mobile: ['', [Validators.required, Validators.pattern(/^[0-9]{10,15}$/)]], mobile: ['', [Validators.required, Validators.pattern(/^[0-9\-\(\)]{10,15}$/)]],
fax: ['', [Validators.pattern(/^[0-9]{10,15}$/)]], fax: ['', [Validators.pattern(/^[0-9\-\(\)]{10,15}$/)]],
email: ['', [Validators.required, Validators.email, Validators.maxLength(100)]], email: ['', [Validators.required, Validators.email, Validators.maxLength(100)]],
}); });
@ -90,16 +92,16 @@ export class ContactsComponent {
loadContacts(): void { loadContacts(): void {
this.isLoading = true; this.isLoading = true;
this.contactService.getContactsById(this.holderid).subscribe({ this.contactService.getContactsById(this.holderid).pipe(finalize(() => {
this.isLoading = false;
})).subscribe({
next: (contacts: Contact[]) => { next: (contacts: Contact[]) => {
this.contacts = contacts; this.contacts = contacts;
this.renderContacts(); this.renderContacts();
this.isLoading = false;
}, },
error: (error: any) => { error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load contacts'); let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load contacts');
this.notificationService.showError(errorMessage); this.notificationService.showError(errorMessage);
this.isLoading = false;
console.error('Error loading contacts:', error); console.error('Error loading contacts:', error);
} }
}); });
@ -146,7 +148,11 @@ export class ContactsComponent {
? this.contactService.updateContact(this.currentContactId!, contactData) ? this.contactService.updateContact(this.currentContactId!, contactData)
: this.contactService.createContact(this.holderid, contactData); : this.contactService.createContact(this.holderid, contactData);
saveObservable.subscribe({ this.changeInProgress = true;
saveObservable.pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: () => { next: () => {
this.notificationService.showSuccess(`Contact ${this.isEditing ? 'updated' : 'added'} successfully`); this.notificationService.showSuccess(`Contact ${this.isEditing ? 'updated' : 'added'} successfully`);
this.loadContacts(); this.loadContacts();

View File

@ -117,7 +117,7 @@
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button mat-raised-button color="primary" type="submit" (click)="saveHolderSelection()"> <button mat-raised-button color="primary" type="submit" [disabled]="changeInProgress" (click)="saveHolderSelection()">
{{ 'Save' }} {{ 'Save' }}
</button> </button>
</div> </div>

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, inject, Input, Output, ViewChild } from '@angular/core'; import { Component, EventEmitter, inject, Input, Output, SimpleChanges, ViewChild } from '@angular/core';
import { AngularMaterialModule } from '../../shared/module/angular-material.module'; import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
@ -18,6 +18,7 @@ import { HolderFilter } from '../../core/models/holder/holder-filter';
import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component'; import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { StorageService } from '../../core/services/common/storage.service'; import { StorageService } from '../../core/services/common/storage.service';
import { finalize } from 'rxjs';
@Component({ @Component({
selector: 'app-holder-search', selector: 'app-holder-search',
@ -27,11 +28,16 @@ import { StorageService } from '../../core/services/common/storage.service';
providers: [{ provide: MatPaginatorIntl, useClass: CustomPaginator }], providers: [{ provide: MatPaginatorIntl, useClass: CustomPaginator }],
}) })
export class SearchHolderComponent { export class SearchHolderComponent {
private _paginator!: MatPaginator;
@ViewChild(MatPaginator, { static: false }) @ViewChild(MatPaginator, { static: false })
set paginator(value: MatPaginator) { set paginator(value: MatPaginator) {
this._paginator = value;
this.dataSource.paginator = value; this.dataSource.paginator = value;
} }
get paginator(): MatPaginator {
return this._paginator;
}
@ViewChild(MatSort, { static: false }) @ViewChild(MatSort, { static: false })
set sort(value: MatSort) { set sort(value: MatSort) {
@ -47,6 +53,7 @@ export class SearchHolderComponent {
showInactiveHolders: boolean = false; showInactiveHolders: boolean = false;
holders: BasicDetail[] = [] holders: BasicDetail[] = []
isLoading: boolean = false; isLoading: boolean = false;
changeInProgress: boolean = false;
userPreferences: UserPreferences; userPreferences: UserPreferences;
searchForm: FormGroup; searchForm: FormGroup;
@ -62,7 +69,8 @@ export class SearchHolderComponent {
dataSource = new MatTableDataSource<any>([]); dataSource = new MatTableDataSource<any>([]);
ngAfterViewInit() { ngAfterViewInit() {
this.dataSource.paginator = this.paginator; // This is different from other pages to show the item selected in the table.
//this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort; this.dataSource.sort = this.sort;
} }
@ -85,14 +93,25 @@ export class SearchHolderComponent {
this.searchHolders(); this.searchHolders();
} }
ngOnChanges(changes: SimpleChanges): void {
if (changes['selectedHolderId'] && this.paginator) {
if (this.selectedHolderId && this.dataSource.data.length) {
this.goToItemPage(this.selectedHolderId);
}
}
}
onSearch(): void { onSearch(): void {
this.searchHolders(); this.searchHolders();
} }
saveHolderSelection(): void { saveHolderSelection(): void {
this.changeInProgress = true;
const saveObservable = this.carnetHolderService.saveApplicationHolder(this.headerid, this.selectedHolderId ? Number(this.selectedHolderId) : 0); const saveObservable = this.carnetHolderService.saveApplicationHolder(this.headerid, this.selectedHolderId ? Number(this.selectedHolderId) : 0);
saveObservable.subscribe({ saveObservable.pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: (basicData: any) => { next: (basicData: any) => {
this.notificationService.showSuccess(`Holder updated successfully`); this.notificationService.showSuccess(`Holder updated successfully`);
this.holderSelectionCompleted.emit(true); this.holderSelectionCompleted.emit(true);
@ -114,16 +133,16 @@ export class SearchHolderComponent {
this.isLoading = true; this.isLoading = true;
const filterData: HolderFilter = this.searchForm.value; const filterData: HolderFilter = this.searchForm.value;
this.holderService.getHolders(filterData).subscribe({ this.holderService.getHolders(filterData).pipe(finalize(() => {
this.isLoading = false;
})).subscribe({
next: (holders: BasicDetail[]) => { next: (holders: BasicDetail[]) => {
this.dataSource.data = this.holders = holders; this.dataSource.data = this.holders = holders;
this.renderHolders() this.renderHolders()
this.isLoading = false;
}, },
error: (error: any) => { error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to search holders'); let errorMessage = this.errorHandler.handleApiError(error, 'Failed to search holders');
this.notificationService.showError(errorMessage); this.notificationService.showError(errorMessage);
this.isLoading = false;
console.error('Error loading holders:', error); console.error('Error loading holders:', error);
} }
}); });
@ -222,8 +241,25 @@ export class SearchHolderComponent {
getAddressLabel(holder: BasicDetail): string { getAddressLabel(holder: BasicDetail): string {
return `${holder.address1}, ${holder.address2} const parts = [
${holder.city}, ${holder.state}, ${holder.zip}, holder.address1,
${holder.country}`; holder.address2,
holder.city,
holder.state,
holder.zip,
holder.country
];
// Filter out any empty, null, or undefined parts and then join them.
return parts.filter(part => part).join(', ');
}
goToItemPage(holderid: number): void {
const itemIndex = this.dataSource.data.findIndex(dataItem => dataItem.holderid === holderid);
if (itemIndex > -1 && this.paginator) {
const targetPageIndex = Math.floor(itemIndex / this.paginator.pageSize);
this.paginator.pageIndex = targetPageIndex;
this.dataSource.paginator = this.paginator;
}
} }
} }