diff --git a/src/app/carnet/shipping/contact-dialog.component.ts b/src/app/carnet/shipping/contact-dialog.component.ts new file mode 100644 index 0000000..2618543 --- /dev/null +++ b/src/app/carnet/shipping/contact-dialog.component.ts @@ -0,0 +1,61 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { AngularMaterialModule } from '../../shared/module/angular-material.module'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-contact-dialog', + imports: [AngularMaterialModule, CommonModule, FormsModule], + template: ` +

Select Contact

+ + + + {{getContactLabel(contact)}} + + + + + + + + `, + styles: [` + mat-radio-group { + display: flex; + flex-direction: column; + gap: 12px; + } + mat-radio-button { + margin: 4px 0; + } + `] +}) +export class ContactDialogComponent { + selectedContact: any | null = null; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { contacts: any[] } + ) { } + + get contacts() { + return this.data.contacts; + } + + onSelect(): void { + this.dialogRef.close(this.selectedContact); + } + + onCancel(): void { + this.dialogRef.close(); + } + + getContactLabel(contact: any): string { + return `${contact.firstName} ${contact.middleInitial} ${contact.lastName}, + ${contact.email}, ${contact.phone}`; + } +} \ No newline at end of file diff --git a/src/app/carnet/shipping/shipping.component.html b/src/app/carnet/shipping/shipping.component.html index 79a3731..3cbcee5 100644 --- a/src/app/carnet/shipping/shipping.component.html +++ b/src/app/carnet/shipping/shipping.component.html @@ -33,6 +33,10 @@ + @@ -249,7 +253,7 @@
Delivery Type - + {{ deliveryType.name }} @@ -263,7 +267,8 @@ Delivery Method - + {{ deliveryMethod.name }} diff --git a/src/app/carnet/shipping/shipping.component.ts b/src/app/carnet/shipping/shipping.component.ts index 0ec348d..a0e03c1 100644 --- a/src/app/carnet/shipping/shipping.component.ts +++ b/src/app/carnet/shipping/shipping.component.ts @@ -15,6 +15,10 @@ import { DeliveryType } from '../../core/models/delivery-type'; import { DeliveryMethod } from '../../core/models/delivery-method'; import { PaymentType } from '../../core/models/payment-type'; import { format, addDays, isAfter, isWeekend } from 'date-fns'; +import { MatDialog } from '@angular/material/dialog'; +import { ShippingContact } from '../../core/models/carnet/shipping-contact'; +import { ShippingAddress } from '../../core/models/carnet/shipping-address'; +import { ContactDialogComponent } from './contact-dialog.component'; @Component({ selector: 'app-shipping', @@ -28,6 +32,8 @@ export class ShippingComponent { @Output() completed = new EventEmitter(); private fb = inject(FormBuilder); + private dialog = inject(MatDialog); + private shippingService = inject(ShippingService); private notificationService = inject(NotificationService); private errorHandler = inject(ApiErrorHandlerService); @@ -49,16 +55,33 @@ export class ShippingComponent { private destroy$ = new Subject(); // preparer contact and address mock data - preparerContact = { - firstName: 'John', - lastName: 'Doe', - middleInitial: 'A', - title: 'Mr.', - phone: '1234567890', - mobile: '0987654321', - fax: '1234567890', - email: 'j@doe.com', - } + preparerContact: ShippingContact | null | undefined = null; + + preparerContacts: ShippingContact[] = [ + { + contactid: 1, + firstName: 'John', + lastName: 'Doe', + middleInitial: 'A', + title: 'Mr.', + phone: '1234567890', + mobile: '0987654321', + fax: '1234567890', + email: 'j@doe.com', + defaultContact: true + }, + { + contactid: 2, + firstName: 'Jane', + lastName: 'Smith', + middleInitial: 'B', + title: 'Ms.', + phone: '2345678901', + mobile: '1098765432', + fax: '2345678901', + email: 'jan@sm.cm', + defaultContact: false + }]; preparerAddress = { companyName: 'ABC Company', @@ -80,16 +103,33 @@ export class ShippingComponent { country: 'US' }; - holderContact = { - firstName: 'Jane', - lastName: 'Smith', - middleInitial: 'B', - title: 'Ms.', - phone: '2345678901', - mobile: '1098765432', - fax: '2345678901', - email: 'jan@sm.cm' - }; + holderContact: ShippingContact | null | undefined = null; + + holderContacts: ShippingContact[] = [ + { + contactid: 1, + firstName: 'John', + lastName: 'Doe', + middleInitial: 'A', + title: 'Mr.', + phone: '1234567890', + mobile: '0987654321', + fax: '1234567890', + email: 'j@doe.com', + defaultContact: false + }, + { + contactid: 2, + firstName: 'Jane', + lastName: 'Smith', + middleInitial: 'B', + title: 'Ms.', + phone: '2345678901', + mobile: '1098765432', + fax: '2345678901', + email: 'jan@sm.cm', + defaultContact: true + }]; constructor() { this.shippingForm = this.fb.group({ @@ -124,19 +164,8 @@ export class ShippingComponent { paymentNotes: [''] }); - // Update form validation based on selections - this.shippingForm.get('shipTo')?.valueChanges.subscribe(value => { - this.updateShippingValidation(value); - }); - this.shippingForm.get('deliveryMethod')?.valueChanges.subscribe(value => { - const courierControl = this.shippingForm.get('courierAccount'); - if (value === 'CLC') { - courierControl?.setValidators([Validators.required]); - } else { - courierControl?.clearValidators(); - } - courierControl?.updateValueAndValidity(); + }); this.shippingForm.get('deliveryType')?.valueChanges.subscribe(() => { @@ -156,6 +185,10 @@ export class ShippingComponent { if (this.headerid && this.isEditMode) { this.loadShippingData(); + + // TODO + this.preparerContact = this.preparerContacts.find(pc => pc.defaultContact); + this.holderContact = this.holderContacts.find(hc => hc.defaultContact); } } @@ -164,6 +197,43 @@ export class ShippingComponent { this.destroy$.complete(); } + onSubmit(): void { + if (this.shippingForm.invalid) { + this.shippingForm.markAllAsTouched(); + return; + } + + this.isLoading = true; + const shippingData = this.shippingForm.value; + + this.shippingService.saveShippingDetails(this.headerid, shippingData).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; + } + }); + } + + onDeliveryTypeChange(): void { + this.calculateDeliveryEstimate(); + } + + onDeliveryMethodChange(deliveryMethod: string): void { + const courierControl = this.shippingForm.get('courierAccount'); + if (deliveryMethod === 'CLC') { + courierControl?.setValidators([Validators.required]); + } else { + courierControl?.clearValidators(); + } + courierControl?.updateValueAndValidity(); + } + onCountryChange(country: string): void { this.shippingForm.get('address.state')?.reset(); @@ -174,6 +244,39 @@ export class ShippingComponent { this.shippingForm.get('address.zip')?.updateValueAndValidity(); } + editAddressForm(): void { + this.showAddressForm = true; + let shipTo = this.shippingForm.get('shipTo')?.value; + + if (shipTo === 'preparer') { + this.shippingForm.get('address')?.patchValue(this.preparerAddress); + this.loadStates(this.preparerAddress.country); + } else if (shipTo === 'holder') { + this.shippingForm.get('address')?.patchValue(this.holderAddress); + this.loadStates(this.preparerAddress.country); + } + } + + cancelEditAddressForm(): void { + this.showAddressForm = false; + this.shippingForm.get('address')?.reset(); + } + + onShipToChange(event: any): void { + const shipTo = event.value; + this.showAddressForm = false; + this.showContactForm = false; + + this.updateShippingValidation(shipTo); + + if (shipTo === 'thirdParty') { + this.shippingForm.get('contact')?.reset(); + this.shippingForm.get('address')?.reset(); + this.showAddressForm = true; + this.showContactForm = true; + } + } + loadCountries(): void { this.commonService.getCountries(0) .pipe(takeUntil(this.destroy$)) @@ -285,8 +388,14 @@ export class ShippingComponent { this.loadStates(shipping.address?.country); } + if (shipping.deliveryMethod === 'CLC') { + this.onDeliveryMethodChange(shipping.deliveryMethod); + } + + this.calculateDeliveryEstimate(); + this.updateShippingValidation(shipping.shipTo); + if (shipping.shipTo === 'thirdParty') { - this.updateShippingValidation(shipping.shipTo); this.showAddressForm = true; this.showContactForm = true; @@ -317,8 +426,64 @@ export class ShippingComponent { notes: shipping.contact?.notes, }) } + } - this.calculateDeliveryEstimate(); + loadPreparerContacts(): void { + this.isLoading = true; + this.shippingService.getPreparerContactsById().subscribe({ + next: (data: ShippingContact) => { + // this.preparerContacts = data; + this.preparerContact = this.preparerContacts.find(pc => pc.defaultContact); + }, + error: (error: any) => { + let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load preparer contacts data'); + this.notificationService.showError(errorMessage); + this.isLoading = false; + } + }); + } + + loadHolderContacts(): void { + this.isLoading = true; + this.shippingService.getHolderContactsById(0).subscribe({ + next: (data: ShippingContact) => { + // this.holderContacts = data; + this.holderContact = this.holderContacts.find(hc => hc.defaultContact); + }, + error: (error: any) => { + let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load holder contacts data'); + this.notificationService.showError(errorMessage); + this.isLoading = false; + } + }); + } + + loadPreparerAddress(): void { + this.isLoading = true; + this.shippingService.getPreparerAddress().subscribe({ + next: (data: ShippingAddress) => { + // this.preparerAddress = data; + }, + error: (error: any) => { + let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load preparer address data'); + this.notificationService.showError(errorMessage); + this.isLoading = false; + } + }); + } + + loadHolderAddress(): void { + this.isLoading = true; + this.shippingService.getHolderAddressById(0).subscribe({ + next: (data: ShippingAddress) => { + // this.holderAddress = data; + }, + error: (error: any) => { + let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load holder address data'); + this.notificationService.showError(errorMessage); + this.isLoading = false; + } + }); } updateShippingValidation(shipTo: string): void { @@ -340,60 +505,38 @@ export class ShippingComponent { } else if (key === 'zip') { addressGroup.get(key)?.setValidators([Validators.required, ZipCodeValidator('country')]); } + addressGroup.get(key)?.updateValueAndValidity(); }); Object.keys(contactGroup.controls).forEach(key => { if (key === 'firstName') { - addressGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(50)]); + contactGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(50)]); } else if (key === 'lastName') { - addressGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(50)]); + contactGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(50)]); } else if (key === 'middleInitial') { - addressGroup.get(key)?.setValidators([Validators.maxLength(1)]); + contactGroup.get(key)?.setValidators([Validators.maxLength(1)]); } else if (key === 'title') { - addressGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(100)]); + contactGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(100)]); } else if (key === 'phone') { - addressGroup.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') { - addressGroup.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') { - addressGroup.get(key)?.setValidators([Validators.pattern(/^[0-9]{10,15}$/)]); + contactGroup.get(key)?.setValidators([Validators.pattern(/^[0-9]{10,15}$/)]); } else if (key === 'email') { - addressGroup.get(key)?.setValidators([Validators.required, Validators.email, Validators.maxLength(100)]); + contactGroup.get(key)?.setValidators([Validators.required, Validators.email, Validators.maxLength(100)]); } + contactGroup.get(key)?.updateValueAndValidity(); }); } else { Object.keys(addressGroup.controls).forEach(key => { addressGroup.get(key)?.clearValidators(); + addressGroup.get(key)?.updateValueAndValidity(); }); Object.keys(contactGroup.controls).forEach(key => { contactGroup.get(key)?.clearValidators(); + contactGroup.get(key)?.updateValueAndValidity(); }); } - - // addressGroup.updateValueAndValidity(); - // contactGroup.updateValueAndValidity(); - } - - onSubmit(): void { - if (this.shippingForm.invalid) { - this.shippingForm.markAllAsTouched(); - return; - } - - this.isLoading = true; - const shippingData = this.shippingForm.value; - - this.shippingService.saveShippingDetails(this.headerid, shippingData).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; - } - }); } getAddressLabel(): string { @@ -417,50 +560,19 @@ export class ShippingComponent { getContactLabel(): string { let shipTo = this.shippingForm.get('shipTo')?.value; - if (shipTo === 'preparer') { + if (shipTo === 'preparer' && this.preparerContact) { return `${this.preparerContact.firstName} ${this.preparerContact.middleInitial} ${this.preparerContact.lastName}, ${this.preparerContact.email}, ${this.preparerContact.phone}`; } - if (shipTo === 'holder') { + if (shipTo === 'holder' && this.holderContact) { return `${this.holderContact.firstName} ${this.holderContact.middleInitial} ${this.holderContact.lastName}, ${this.holderContact.email}, ${this.holderContact.phone}`; } return ''; } - editAddressForm(): void { - this.showAddressForm = true; - let shipTo = this.shippingForm.get('shipTo')?.value; - - if (shipTo === 'preparer') { - this.shippingForm.get('address')?.patchValue(this.preparerAddress); - this.loadStates(this.preparerAddress.country); - } else if (shipTo === 'holder') { - this.shippingForm.get('address')?.patchValue(this.holderAddress); - this.loadStates(this.preparerAddress.country); - } - } - - cancelEditAddressForm(): void { - this.showAddressForm = false; - this.shippingForm.get('address')?.reset(); - } - - onShipToChange(event: any): void { - const shipTo = event.value; - this.showAddressForm = false; - this.showContactForm = false; - - if (shipTo === 'thirdParty') { - this.shippingForm.get('contact')?.reset(); - this.shippingForm.get('address')?.reset(); - this.showAddressForm = true; - this.showContactForm = true; - } - } - - private calculateDeliveryEstimate(): void { + calculateDeliveryEstimate(): void { const deliveryType = this.shippingForm.get('deliveryType')?.value; const deliveryTypeObj = this.deliveryTypes.find(dt => dt.value === deliveryType); const daysToDelivery: number = Number(deliveryTypeObj?.daysToDelivery) !== 0 ? Number(deliveryTypeObj?.daysToDelivery) : 3; @@ -511,4 +623,32 @@ export class ShippingComponent { this.deliveryEstimate = message; } + + selectContact(): void { + let shipTo = this.shippingForm.get('shipTo')?.value; + + if (shipTo === 'preparer') { + this.preparerContact = this.preparerContacts.find(pc => pc.defaultContact); + } else if (shipTo === 'holder') { + this.holderContact = this.holderContacts.find(hc => hc.defaultContact); + } + + const contacts = shipTo === 'preparer' ? this.preparerContacts : this.holderContacts; + + const dialogRef = this.dialog.open(ContactDialogComponent, { + width: '500px', + data: { contacts } + }); + + dialogRef.afterClosed().subscribe(selectedItem => { + if (selectedItem) { + const selectedContact = contacts.find(c => c.contactid === selectedItem.contactid); + if (shipTo === 'preparer') { + this.preparerContact = selectedContact; + } else { + this.holderContact = selectedContact; + } + } + }); + } } \ No newline at end of file diff --git a/src/app/core/models/carnet/shipping-contact.ts b/src/app/core/models/carnet/shipping-contact.ts index 97ee33d..44f028d 100644 --- a/src/app/core/models/carnet/shipping-contact.ts +++ b/src/app/core/models/carnet/shipping-contact.ts @@ -10,4 +10,6 @@ export interface ShippingContact { email: string; refNumber?: string | null; notes?: string | null; + defaultContact?: boolean; + isInactive?: boolean; } diff --git a/src/app/core/services/carnet/shipping.service.ts b/src/app/core/services/carnet/shipping.service.ts index 4a0a279..4143a56 100644 --- a/src/app/core/services/carnet/shipping.service.ts +++ b/src/app/core/services/carnet/shipping.service.ts @@ -4,6 +4,8 @@ import { HttpClient } from '@angular/common/http'; import { filter, map, Observable } from 'rxjs'; import { UserService } from '../common/user.service'; import { Shipping } from '../../models/carnet/shipping'; +import { ShippingContact } from '../../models/carnet/shipping-contact'; +import { ShippingAddress } from '../../models/carnet/shipping-address'; @Injectable({ providedIn: 'root' @@ -35,32 +37,28 @@ export class ShippingService { needsLostDocProtection: shippingDetails.LDIPROTECTION === 'Y' }; - if (shippingDetails.address) { - shippingData.address = { - addressid: shippingDetails.SHIPADDRESSID, - address1: shippingDetails.ADDRESS1, - address2: shippingDetails.ADDRESS2, - city: shippingDetails.CITY, - state: shippingDetails.STATE, - zip: shippingDetails.ZIP, - country: shippingDetails.COUNTRY - } + shippingData.address = { + addressid: shippingDetails.SHIPADDRESSID, + address1: shippingDetails.ADDRESS1, + address2: shippingDetails.ADDRESS2, + city: shippingDetails.CITY, + state: shippingDetails.STATE, + zip: shippingDetails.ZIP, + country: shippingDetails.COUNTRY } - if (shippingDetails.contact) { - shippingData.contact = { - contactid: shippingDetails.SHIPCONTACTID, - firstName: shippingDetails.FIRSTNAME, - lastName: shippingDetails.LASTNAME, - middleInitial: shippingDetails.MIDDLEINITIAL, - title: shippingDetails.TITLE, - phone: shippingDetails.PHONENO, - mobile: shippingDetails.MOBILENO, - fax: shippingDetails.FAXNO, - email: shippingDetails.EMAILADDRESS, - refNumber: shippingDetails.REFNO, - notes: shippingDetails.NOTES, - } + shippingData.contact = { + contactid: shippingDetails.SHIPCONTACTID, + firstName: shippingDetails.FIRSTNAME, + lastName: shippingDetails.LASTNAME, + middleInitial: shippingDetails.MIDDLEINITIAL, + title: shippingDetails.TITLE, + phone: shippingDetails.PHONENO, + mobile: shippingDetails.MOBILENO, + fax: shippingDetails.FAXNO, + email: shippingDetails.EMAILADDRESS, + refNumber: shippingDetails.REFNO, + notes: shippingDetails.NOTES, } return shippingData; @@ -104,4 +102,52 @@ export class ShippingService { return this.http.patch(`${this.apiUrl}/${this.apiDb}/UpdateShippingDetails`, shippingDetails); } + + getPreparerContactsById(): ShippingContact[] | any { + return this.http.get(`${this.apiUrl}/${this.apiDb}/GetPreparerContactsByClientid/${this.userService.getUserSpid()}/${this.userService.getUserClientid()}`).pipe( + map(response => this.mapToContacts(response))); + } + + getHolderContactsById(id: number): ShippingContact[] | any { + return this.http.get(`${this.apiUrl}/${this.apiDb}/GetHolderContacts/${this.userService.getUserSpid()}/${id}`).pipe( + map(response => this.mapToContacts(response))); + } + + private mapToContacts(data: any[]): ShippingContact[] { + return data.map(contact => ({ + contactid: contact.CLIENTCONTACTID, + defaultContact: contact.DEFCONTACTFLAG === 'Y', + firstName: contact.FIRSTNAME, + lastName: contact.LASTNAME, + title: contact.TITLE, + phone: contact.PHONENO, + mobile: contact.MOBILENO, + fax: contact.FAXNO || null, + email: contact.EMAILADDRESS, + middleInitial: contact.MIDDLEINITIAL || null, + isInactive: contact.INACTIVEFLAG === 'Y' || false + })); + } + + getPreparerAddress(): ShippingAddress[] | any { + return this.http.get(`${this.apiUrl}/${this.apiDb}/GetPreparerLocByClientid/${this.userService.getUserSpid()}/${this.userService.getUserClientid()}`).pipe( + map(response => this.mapToAddress(response))); + } + + getHolderAddressById(id: number): ShippingContact[] | any { + return this.http.get(`${this.apiUrl}/${this.apiDb}/GetHolderRecord/${this.userService.getUserSpid()}/${id}`).pipe( + map(response => this.mapToAddress(response))); + } + + private mapToAddress(data: any[]): ShippingAddress[] { + return data.map(address => ({ + addressid: address.SHIPADDRESSID, + address1: address.ADDRESS1, + address2: address.ADDRESS2, + city: address.CITY, + state: address.STATE, + zip: address.ZIP, + country: address.COUNTRY + })); + } }