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">
<ng-template matStepLabel>Shipping & Payment</ng-template>
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid"
[applicationName]="applicationName" [enableSubmitButton]="allSectionsCompleted"
[refreshShippingData]="refreshShippingData">
[applicationName]="applicationName" [enableSubmitButton]="allSectionsCompleted">
</app-shipping>
</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 { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { CommonModule } from '@angular/common';
@ -27,7 +27,6 @@ export class AddCarnetComponent {
headerid: number = 0;
applicationName: string = '';
userPreferences: UserPreferences;
refreshShippingData: boolean = false;
allSectionsCompleted: boolean = false;
// Track completion of each step
@ -39,6 +38,9 @@ export class AddCarnetComponent {
shipping: false
};
@ViewChild(ShippingComponent, { static: false })
private shippingComponent!: ShippingComponent;
constructor(userPrefenceService: UserPreferencesService) {
this.userPreferences = userPrefenceService.getPreferences();
}
@ -60,7 +62,9 @@ export class AddCarnetComponent {
}
onHolderSelectionUpdated(updated: boolean): void {
this.refreshShippingData = updated;
if (updated) {
this.shippingComponent?.refreshShippingData();
}
}
onGoodsSectionSaved(completed: boolean): void {

View File

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

View File

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

View File

@ -219,7 +219,8 @@
</div>
<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
</button>
<button mat-button type="button" (click)="cancelEdit()">Cancel</button>
@ -228,7 +229,8 @@
</div>
<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
</button>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@
</div>
<div class="copyright">
&copy; {{ currentYear }} USCIB Carnet Portal. All rights reserved.
&copy; {{ currentYear }} {{currentServiceProviderName}}. All rights reserved.
</div>
</div>
</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 { CommonModule } from '@angular/common';
import { User } from '../../core/models/user';
import { UserService } from '../../core/services/common/user.service';
@Component({
selector: 'app-footer',
@ -10,5 +12,20 @@ import { CommonModule } from '@angular/common';
})
export class FooterComponent {
currentYear = new Date().getFullYear();
currentServiceProviderName: string = '';
userDetails: User | null = {};
@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,17 +6,6 @@
<div class="navbar-menu">
<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">
<button mat-icon-button (click)="toggleProfileMenu()" class="profile-button">
@ -29,6 +18,11 @@
<span>{{ userEmail }}</span>
</div>
<button mat-menu-item (click)="navigateTo('/usersettings')">
<mat-icon>settings</mat-icon>
<span>User Preferences</span>
</button>
<button mat-menu-item (click)="logout()">
<mat-icon>logout</mat-icon>
<span>Logout</span>

View File

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

View File

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

View File

@ -136,7 +136,8 @@ export class UserService {
locationid: userDetails.LOCATIONID,
urlKey: userDetails.ENCURLKEY,
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
</button>
<button mat-raised-button color="primary" type="submit"
[disabled]="basicDetailsForm.invalid || !basicDetailsForm.dirty">
[disabled]="basicDetailsForm.invalid || !basicDetailsForm.dirty || changeInProgress">
{{ isEditMode ? 'Update' : 'Save' }}
</button>
</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 { ZipCodeValidator } from '../../shared/validators/zipcode-validator';
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 { BasicDetailService } from '../../core/services/holder/basic-detail.service';
import { BasicDetail } from '../../core/models/holder/basic-detail';
@ -27,7 +27,6 @@ export class BasicDetailComponent implements OnInit, OnDestroy {
@Input() holderid: number = 0;
@Output() holderIdCreated = new EventEmitter<number>();
// @Output() holderName = new EventEmitter<string>();
basicDetailsForm: FormGroup;
countries: Country[] = [];
@ -36,6 +35,7 @@ export class BasicDetailComponent implements OnInit, OnDestroy {
currentApplicationDetails: { headerid: number, applicationName: string } | null = null;
isLoading = false;
changeInProgress = false;
countriesHasStates = ['US', 'CA', 'MX'];
private destroy$ = new Subject<void>();
@ -65,7 +65,7 @@ export class BasicDetailComponent implements OnInit, OnDestroy {
address2: ['', Validators.maxLength(100)],
city: ['', [Validators.required, Validators.maxLength(50)]],
state: ['', Validators.required],
country: ['', Validators.required],
country: ['US', Validators.required],
zip: ['', [Validators.required, ZipCodeValidator('country')]]
});
}
@ -75,19 +75,21 @@ export class BasicDetailComponent implements OnInit, OnDestroy {
if (this.holderid > 0) {
this.isLoading = true;
this.basicDetailService.getBasicDetailByHolderId(this.holderid).subscribe({
this.basicDetailService.getBasicDetailByHolderId(this.holderid).pipe(finalize(() => {
this.isLoading = false;
})).subscribe({
next: (basicDetail: BasicDetail) => {
this.patchFormData(basicDetail);
// this.holderName.emit(basicDetail.holderName);
this.isLoading = false;
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load basic details');
this.notificationService.showError(errorMessage);
this.isLoading = false;
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.createBasicDetail(basicDetailData);
saveObservable.subscribe({
this.changeInProgress = true;
saveObservable.pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: (basicData: any) => {
this.notificationService.showSuccess(`Basic details ${this.isEditMode ? 'updated' : 'added'} successfully`);
if (!this.isEditMode) {
this.holderIdCreated.emit(basicData.HOLDERID);
// change to edit mode after creation
this.isEditMode = true;
this.holderid = basicData.HOLDERID;
}
// if (this.isEditMode) {
@ -176,34 +186,29 @@ export class BasicDetailComponent implements OnInit, OnDestroy {
loadLookupData(): void {
this.commonService.getCountries(0)
this.commonService.getCountries()
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (countries) => {
this.countries = countries;
this.isLoading = false;
},
error: (error) => {
console.error('Failed to load countries', error);
this.isLoading = false;
}
});
}
loadStates(country: string): void {
this.isLoading = true;
country = this.countriesHasStates.includes(country) ? country : 'FN';
this.commonService.getStates(country, this.holderid)
this.commonService.getStates(country)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (states) => {
this.states = states;
this.updateStateControl('state', country);
this.isLoading = false;
},
error: (error) => {
console.error('Failed to load states', error);
this.isLoading = false;
}
});
}

View File

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

View File

@ -117,7 +117,7 @@
</div>
<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' }}
</button>
</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 { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
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 { MatDialog } from '@angular/material/dialog';
import { StorageService } from '../../core/services/common/storage.service';
import { finalize } from 'rxjs';
@Component({
selector: 'app-holder-search',
@ -27,11 +28,16 @@ import { StorageService } from '../../core/services/common/storage.service';
providers: [{ provide: MatPaginatorIntl, useClass: CustomPaginator }],
})
export class SearchHolderComponent {
private _paginator!: MatPaginator;
@ViewChild(MatPaginator, { static: false })
set paginator(value: MatPaginator) {
this._paginator = value;
this.dataSource.paginator = value;
}
get paginator(): MatPaginator {
return this._paginator;
}
@ViewChild(MatSort, { static: false })
set sort(value: MatSort) {
@ -47,6 +53,7 @@ export class SearchHolderComponent {
showInactiveHolders: boolean = false;
holders: BasicDetail[] = []
isLoading: boolean = false;
changeInProgress: boolean = false;
userPreferences: UserPreferences;
searchForm: FormGroup;
@ -62,7 +69,8 @@ export class SearchHolderComponent {
dataSource = new MatTableDataSource<any>([]);
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;
}
@ -85,14 +93,25 @@ export class SearchHolderComponent {
this.searchHolders();
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['selectedHolderId'] && this.paginator) {
if (this.selectedHolderId && this.dataSource.data.length) {
this.goToItemPage(this.selectedHolderId);
}
}
}
onSearch(): void {
this.searchHolders();
}
saveHolderSelection(): void {
this.changeInProgress = true;
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) => {
this.notificationService.showSuccess(`Holder updated successfully`);
this.holderSelectionCompleted.emit(true);
@ -114,16 +133,16 @@ export class SearchHolderComponent {
this.isLoading = true;
const filterData: HolderFilter = this.searchForm.value;
this.holderService.getHolders(filterData).subscribe({
this.holderService.getHolders(filterData).pipe(finalize(() => {
this.isLoading = false;
})).subscribe({
next: (holders: BasicDetail[]) => {
this.dataSource.data = this.holders = holders;
this.renderHolders()
this.isLoading = false;
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to search holders');
this.notificationService.showError(errorMessage);
this.isLoading = false;
console.error('Error loading holders:', error);
}
});
@ -222,8 +241,25 @@ export class SearchHolderComponent {
getAddressLabel(holder: BasicDetail): string {
return `${holder.address1}, ${holder.address2}
${holder.city}, ${holder.state}, ${holder.zip},
${holder.country}`;
const parts = [
holder.address1,
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;
}
}
}