Compare commits

..

1 Commits
main ... dev-v1

Author SHA1 Message Date
Kallesh B S
46c619c998 holder selection flow 2025-07-15 19:56:41 +05:30
123 changed files with 2182 additions and 6478 deletions

View File

@ -34,7 +34,6 @@
}
],
"styles": [
"@angular/material/prebuilt-themes/azure-blue.css",
"src/styles.scss"
],
"server": "src/main.server.ts",
@ -69,28 +68,6 @@
"with": "src/environments/environment.development.ts"
}
]
},
"dev-1": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment-dev-1.ts"
}
]
},
"dev-2": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment-dev-2.ts"
}
]
}
},
"defaultConfiguration": "production"
@ -100,9 +77,7 @@
"options": {
"allowedHosts": [
"localhost",
"client.alphaomegainfosys.com",
"client-1.alphaomegainfosys.com",
"client-2.alphaomegainfosys.com"
"client.alphaomegainfosys.com"
]
},
"configurations": {
@ -112,12 +87,6 @@
"development": {
"buildTarget": "client-app:build:development",
"port": 5173
},
"dev-1": {
"buildTarget": "client-app:build:dev-1"
},
"dev-2": {
"buildTarget": "client-app:build:dev-2"
}
},
"defaultConfiguration": "development"
@ -141,7 +110,6 @@
}
],
"styles": [
"@angular/material/prebuilt-themes/azure-blue.css",
"src/styles.scss"
]
}

11
package-lock.json generated
View File

@ -20,7 +20,6 @@
"@angular/router": "^20.0.0",
"@angular/ssr": "^20.0.4",
"chart.js": "^4.3.0",
"date-fns": "^4.1.0",
"express": "^5.1.0",
"ng2-charts": "^8.0.0",
"ngx-cookie-service": "^20.0.1",
@ -4465,16 +4464,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/date-fns": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/date-format": {
"version": "4.0.14",
"resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz",

View File

@ -33,7 +33,6 @@
"@angular/router": "^20.0.0",
"@angular/ssr": "^20.0.4",
"chart.js": "^4.3.0",
"date-fns": "^4.1.0",
"express": "^5.1.0",
"ng2-charts": "^8.0.0",
"ngx-cookie-service": "^20.0.1",

View File

@ -4,10 +4,10 @@ html {
color-scheme: light;
/* Primary palette variables */
--mat-sys-primary: light-dark(#607c7c, #afcccc);
--mat-sys-primary: light-dark(#486363, #afcccc);
--mat-sys-on-primary: light-dark(#ffffff, #193535);
--mat-sys-primary-container: light-dark(#ffffff, #304b4c);
--mat-sys-on-primary-container: light-dark(#607c7c, #cae8e8);
--mat-sys-primary-container: light-dark(#cae8e8, #304b4c);
--mat-sys-on-primary-container: light-dark(#022020, #cae8e8);
--mat-sys-inverse-primary: light-dark(#afcccc, #486363);
--mat-sys-primary-fixed: light-dark(#cae8e8, #cae8e8);
--mat-sys-primary-fixed-dim: light-dark(#afcccc, #afcccc);
@ -15,10 +15,10 @@ html {
--mat-sys-on-primary-fixed-variant: light-dark(#304b4c, #304b4c);
/* Secondary palette variables */
--mat-sys-secondary: light-dark(#607c7c, #b3cbcf);
--mat-sys-secondary: light-dark(#4c6266, #b3cbcf);
--mat-sys-on-secondary: light-dark(#ffffff, #1d3437);
--mat-sys-secondary-container: light-dark(#607c7c, #344a4e);
--mat-sys-on-secondary-container: light-dark(#ffffff, #cee7eb);
--mat-sys-secondary-container: light-dark(#cee7eb, #344a4e);
--mat-sys-on-secondary-container: light-dark(#071f22, #cee7eb);
--mat-sys-secondary-fixed: light-dark(#cee7eb, #cee7eb);
--mat-sys-secondary-fixed-dim: light-dark(#b3cbcf, #b3cbcf);
--mat-sys-on-secondary-fixed: light-dark(#071f22, #071f22);
@ -53,8 +53,7 @@ html {
--mat-sys-inverse-on-surface: light-dark(#f1f0f0, #2f3130);
--mat-sys-outline: light-dark(#727878, #8b9292);
--mat-sys-outline-variant: light-dark(#c1c8c7, #414848);
--mat-sys-neutral10: light-dark(#1a1c1c, #1a1c1c);
/* Variable used for the form field native select option text color */
--mat-sys-neutral10: light-dark(#1a1c1c, #1a1c1c); /* Variable used for the form field native select option text color */
/* Error palette variables */
--mat-sys-error: light-dark(#ba1a1a, #ffb4ab);
@ -65,22 +64,16 @@ html {
/* Neutral variant palette variables */
--mat-sys-surface-variant: light-dark(#dde4e3, #414848);
--mat-sys-on-surface-variant: light-dark(#414848, #c1c8c7);
--mat-sys-neutral-variant20: light-dark(#2b3232, #2b3232);
/* Variable used for the sidenav scrim (container background shadow when opened) */
--mat-sys-neutral-variant20: light-dark(#2b3232, #2b3232); /* Variable used for the sidenav scrim (container background shadow when opened) */
/* TYPOGRAPHY SYSTEM VARIABLES */
/* Typography variables. Only used in the different typescale system variables. */
--mat-sys-brand-font-family: Roboto;
/* The font-family to use for brand text. */
--mat-sys-plain-font-family: Roboto;
/* The font-family to use for plain text. */
--mat-sys-bold-font-weight: 700;
/* The font-weight to use for bold text. */
--mat-sys-medium-font-weight: 500;
/* The font-weight to use for medium text. */
--mat-sys-regular-font-weight: 400;
/* The font-weight to use for regular text. */
--mat-sys-brand-font-family: Roboto; /* The font-family to use for brand text. */
--mat-sys-plain-font-family: Roboto; /* The font-family to use for plain text. */
--mat-sys-bold-font-weight: 700; /* The font-weight to use for bold text. */
--mat-sys-medium-font-weight: 500; /* The font-weight to use for medium text. */
--mat-sys-regular-font-weight: 400; /* The font-weight to use for regular text. */
/* Typescale variables. */
/* Warning: Risk of reduced fidelity from using the composite typography tokens (ex. --mat-sys-body-large) since
@ -240,13 +233,4 @@ html {
--mat-sys-focus-state-layer-opacity: 0.12;
--mat-sys-hover-state-layer-opacity: 0.08;
--mat-sys-pressed-state-layer-opacity: 0.12;
/* CUSTOM BUTTON COLORS */
--custom-button-color: #ffffff;
--custom-button-background-color: #627f9a;
--custom-hover-color: #384857;
/* CUSTOM NAV COLORS */
--custom-nav-color: #ffffff;
--custom-nav-background-color: #607c7c;
}
}

View File

@ -1,13 +1,13 @@
/* Note: Color palettes are generated from primary: #003057, secondary: #18749c */
/* Note: Color palettes are generated from primary: #103454, secondary: #18749c */
html {
/* COLOR SYSTEM VARIABLES */
color-scheme: light;
/* Primary palette variables */
--mat-sys-primary: light-dark(#003057, #aac9f1);
--mat-sys-primary: light-dark(#416183, #aac9f1);
--mat-sys-on-primary: light-dark(#ffffff, #0d3252);
--mat-sys-primary-container: light-dark(#ffffff, #29496a);
--mat-sys-on-primary-container: light-dark(#003057, #d1e4ff);
--mat-sys-primary-container: light-dark(#d1e4ff, #29496a);
--mat-sys-on-primary-container: light-dark(#001d35, #d1e4ff);
--mat-sys-inverse-primary: light-dark(#aac9f1, #416183);
--mat-sys-primary-fixed: light-dark(#d1e4ff, #d1e4ff);
--mat-sys-primary-fixed-dim: light-dark(#aac9f1, #aac9f1);
@ -15,10 +15,10 @@ html {
--mat-sys-on-primary-fixed-variant: light-dark(#29496a, #29496a);
/* Secondary palette variables */
--mat-sys-secondary: light-dark(#003057, #84cffc);
--mat-sys-secondary: light-dark(#00658b, #84cffc);
--mat-sys-on-secondary: light-dark(#ffffff, #00344a);
--mat-sys-secondary-container: light-dark(#003057, #004c6a);
--mat-sys-on-secondary-container: light-dark(#ffffff, #c5e7ff);
--mat-sys-secondary-container: light-dark(#c5e7ff, #004c6a);
--mat-sys-on-secondary-container: light-dark(#001e2d, #c5e7ff);
--mat-sys-secondary-fixed: light-dark(#c5e7ff, #c5e7ff);
--mat-sys-secondary-fixed-dim: light-dark(#84cffc, #84cffc);
--mat-sys-on-secondary-fixed: light-dark(#001e2d, #001e2d);
@ -53,8 +53,7 @@ html {
--mat-sys-inverse-on-surface: light-dark(#f1f0f3, #2f3033);
--mat-sys-outline: light-dark(#73777e, #8d9198);
--mat-sys-outline-variant: light-dark(#c3c7ce, #43474e);
--mat-sys-neutral10: light-dark(#1a1c1e, #1a1c1e);
/* Variable used for the form field native select option text color */
--mat-sys-neutral10: light-dark(#1a1c1e, #1a1c1e); /* Variable used for the form field native select option text color */
/* Error palette variables */
--mat-sys-error: light-dark(#ba1a1a, #ffb4ab);
@ -65,22 +64,16 @@ html {
/* Neutral variant palette variables */
--mat-sys-surface-variant: light-dark(#dfe2eb, #43474e);
--mat-sys-on-surface-variant: light-dark(#43474e, #c3c7ce);
--mat-sys-neutral-variant20: light-dark(#2c3137, #2c3137);
/* Variable used for the sidenav scrim (container background shadow when opened) */
--mat-sys-neutral-variant20: light-dark(#2c3137, #2c3137); /* Variable used for the sidenav scrim (container background shadow when opened) */
/* TYPOGRAPHY SYSTEM VARIABLES */
/* Typography variables. Only used in the different typescale system variables. */
--mat-sys-brand-font-family: Roboto;
/* The font-family to use for brand text. */
--mat-sys-plain-font-family: Roboto;
/* The font-family to use for plain text. */
--mat-sys-bold-font-weight: 700;
/* The font-weight to use for bold text. */
--mat-sys-medium-font-weight: 500;
/* The font-weight to use for medium text. */
--mat-sys-regular-font-weight: 400;
/* The font-weight to use for regular text. */
--mat-sys-brand-font-family: Roboto; /* The font-family to use for brand text. */
--mat-sys-plain-font-family: Roboto; /* The font-family to use for plain text. */
--mat-sys-bold-font-weight: 700; /* The font-weight to use for bold text. */
--mat-sys-medium-font-weight: 500; /* The font-weight to use for medium text. */
--mat-sys-regular-font-weight: 400; /* The font-weight to use for regular text. */
/* Typescale variables. */
/* Warning: Risk of reduced fidelity from using the composite typography tokens (ex. --mat-sys-body-large) since
@ -240,13 +233,4 @@ html {
--mat-sys-focus-state-layer-opacity: 0.12;
--mat-sys-hover-state-layer-opacity: 0.08;
--mat-sys-pressed-state-layer-opacity: 0.12;
/* CUSTOM BUTTON COLORS */
--custom-button-color: #fff;
--custom-button-background-color: #FF6A00;
--custom-hover-color: #FFAC70;
/* CUSTOM NAV COLORS */
--custom-nav-color: #003057;
--custom-nav-background-color: #ffffff;
}
}

View File

@ -4,4 +4,4 @@
<router-outlet></router-outlet>
</main>
<app-footer [isUserLoggedIn]="isUserLoggedIn" ></app-footer>
<app-footer></app-footer>

View File

@ -6,7 +6,6 @@ import { provideClientHydration, withEventReplay } from '@angular/platform-brows
import { provideCharts, withDefaultRegisterables } from 'ng2-charts';
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { AuthInterceptor } from './core/interceptors/auth.interceptor';
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
export const appConfig: ApplicationConfig = {
providers: [
@ -17,6 +16,5 @@ export const appConfig: ApplicationConfig = {
provideHttpClient(withInterceptorsFromDi()),
provideCharts(withDefaultRegisterables()),
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { subscriptSizing: 'dynamic' } }
]
};

View File

@ -7,15 +7,6 @@ export const serverRoutes: ServerRoute[] = [
{ path: ':appId/usersettings', renderMode: RenderMode.Client },
{ path: ':appId/add-carnet', renderMode: RenderMode.Client },
{ path: ':appId/edit-carnet/:headerid', renderMode: RenderMode.Client },
{ path: ':appId/view-carnet/:headerid', renderMode: RenderMode.Client },
{ path: ':appId/view-extend-carnet/:headerid', renderMode: RenderMode.Client },
{ path: ':appId/duplicate-carnet/:headerid', renderMode: RenderMode.Client },
{ path: ':appId/extend-carnet/:headerid', renderMode: RenderMode.Client },
{ path: ':appId/additional-sets/:headerid', renderMode: RenderMode.Client },
{ path: ':appId/add-holder', renderMode: RenderMode.Client },
{ path: ':appId/edit-holder/:holderid', renderMode: RenderMode.Client },
{ path: ':appId/terms', renderMode: RenderMode.Client },
{ path: ':appId/checkout', renderMode: RenderMode.Client },
{
path: '**',
renderMode: RenderMode.Prerender

View File

@ -15,15 +15,8 @@ export const routes: Routes = [
{ path: 'usersettings', loadComponent: () => import('./user-settings/user-settings.component').then(m => m.UserSettingsComponent) },
{ path: 'add-carnet', loadComponent: () => import('./carnet/add/add-carnet.component').then(m => m.AddCarnetComponent) },
{ path: 'edit-carnet/:headerid', loadComponent: () => import('./carnet/edit/edit-carnet.component').then(m => m.EditCarnetComponent) },
{ path: 'add-holder', loadComponent: () => import('./holder/add/add-holder.component').then(m => m.AddHolderComponent) },
{ path: 'edit-holder/:holderid', loadComponent: () => import('./holder/edit/edit-holder.component').then(m => m.EditHolderComponent) },
{ path: 'view-carnet/:headerid', loadComponent: () => import('./carnet/view/view-carnet.component').then(m => m.ViewCarnetComponent) },
{ path: 'view-extend-carnet/:headerid', loadComponent: () => import('./carnet/view/view-carnet.component').then(m => m.ViewCarnetComponent) },
{ path: 'duplicate-carnet/:headerid', loadComponent: () => import('./carnet/edit/edit-carnet.component').then(m => m.EditCarnetComponent) },
{ path: 'extend-carnet/:headerid', loadComponent: () => import('./carnet/edit/edit-carnet.component').then(m => m.EditCarnetComponent) },
{ path: 'additional-sets/:headerid', loadComponent: () => import('./carnet/edit/edit-carnet.component').then(m => m.EditCarnetComponent) },
{ path: 'terms', loadComponent: () => import('./carnet/terms-conditions/terms-conditions.component').then(m => m.TermsConditionsComponent) },
{ path: 'checkout', loadComponent: () => import('./carnet/checkout/checkout.component').then(m => m.CheckoutComponent) },
{ path: 'add-holder', loadComponent: () => import('./holder/add/add.component').then(m => m.AddComponent) },
{ path: 'edit-holder/:holderid', loadComponent: () => import('./holder/edit/edit.component').then(m => m.EditComponent) },
{ path: '', redirectTo: 'home', pathMatch: 'full' }
],
canActivate: [AuthGuard, AppIdGuard]

View File

@ -7,7 +7,7 @@
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.applicationDetail"
[editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Application Name</ng-template>
<app-application [isEditMode]="isEditMode" (applicationCreated)="onApplicationDetailCreated($event)">
<app-application [isEditMode]="isEditMode" (headerIdCreated)="onApplicationDetailCreated($event)">
</app-application>
</mat-step>
@ -15,9 +15,17 @@
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.holderSelection"
[editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Holder Selection</ng-template>
<app-holder (completed)="onHolderSelectionSaved($event)" [headerid]="headerid"
[applicationName]="applicationName" (updated)="onHolderSelectionUpdated($event)">
</app-holder>
<!-- <app-holder (completed)="onHolderSelectionSaved($event)" [headerid]="headerid">
</app-holder> -->
<!-- <app-search [searchedHolder]="searchedHolder" (searchedHolderEmit)="onSearchedHolder($event)"
(selectHolderCompleted)="onHolderSelectionSaved($event)" [HOLDERID]="holderid"
[headerid]="headerid"></app-search> -->
<app-search
(selectHolderCompleted)="onHolderSelectionSaved($event)"
[headerid]="headerid"></app-search>
</mat-step>
<!-- Goods Section Step -->
@ -40,14 +48,8 @@
<!-- Shipping & Payment Step -->
<mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Shipping & Payment</ng-template>
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid"
[applicationName]="applicationName" [missingSectionMessage]="missingSectionMessage">
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid">
</app-shipping>
</mat-step>
<!-- Icon overrides. -->
<ng-template matStepperIcon="edit">
<mat-icon>done</mat-icon>
</ng-template>
</mat-stepper>
</div>

View File

@ -1,5 +1,5 @@
import { Component, inject, ViewChild } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { Component, inject } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { CommonModule } from '@angular/common';
import { StepperSelectionEvent } from '@angular/cdk/stepper';
@ -10,24 +10,25 @@ import { TravelPlanComponent } from '../travel-plan/travel-plan.component';
import { ShippingComponent } from '../shipping/shipping.component';
import { UserPreferencesService } from '../../core/services/user-preference.service';
import { UserPreferences } from '../../core/models/user-preference';
import { SearchComponent } from '../../holder/search/search.component';
import { CarnetStateStore } from '../../core/services/carnet/carnet-application.service';
@Component({
selector: 'app-add-carnet',
imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule,
ApplicationComponent, HolderComponent, GoodsComponent, TravelPlanComponent,
ApplicationComponent, SearchComponent, GoodsComponent, TravelPlanComponent,
ShippingComponent],
templateUrl: './add-carnet.component.html',
styleUrl: './add-carnet.component.scss'
})
export class AddCarnetComponent {
store = inject(CarnetStateStore);
currentStep = 0;
isLinear = true;
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = 'new';
isEditMode = false;
headerid: number = 0;
applicationName: string = '';
userPreferences: UserPreferences;
missingSectionMessage: string = '';
// Track completion of each step
stepsCompleted = {
@ -38,12 +39,6 @@ export class AddCarnetComponent {
shipping: false
};
@ViewChild(HolderComponent, { static: false })
private holderComponent!: HolderComponent;
@ViewChild(ShippingComponent, { static: false })
private shippingComponent!: ShippingComponent;
constructor(userPrefenceService: UserPreferencesService) {
this.userPreferences = userPrefenceService.getPreferences();
}
@ -52,74 +47,27 @@ export class AddCarnetComponent {
this.currentStep = event.selectedIndex;
}
onApplicationDetailCreated(data: { headerid: number, applicationName: string }): void {
this.headerid = data.headerid;
this.applicationName = data.applicationName;
onApplicationDetailCreated(data: { HEADERID: number, APPLICATION_NAME: string }): void {
this.store.headerid.set(data.HEADERID);
this.store.applicationName.set(data.APPLICATION_NAME);
this.headerid = data.HEADERID;
this.stepsCompleted.applicationDetail = true;
this.isLinear = false; // Disable linear mode after application detail is created
this.holderComponent.refreshHolder();
}
onHolderSelectionSaved(completed: boolean): void {
this.stepsCompleted.holderSelection = completed;
this.isAllSectionsCompleted();
}
onHolderSelectionUpdated(updated: boolean): void {
if (updated) {
this.shippingComponent?.refreshShippingData();
}
}
onGoodsSectionSaved(completed: boolean): void {
this.stepsCompleted.goodsSection = completed;
this.isAllSectionsCompleted();
}
onTravelPlanSaved(completed: boolean): void {
this.stepsCompleted.travelPlan = completed;
this.isAllSectionsCompleted();
}
onShippingSaved(completed: boolean): void {
this.stepsCompleted.shipping = completed;
this.isAllSectionsCompleted();
}
isAllSectionsCompleted(): void {
let allSectionsCompleted = this.stepsCompleted.applicationDetail
&& this.stepsCompleted.holderSelection && this.stepsCompleted.goodsSection
&& this.stepsCompleted.shipping && this.stepsCompleted.travelPlan;
if (allSectionsCompleted) {
this.missingSectionMessage = '';
}
else {
let message = 'Missing details in: ';
if (!this.stepsCompleted.applicationDetail) {
message += 'Application Name, ';
}
if (!this.stepsCompleted.holderSelection) {
message += 'Holder Selection, ';
}
if (!this.stepsCompleted.goodsSection) {
message += 'Goods Section, ';
}
if (!this.stepsCompleted.travelPlan) {
message += 'Travel Plan, ';
}
if (!this.stepsCompleted.shipping) {
message += 'Shipping & Payment, ';
}
// Remove trailing comma and space
this.missingSectionMessage = message.replace(/, $/, '');
}
}
}

View File

@ -15,13 +15,13 @@
Name is required
</mat-error>
<mat-error *ngIf="f['name'].errors?.['maxlength']">
Maximum 50 characters allowed
Maximum 100 characters allowed
</mat-error>
</mat-form-field>
</div>
<div class="form-actions">
<button mat-raised-button color="primary" type="submit" *ngIf="!isViewMode"
<button mat-raised-button color="primary" type="submit"
[disabled]="applicationDetailsForm.invalid || disableSaveButton ">
Save
</button>

View File

@ -11,10 +11,6 @@
background: none;
box-shadow: none;
mat-card-content {
padding: inherit 0;
}
.loading-shade {
position: absolute;
top: 0;

View File

@ -17,11 +17,11 @@ import { ActivatedRoute } from '@angular/router';
})
export class ApplicationComponent {
@Input() isEditMode = false;
@Input() isViewMode = false;
@Input() headerid: number = 0;
@Input() applicationName: string = '';
@Output() applicationCreated = new EventEmitter<{ headerid: number, applicationName: string }>();
// @Output() headerIdCreated = new EventEmitter<number>();
@Output() headerIdCreated = new EventEmitter<{ HEADERID: number, APPLICATION_NAME: string }>();
applicationDetailsForm: FormGroup;
isLoading = false;
@ -40,6 +40,8 @@ export class ApplicationComponent {
}
ngOnInit(): void {
this.applicationName = this.route.snapshot.queryParamMap.get('applicationname') || '';
// Patch edit form data
if (this.applicationName) {
this.patchFormData();
@ -68,7 +70,7 @@ export class ApplicationComponent {
createForm(): FormGroup {
return this.fb.group({
name: ['', [Validators.required, Validators.maxLength(50)]],
name: ['', [Validators.required, Validators.maxLength(100)]],
});
}
@ -77,7 +79,7 @@ export class ApplicationComponent {
name: this.applicationName,
});
if (this.isEditMode || this.isViewMode) {
if (this.isEditMode) {
this.applicationDetailsForm.get('name')?.disable();
this.disableSaveButton = true;
}
@ -101,7 +103,7 @@ export class ApplicationComponent {
this.applicationDetailService.createApplicationDetails(applicationDetailData).subscribe({
next: (applicationData: any) => {
this.notificationService.showSuccess(`Application details added successfully`);
this.applicationCreated.emit({ headerid: +applicationData.HEADERID, applicationName: applicationDetailData.name });
this.headerIdCreated.emit({ HEADERID: +applicationData.HEADERID, APPLICATION_NAME: applicationDetailData.name });
this.applicationDetailsForm.get('name')?.disable();
this.disableSaveButton = true;
this.isLoading = false;

View File

@ -1,61 +0,0 @@
<div class="checkout-container">
<mat-card class="card-body" appearance="outlined">
<mat-card-header>
<mat-card-title>Checkout</mat-card-title>
</mat-card-header>
<mat-card-content>
<!-- Carnet Summary -->
<div class="order-summary">
<div *ngIf="estimatedFees.basicFee" class="summary-item">
<span>Basic fee: </span>
<span>{{estimatedFees.basicFee | currency}}</span>
</div>
<div *ngIf="estimatedFees.counterFoilFee" class="summary-item">
<span>Counterfoil fee: </span>
<span>{{estimatedFees.counterFoilFee | currency}}</span>
</div>
<div *ngIf="estimatedFees.continuationSheetFee" class="summary-item">
<span>Continuation sheet fee: </span>
<span>{{estimatedFees.continuationSheetFee | currency}}</span>
</div>
<div *ngIf="estimatedFees.expeditedFee" class="summary-item">
<span>Expedited fee: </span>
<span>{{estimatedFees.expeditedFee | currency}}</span>
</div>
<div *ngIf="estimatedFees.shippingFee" class="summary-item">
<span>Shipping fee: </span>
<span>{{estimatedFees.shippingFee | currency}}</span>
</div>
<div *ngIf="estimatedFees.bondPremium" class="summary-item">
<span>{{bondPremiumLabel}}: </span>
<span>{{estimatedFees.bondPremium | currency}}</span>
</div>
<div *ngIf="estimatedFees.cargoPremium" class="summary-item">
<span>Cargo Premium: </span>
<span>{{estimatedFees.cargoPremium | currency}}</span>
</div>
<div *ngIf="estimatedFees.ldiPremium" class="summary-item">
<span>LDI Premium: </span>
<span>{{estimatedFees.ldiPremium | currency}}</span>
</div>
<div class="summary-item total">
<strong>Total:</strong>
<strong>{{ orderTotal | currency }}</strong>
</div>
</div>
<div class="paypal-container">
<div #paypalcontrol></div>
</div>
</mat-card-content>
</mat-card>
</div>

View File

@ -1,36 +0,0 @@
.checkout-container {
max-width: 500px;
margin: 0 auto;
padding: 0 1rem;
.mat-mdc-card-header {
justify-content: center;
padding: 1rem;
border-bottom: 1px solid #ddd;
margin-bottom: 0.5rem;
}
mat-card-title {
color: var(--mat-sys-primary);
font-weight: 500;
}
.order-summary {
.summary-item {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.summary-item.total {
border-top: 1px solid #ddd;
padding-top: 0.5rem;
margin-top: 0.5rem;
font-size: 1.1rem;
}
}
.paypal-container {
margin-top: 2rem;
}
}

View File

@ -1,207 +0,0 @@
import { Component, ElementRef, inject, ViewChild } from '@angular/core';
import { PaymentService } from '../../core/services/carnet/payment.service';
import { CommonModule } from '@angular/common';
import { Fees } from '../../core/models/carnet/fee';
import { CarnetService } from '../../core/services/carnet/carnet.service';
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
import { NavigationService } from '../../core/services/common/navigation.service';
import { NotificationService } from '../../core/services/common/notification.service';
import { StorageService } from '../../core/services/common/storage.service';
import { finalize } from 'rxjs';
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { PaymentDetail } from '../../core/models/carnet/payment-details';
// This is necessary to tell TypeScript that a global 'paypal' object exists.
declare var paypal: any;
@Component({
selector: 'app-checkout',
imports: [CommonModule, AngularMaterialModule],
templateUrl: './checkout.component.html',
styleUrl: './checkout.component.scss'
})
export class CheckoutComponent {
paymentCompleted: boolean = false;
paymentError: boolean = false;
isLoading: boolean = false;
paymentDetails: any = null;
currentApplicationDetails: { headerid: number, applicationName: string, applicationType: string } | null = null;
changeInProgress: boolean = false;
estimatedFees: Fees = {};
bondPremiumLabel: string = 'Bond Premium';
@ViewChild('paypalcontrol', { static: true }) paypalElement!: ElementRef;
orderDetails: PaymentDetail = {};
orderTotal = 0.00;
currency = 'USD';
private paymentService = inject(PaymentService);
private notificationService = inject(NotificationService);
private errorHandler = inject(ApiErrorHandlerService);
private storageService = inject(StorageService);
private navigationService = inject(NavigationService);
private carnetService = inject(CarnetService);
constructor() {
this.currentApplicationDetails = this.storageService.get<{ headerid: number, applicationName: string, applicationType: string }>('currentapplication')
this.orderDetails.headerid = this.currentApplicationDetails?.headerid;
this.orderDetails.applicationName = this.currentApplicationDetails?.applicationName;
this.orderDetails.description = ` 'Carnet Application - ${this.currentApplicationDetails?.headerid} - ${this.currentApplicationDetails?.applicationName}`
}
ngOnInit(): void {
if (this.currentApplicationDetails?.headerid) {
this.carnetService.getEstimatedFees(this.currentApplicationDetails?.headerid).subscribe({
next: (data: Fees) => {
if (data) {
this.estimatedFees = data;
if (this.estimatedFees.securityType === 'B') {
this.bondPremiumLabel = 'Bond Premium';
} else if (this.estimatedFees.securityType === 'C') {
this.bondPremiumLabel = 'Cash Deposit';
} else if (this.estimatedFees.securityType === 'G') {
this.bondPremiumLabel = 'Refundable claim deposit';
}
this.calculateTotal();
}
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to get estimated fees');
this.notificationService.showError(errorMessage);
console.error('Error getting estimated fees:', error);
}
});
}
}
ngAfterViewInit(): void {
this.initConfig();
}
initConfig(): void {
paypal
.Buttons({
style: {
layout: 'vertical', // or 'horizontal'
color: 'gold',
shape: 'pill',
label: 'pay',
height: 35 // Height in pixels (default is ~35-45px)
// 'paypal' Standard PayPal button [defoult]
// 'checkout' "Checkout with PayPal"
// 'buynow' "Buy Now with PayPal"
// 'pay' "Pay with PayPal"
// 'installment' Displays installment information
// 'subscribe' For subscriptions
// 'donate' For donations
// pay : Pay with [PayPal]
// buynow : [PayPal] Buy Now
// Error: Expected style.height to be between 25px and 55px - got 10px
// width width usually doesnt need to be set or must be reasonable.
},
// 1. Call your server to set up the transaction
createOrder: async (data: any, actions: any) => {
try {
// Call the createOrder method from your service
const orderId = await this.paymentService.createOrder(this.orderDetails);
this.orderDetails.orderId = orderId;
return orderId;
} catch (error) {
this.notificationService.showError('Failed to initiate the payment');
console.error('Error creating order:', error);
return;
}
},
// 2. Call your server to finalize the transaction
onApprove: async (data: any, actions: any) => {
try {
// data.orderID is the ID of the transaction from PayPal
await this.paymentService.completePayment(this.orderDetails);
// You can now redirect the user or update the UI
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"]);
},
error: (error) => {
this.notificationService.showError('Failed to submit application');
console.error('Error submitting the application', error);
}
});
} catch (error) {
this.notificationService.showError('Failed to complete the payment');
console.error('Error completing the payment:', error);
return;
}
},
// 3. Handle errors
onError: (error: any) => {
this.notificationService.showError('Payment transaction failed');
this.orderDetails.paymentStatus = 'Failed';
this.orderDetails.paymentErrorDetails = JSON.stringify(error);
this.paymentService.logTransactionDetails(this.orderDetails);
console.error('An error occurred during the transaction:', error);
},
// 4. Handle cancellation
onCancel: (data: any) => {
this.orderDetails.paymentStatus = 'Cancelled';
if (this.orderDetails.orderId) {
this.paymentService.logTransactionDetails(this.orderDetails);
}
this.onCancel();
}
}).render(this.paypalElement.nativeElement)
.catch((error: any) => {
console.error('Failed to render PayPal Buttons:', error);
});
}
calculateTotal(): void {
const excludedProperties = ['securityAmount', 'insuredValue', 'securityType'];
const validFees = Object.entries(this.estimatedFees)
.filter(([key]) => !excludedProperties.includes(key))
.map(([, value]) => value)
.filter((fee): fee is number => typeof fee === 'number');
this.orderTotal = validFees.reduce((total, fee) => total + fee, 0);
this.orderDetails.price = this.orderTotal?.toString() ?? '0.00';
}
onCancel(): void {
this.storageService.removeItem('currentapplication');
let route: string = 'edit-carnet';
if (this.currentApplicationDetails?.applicationType === 'duplicate') {
route = 'duplicate-carnet';
}
else if (this.currentApplicationDetails?.applicationType === 'extend') {
route = 'extend-carnet';
} else if (this.currentApplicationDetails?.applicationType === 'additional') {
route = 'additional-sets';
}
this.navigationService.navigate([route, this.currentApplicationDetails?.headerid],
{
state: { isEditMode: true },
queryParams: {
applicationname: this.currentApplicationDetails?.applicationName,
return: 'shipping'
}
})
}
}

View File

@ -3,61 +3,45 @@
<mat-stepper orientation="vertical" [linear]="isLinear" (selectionChange)="onStepChange($event)"
[selectedIndex]="currentStep">
<!-- Replacement Application Step -->
<mat-step *ngIf="applicationType === 'extend'" [completed]="stepsCompleted.extension"
[editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Extension Application</ng-template>
<app-replacement-set (completed)="onExtensionApplicationSaved($event)" [headerid]="headerid"
[isEditMode]="isEditMode && applicationType === 'extend'">
</app-replacement-set>
</mat-step>
<!-- Application Name Step -->
<mat-step [completed]="stepsCompleted.applicationDetail" [editable]="stepsCompleted.applicationDetail">
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.applicationDetail"
[editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Application Name</ng-template>
<app-application [isEditMode]="isEditMode" [applicationName]="applicationName">
<app-application [isEditMode]="isEditMode">
</app-application>
</mat-step>
<!-- Holder Selection Step -->
<mat-step [completed]="stepsCompleted.holderSelection" [editable]="stepsCompleted.applicationDetail">
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.holderSelection"
[editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Holder Selection</ng-template>
<app-holder (completed)="onHolderSelectionSaved($event)" [headerid]="headerid"
[isViewMode]="applicationType !== 'edit'" [applicationName]="applicationName"
(updated)="onHolderSelectionUpdated($event)">
</app-holder>
<!-- <app-holder (completed)="onHolderSelectionSaved($event)" [headerid]="headerid">
</app-holder> -->
<app-search (selectHolderCompleted)="onHolderSelectionSaved($event)" [headerid]="headerid"></app-search>
</mat-step>
<!-- Goods Section Step -->
<mat-step [completed]="stepsCompleted.goodsSection" [editable]="stepsCompleted.applicationDetail">
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.goodsSection"
[editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Goods Section</ng-template>
<app-goods (completed)="onGoodsSectionSaved($event)" [headerid]="headerid"
[isEditMode]="isEditMode && applicationType === 'edit'" [isViewMode]="applicationType !== 'edit'"
<app-goods (completed)="onGoodsSectionSaved($event)" [headerid]="headerid" [isEditMode]="isEditMode"
[userPreferences]="userPreferences">
</app-goods>
</mat-step>
<!-- Travel Plan Step -->
<mat-step [completed]="stepsCompleted.travelPlan" [editable]="stepsCompleted.applicationDetail">
<mat-step *ngIf="applicationType === 'new' || applicationType === 'extend' || applicationType === 'additional'"
[completed]="stepsCompleted.travelPlan" [editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Travel Plan</ng-template>
<app-travel-plan (completed)="onTravelPlanSaved($event)" [headerid]="headerid"
[applicationType]="applicationType" (needsInsurance)="onNeedsInsurance($event)"
[isViewMode]="applicationType !== 'edit' && applicationType !== 'additional' && applicationType !== 'extend'">
<app-travel-plan (completed)="onTravelPlanSaved($event)" [headerid]="headerid">
</app-travel-plan>
</mat-step>
<!-- Shipping & Payment Step -->
<mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Shipping & Payment</ng-template>
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid" [isEditMode]="isEditMode"
[applicationType]="applicationType" [applicationName]="applicationName"
[missingSectionMessage]="missingSectionMessage">
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid">
</app-shipping>
</mat-step>
<!-- Icon overrides. -->
<ng-template matStepperIcon="edit">
<mat-icon>done</mat-icon>
</ng-template>
</mat-stepper>
</div>

View File

@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
import { Component, inject, ViewChild } from '@angular/core';
import { Component, inject } from '@angular/core';
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { ReactiveFormsModule } from '@angular/forms';
import { ApplicationComponent } from '../application/application.component';
@ -11,28 +11,22 @@ import { ShippingComponent } from '../shipping/shipping.component';
import { TravelPlanComponent } from '../travel-plan/travel-plan.component';
import { UserPreferences } from '../../core/models/user-preference';
import { UserPreferencesService } from '../../core/services/user-preference.service';
import { ReplacementSetComponent } from '../replacement-set/replacement-set.component';
import { SearchComponent } from '../../holder/search/search.component';
@Component({
selector: 'app-edit-carnet',
imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule,
ApplicationComponent, HolderComponent, GoodsComponent, TravelPlanComponent, ShippingComponent, ReplacementSetComponent],
ApplicationComponent, SearchComponent, GoodsComponent, TravelPlanComponent, ShippingComponent],
templateUrl: './edit-carnet.component.html',
styleUrl: './edit-carnet.component.scss'
})
export class EditCarnetComponent {
currentStep = 0;
isLinear = false;
applicationType: 'new' | 'edit' | 'additional' | 'duplicate' | 'extend' | null = 'edit';
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = 'new';
isEditMode = true; // Set to true for edit mode
headerid: number = 0;
userPreferences: UserPreferences;
applicationName: string = '';
missingSectionMessage: string = '';
@ViewChild(ShippingComponent, { static: false })
private shippingComponent!: ShippingComponent;
// Track completion of each step
stepsCompleted = {
@ -40,8 +34,7 @@ export class EditCarnetComponent {
holderSelection: false,
goodsSection: false,
travelPlan: false,
shipping: false,
extension: false
shipping: false
};
private route = inject(ActivatedRoute);
@ -53,35 +46,6 @@ export class EditCarnetComponent {
ngOnInit(): void {
const idParam = this.route.snapshot.paramMap.get('headerid');
this.headerid = idParam ? parseInt(idParam, 10) : 0;
this.applicationName = this.route.snapshot.queryParamMap.get('applicationname') || '';
let returnTo = this.route.snapshot.queryParamMap.get('return');
// set application type based on the route url
const urlSegments = this.route.snapshot.url.map(segment => segment.path);
if (urlSegments.includes('duplicate-carnet') || urlSegments.includes('process-duplicate-carnet')) {
this.applicationType = 'duplicate';
} else if (urlSegments.includes('extend-carnet') || urlSegments.includes('process-extend-carnet')) {
this.applicationType = 'extend';
}
else if (urlSegments.includes('additional-sets') || urlSegments.includes('process-additional-sets')) {
this.applicationType = 'additional';
} else {
this.applicationType = 'edit';
}
if (returnTo === 'holder') {
this.currentStep = 1;
} else if (returnTo === 'shipping' && this.applicationType === 'extend') {
this.currentStep = 5;
} else if (returnTo === 'shipping') {
this.currentStep = 4;
} else if (this.applicationType === 'additional') {
this.currentStep = 3;
} else if (this.applicationType === 'duplicate') {
this.currentStep = 4;
}
}
onStepChange(event: StepperSelectionEvent): void {
@ -90,79 +54,17 @@ export class EditCarnetComponent {
onHolderSelectionSaved(completed: boolean): void {
this.stepsCompleted.holderSelection = completed;
this.isAllSectionsCompleted();
}
onGoodsSectionSaved(completed: boolean): void {
this.stepsCompleted.goodsSection = completed;
this.isAllSectionsCompleted();
}
onHolderSelectionUpdated(updated: boolean): void {
if (updated) {
this.shippingComponent?.refreshShippingData();
}
this.isAllSectionsCompleted();
}
onTravelPlanSaved(completed: boolean): void {
this.stepsCompleted.travelPlan = completed;
this.isAllSectionsCompleted();
}
onShippingSaved(completed: boolean): void {
this.stepsCompleted.shipping = completed;
this.isAllSectionsCompleted();
}
onNeedsInsurance(data: boolean): void {
this.shippingComponent?.refreshInsuranceData(data);
}
onExtensionApplicationSaved(completed: boolean): void {
this.stepsCompleted.extension = completed;
this.isAllSectionsCompleted();
}
isAllSectionsCompleted() {
let allSectionsCompleted = this.stepsCompleted.applicationDetail
&& this.stepsCompleted.holderSelection && this.stepsCompleted.goodsSection
&& this.stepsCompleted.shipping && this.stepsCompleted.travelPlan
&& (this.applicationType !== 'extend' || this.stepsCompleted.extension);
if (allSectionsCompleted) {
this.missingSectionMessage = '';
}
else {
let message = 'Missing details in: ';
if(this.applicationType === 'extend' && !this.stepsCompleted.extension) {
message += 'Extension Application, ';
}
if (!this.stepsCompleted.applicationDetail) {
message += 'Application Name, ';
}
if (!this.stepsCompleted.holderSelection) {
message += 'Holder Selection, ';
}
if (!this.stepsCompleted.goodsSection) {
message += 'Goods Section, ';
}
if (!this.stepsCompleted.travelPlan) {
message += 'Travel Plan, ';
}
if (!this.stepsCompleted.shipping) {
message += 'Shipping & Payment, ';
}
// Remove trailing comma and space
this.missingSectionMessage = message.replace(/, $/, '');
}
}
}

View File

@ -16,16 +16,11 @@
</div>
<div class="form-section">
<mat-radio-group formControlName="roadVehiclesUsed" class="radio-group">
<mat-label class="question">Are Road vehicle included in your General List?</mat-label>
<mat-radio-button value="Y" class="radio-button">Yes</mat-radio-button>
<mat-radio-button value="N" class="radio-button">No</mat-radio-button>
</mat-radio-group>
<mat-radio-group formControlName="horseUsed" class="radio-group">
<mat-label class="question">Are Horse included in your General List?</mat-label>
<mat-radio-button value="Y" class="radio-button">Yes</mat-radio-button>
<mat-radio-button value="N" class="radio-button">No</mat-radio-button>
</mat-radio-group>
<div class="checkbox-group">
<mat-checkbox labelPosition="before" formControlName="roadVehiclesUsed">Road Vehicles
used?</mat-checkbox>
<mat-checkbox labelPosition="before" formControlName="horseUsed">Horse used?</mat-checkbox>
</div>
</div>
<!-- Authorized Representatives Section -->
@ -43,16 +38,12 @@
<div class="table-actions">
<h3>Goods Items</h3>
<div class="actions">
<button mat-raised-button color="primary" type="button" *ngIf="!isViewMode" (click)="addNewItem()">
<button mat-raised-button color="primary" type="button" (click)="addNewItem()">
<mat-icon>add</mat-icon> Add Item
</button>
<button mat-raised-button color="primary" type="button" *ngIf="!isViewMode" (click)="deleteAllItems()">
<mat-icon>delete</mat-icon> Delete All
</button>
<div class="upload-section">
<input type="file" accept=".xlsx,.xls,.csv" (change)="fileUpload($event)" hidden #fileInput>
<button mat-raised-button type="button" *ngIf="!isViewMode" (click)="fileInput.click()"
[disabled]="isProcessing">
<button mat-raised-button type="button" (click)="fileInput.click()" [disabled]="isProcessing">
<mat-icon>upload</mat-icon> Upload
</button>
</div>
@ -86,7 +77,7 @@
<!-- Weight Column -->
<ng-container matColumnDef="weight">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Weight</th>
<td mat-cell *matCellDef="let item">{{ item.weight | number:'1.2-2' }}</td>
<td mat-cell *matCellDef="let item">{{ formatDecimalDisplay(item.weight, 4) }}</td>
</ng-container>
<!-- Unit of Measure Column -->
@ -111,12 +102,10 @@
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>
<td mat-cell *matCellDef="let item; let i = index">
<button mat-icon-button color="primary" *ngIf="!isViewMode" (click)="editItem(item)"
matTooltip="Edit">
<button mat-icon-button color="primary" (click)="editItem(item)" matTooltip="Edit">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button color="warn" *ngIf="!isViewMode" (click)="deleteItem(item)"
matTooltip="Delete">
<button mat-icon-button color="warn" (click)="deleteItem(item)" matTooltip="Delete">
<mat-icon>delete</mat-icon>
</button>
</td>
@ -133,8 +122,8 @@
</tr>
</table>
<mat-paginator *ngIf="dataSource.data.length > userPreferences.pageSize!" [length]="dataSource.data.length"
[pageSizeOptions]="[userPreferences.pageSize!]" [hidePageSize]="true" showFirstLastButtons></mat-paginator>
<mat-paginator [length]="dataSource.data.length" [pageSizeOptions]="[userPreferences.pageSize!]"
[hidePageSize]="true" showFirstLastButtons></mat-paginator>
</div>
<!-- Item Form -->
@ -230,26 +219,16 @@
</div>
<div class="form-actions">
<button mat-raised-button color="primary" type="submit" *ngIf="!isViewMode"
[disabled]="itemForm.invalid || changeInProgress">
<button mat-button type="button" (click)="cancelEdit()">Cancel</button>
<button mat-raised-button color="primary" type="submit" [disabled]="itemForm.invalid">
Save
</button>
<button mat-button type="button" (click)="cancelEdit()">Cancel</button>
</div>
</form>
</div>
<div *ngIf="!showItemForm" class="form-actions">
<!-- Totals -->
<div class="totals-section">
<div class="total-item" *ngIf="totalGoodsValue > 0">
<span>Total Goods Value:</span>
<span class="total-value">{{totalGoodsValue | currency}}</span>
</div>
</div>
<button mat-raised-button color="primary" (click)="onSubmit()" *ngIf="!isViewMode"
[disabled]="goodsForm.invalid || changeInProgress">
<button mat-raised-button color="primary" (click)="onSubmit()" [disabled]="goodsForm.invalid">
Save
</button>
</div>

View File

@ -1,7 +1,8 @@
.goods-container {
padding: 16px;
.form-section {
margin-bottom: 0.75rem;
margin-bottom: 1rem;
}
.checkbox-group {
@ -20,22 +21,6 @@
}
}
.radio-group {
display: flex;
gap: 16px;
mat-label {
margin-top: 5px;
color: var(--mat-checkbox-label-text-color, var(--mat-sys-on-surface));
font-family: var(--mat-checkbox-label-text-font, var(--mat-sys-body-medium-font));
line-height: var(--mat-checkbox-label-text-line-height, var(--mat-sys-body-medium-line-height));
font-size: var(--mat-checkbox-label-text-size, var(--mat-sys-body-medium-size));
letter-spacing: var(--mat-checkbox-label-text-tracking, var(--mat-sys-body-medium-tracking));
font-weight: var(--mat-checkbox-label-text-weight, var(--mat-sys-body-medium-weight));
}
}
.full-width {
width: 100%;
}
@ -44,14 +29,14 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
margin-bottom: 16px;
h3 {
margin: 0;
color: var(--mat-sys-primary);
font-weight: 500;
}
.actions {
display: flex;
gap: 16px;
@ -75,6 +60,7 @@
.table-container {
position: relative;
overflow-x: auto;
margin-bottom: 16px;
.loading-shade {
position: absolute;
@ -153,7 +139,6 @@
display: flex;
gap: 16px;
margin-bottom: 16px;
align-items: start;
mat-form-field {
flex: 1;
@ -179,23 +164,6 @@
.form-actions {
display: flex;
justify-content: flex-end;
margin-top: 0.9rem;
justify-content: space-between;
.totals-section {
display: flex;
gap: 24px;
font-size: 0.875rem;
.total-item {
display: flex;
gap: 8px;
align-items: center;
.total-value {
font-weight: 500;
}
}
}
margin-top: 24px;
}
}

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 { finalize, Subject, takeUntil } from 'rxjs';
import { Subject, takeUntil } from 'rxjs';
import { Goods, GoodsItem } from '../../core/models/carnet/goods';
@Component({
@ -28,20 +28,12 @@ import { Goods, GoodsItem } from '../../core/models/carnet/goods';
})
export class GoodsComponent {
@Input() headerid: number = 0;
@Input() isViewMode = false;
@Input() userPreferences: UserPreferences = {};
@Input() isEditMode: boolean = false;
@Output() completed = new EventEmitter<boolean>();
@ViewChild(MatPaginator, { static: false })
set paginator(value: MatPaginator) {
this.dataSource.paginator = value;
}
@ViewChild(MatSort, { static: false })
set sort(value: MatSort) {
this.dataSource.sort = value;
}
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
// Table configuration
displayedColumns: string[] = ['itemNumber', 'description', 'pieces', 'weight', 'unitOfMeasure', 'value', 'countryOfOrigin', 'actions'];
@ -55,11 +47,9 @@ export class GoodsComponent {
isEditing = false;
currentItem: GoodsItem | null = null;
isLoading = false;
changeInProgress = false;
showItemForm = false;
fileToUpload: File | null = null;
isProcessing = false;
totalGoodsValue: number = 0;
// Data
countries: Country[] = [];
@ -80,8 +70,8 @@ export class GoodsComponent {
commercialSample: [false],
professionalEquipment: [false],
exhibitionsFair: [false],
roadVehiclesUsed: [''],
horseUsed: [''],
roadVehiclesUsed: [false],
horseUsed: [false],
authorizedRepresentatives: ['', Validators.required]
}, { validator: this.atLeastOneRequired }
);
@ -94,7 +84,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: ['US', Validators.required]
countryOfOrigin: ['', Validators.required]
});
}
@ -108,48 +98,45 @@ export class GoodsComponent {
this.loadUnitOfMeasures();
if (this.headerid > 0) {
this.isLoading = true;
this.goodsService.getGoodDetailsByHeaderId(this.headerid).pipe(finalize(() => {
this.isLoading = false;
})).subscribe({
this.goodsService.getGoodDetailsByHeaderId(this.headerid).subscribe({
next: (goodsDetail: Goods) => {
if (goodsDetail) {
this.patchFormData(goodsDetail);
this.completed.emit((this.goodsForm.valid || this.goodsForm.disabled) && 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);
}
});
this.loadGoodsItems();
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
}
}
loadGoodsItems(): void {
this.isLoading = true;
this.goodsService.getGoodsItemsByHeaderId(this.headerid).pipe(finalize(() => {
this.isLoading = false;
})).subscribe({
this.goodsService.getGoodsItemsByHeaderId(this.headerid).subscribe({
next: (items: GoodsItem[]) => {
this.dataSource.data = items;
this.totalGoodsValue = items.reduce((total, item) => total + Number(item.value || 0), 0);
this.completed.emit((this.goodsForm.valid || this.goodsForm.disabled) && this.dataSource.data.length > 0);
this.isLoading = false;
},
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()
this.commonService.getCountries(0)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (countries) => {
@ -157,6 +144,7 @@ export class GoodsComponent {
},
error: (error) => {
console.error('Failed to load countries', error);
this.isLoading = false;
}
});
}
@ -167,9 +155,11 @@ 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;
}
});
}
@ -178,15 +168,11 @@ export class GoodsComponent {
this.goodsForm.patchValue({
commercialSample: data.commercialSample || false,
professionalEquipment: data.professionalEquipment || false,
exhibitionsFair: data.exhibitionsFair || '',
roadVehiclesUsed: data.roadVehiclesUsed || '',
exhibitionsFair: data.exhibitionsFair || false,
roadVehiclesUsed: data.roadVehiclesUsed || false,
horseUsed: data.horseUsed || false,
authorizedRepresentatives: data.authorizedRepresentatives || ''
});
if (this.isViewMode) {
this.goodsForm.disable();
}
}
// Add new item to the table
@ -197,10 +183,8 @@ export class GoodsComponent {
this.itemForm.reset({
pieces: 0,
weight: 0,
value: 0,
countryOfOrigin: 'US',
value: 0
});
this.itemForm.markAsUntouched();
}
// Edit existing item
@ -234,19 +218,16 @@ 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.pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
saveObservable.subscribe({
next: () => {
this.notificationService.showSuccess(`Goods ${this.isEditing ? 'updated' : 'added'} successfully`);
this.loadGoodsItems();
this.cancelEdit();
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, `Failed to ${this.isEditing ? 'update' : 'add'} goods items`);
@ -287,56 +268,18 @@ export class GoodsComponent {
});
}
// Delete all items with confirmation
deleteAllItems(): void {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
width: '350px',
data: {
title: 'Confirm Delete',
message: 'Are you sure you want to delete all the items?',
confirmText: 'Delete',
cancelText: 'Cancel'
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.changeInProgress = true;
this.goodsService.deleteAllGoodsItems(this.headerid).pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: () => {
this.notificationService.showSuccess('All items deleted successfully');
this.loadGoodsItems();
this.cancelEdit();
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to delete all items');
this.notificationService.showError(errorMessage);
console.error('Error deleting all items:', error);
}
});
}
});
}
// Handle file selection for upload
fileUpload(event: any): void {
const fileInput = event.target as HTMLInputElement;
const file: File | null = fileInput?.files ? fileInput.files[0] : null;
if (!file) {
return;
}
const file: File = event.target.files[0];
this.processExcelFile(file)
.then(() => {
fileInput.value = '';
.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');
fileInput.value = '';
});
}
@ -374,14 +317,12 @@ export class GoodsComponent {
// item.unitOfMeasure = this.getUnitOfMeasureValue(item.unitOfMeasure);
// });
this.changeInProgress = true;
this.goodsService.addGoodsItem(this.headerid, items).pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
this.goodsService.addGoodsItem(this.headerid, items).subscribe({
next: () => {
this.notificationService.showSuccess(`Goods uploaded successfully`);
this.loadGoodsItems();
this.cancelEdit();
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
this.fileToUpload = null;
},
error: (error) => {
@ -424,20 +365,15 @@ export class GoodsComponent {
// Parse Excel data into our format
private parseExcelData(data: any[]): any[] {
return data.map(row => {
// Convert row to array if it's not already
const rowArray = Array.isArray(row) ? row : Object.values(row);
return {
itemNumber: rowArray[0]?.toString().trim() || '',
description: rowArray[1]?.toString().trim() || '',
pieces: Number(rowArray[2]) || 0,
weight: Number(rowArray[3]) || 0,
unitOfMeasure: rowArray[4]?.toString().trim() || '',
value: Number(rowArray[5]) || 0,
countryOfOrigin: rowArray[6]?.toString().trim() || ''
};
}).filter(item =>
return data.map(row => ({
itemNumber: row['Item Number'] || row['itemNumber'] || '',
description: row['Description'] || row['description'] || '',
pieces: Number(row['Pieces'] || row['pieces'] || 0),
weight: Number(row['Weight'] || row['weight'] || 0),
unitOfMeasure: row['Unit of Measure'] || row['unit of measure'] || row['unitofmeasure'] || row['UnitOfMeasure'],
value: Number(row['Value'] || row['value'] || 0),
countryOfOrigin: row['Country Of Origin'] || row['Country of Origin'] || row['country of origin'] || row['countryoforigin'] || row['CountryOfOrigin'] || ''
})).filter(item =>
item.itemNumber &&
item.description &&
item.pieces > 0
@ -452,11 +388,9 @@ export class GoodsComponent {
}
const formData = this.goodsForm.value;
this.changeInProgress = true;
this.isLoading = true;
this.goodsService.saveGoodsData(this.headerid, formData).pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
this.goodsService.saveGoodsData(this.headerid, formData).subscribe({
next: () => {
this.notificationService.showSuccess('Goods information saved successfully');
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
@ -465,6 +399,9 @@ 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,2 +1 @@
<app-holder-search (holderSelectionCompleted)="onHolderSelectionSaved($event)" [applicationName]="applicationName"
[headerid]="headerid" [selectedHolderId]="selectedHolderId" [isViewMode]="isViewMode"></app-holder-search>
<p>holder works!</p>

View File

@ -1,56 +1,12 @@
import { Component, EventEmitter, inject, Input, Output } from '@angular/core';
import { UserPreferences } from '../../core/models/user-preference';
import { SearchHolderComponent } from '../../holder/search/search-holder.component';
import { HolderService } from '../../core/services/carnet/holder.service';
import { NotificationService } from '../../core/services/common/notification.service';
import { Holder } from '../../core/models/carnet/holder';
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-holder',
imports: [SearchHolderComponent],
imports: [],
templateUrl: './holder.component.html',
styleUrl: './holder.component.scss'
})
export class HolderComponent {
@Input() isViewMode = false;
@Input() headerid: number = 0;
@Input() applicationName: string = '';
@Input() userPreferences: UserPreferences = {};
@Input() isEditMode: boolean = false;
@Output() completed = new EventEmitter<boolean>();
@Output() updated = new EventEmitter<boolean>();
selectedHolderId: number | undefined = 0;
private holdersService = inject(HolderService);
private notificationService = inject(NotificationService);
private errorHandler = inject(ApiErrorHandlerService);
ngOnInit(): void {
if (this.headerid > 0) {
this.holdersService.getHolder(this.headerid).subscribe({
next: (holder: Holder) => {
if (holder) {
this.selectedHolderId = holder.holderid;
this.completed.emit(true);
}
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to get holder details');
this.notificationService.showError(errorMessage);
console.error('Error getting holder details:', error);
}
});
}
}
public refreshHolder(): void {
this.selectedHolderId = undefined;
}
onHolderSelectionSaved(completed: boolean): void {
this.completed.emit(completed);
this.updated.emit(true); // to update dependent data
}
}

View File

@ -1,98 +0,0 @@
<!-- extension-section.component.html -->
<div class="extension-container">
<div class="loading-shade" *ngIf="isLoading">
<mat-spinner diameter="50"></mat-spinner>
</div>
<!-- General Section -->
<div class="general-section">
<h3>General</h3>
<div class="detail-row">
<label>Carnet No:</label>
<span>{{ replacementDetails?.replacementCarnetNumber || 'N/A' }}</span>
</div>
<div class="detail-row">
<label>Issue Date:</label>
<span>{{ replacementDetails?.issueDate | date:'mediumDate':'UTC' }}</span>
</div>
<div class="detail-row">
<label>Preparer Name:</label>
<span>{{ replacementDetails?.preparerName || 'N/A' }}</span>
</div>
<div class="detail-row">
<label>Address:</label>
<span>{{ getAddressLabel() || 'N/A' }}</span>
</div>
</div>
<!-- Extension Section -->
<div class="extension-section">
<h3>Extension</h3>
<form [formGroup]="extensionForm" (ngSubmit)="onSubmit()">
<div class="form-row">
<mat-form-field appearance="outline" class="current-port">
<mat-label>Current port where the goods are located</mat-label>
<input matInput formControlName="portName">
<!-- <mat-error *ngIf="f['portName'].errors?.['required']">
Port location is required
</mat-error>
<mat-error *ngIf="f['portName'].errors?.['maxlength']">
Maximum 100 characters allowed
</mat-error> -->
</mat-form-field>
<mat-form-field appearance="outline" class="country">
<mat-label>Country</mat-label>
<mat-select formControlName="portCountry">
<mat-option *ngFor="let country of countries" [value]="country.value">
{{ country.name }}
</mat-option>
</mat-select>
<!-- <mat-error *ngIf="f['portCountry'].errors?.['required']">
Port Country is required
</mat-error> -->
</mat-form-field>
</div>
<div class="form-row">
<mat-form-field appearance="outline" class="reason">
<mat-label>Reason for Extension</mat-label>
<mat-select formControlName="reasonForExtension" required>
<mat-option *ngFor="let reason of extensionReasons" [value]="reason.value">
{{ reason.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="f['reasonForExtension'].errors?.['required']">
Reason is required
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="duration">
<mat-label>Duration</mat-label>
<input matInput type="number" formControlName="duration">
<span matTextSuffix>Month(s)</span>
<!-- <mat-error *ngIf="f['duration'].errors?.['required']">
Duration is required
</mat-error> -->
<!-- <mat-error *ngIf="f['duration'].errors?.['min']">
Minimum 1 month required
</mat-error> -->
<mat-error *ngIf="f['duration'].errors?.['max']">
Maximum 12 months allowed
</mat-error>
</mat-form-field>
</div>
<div class="form-actions">
<button mat-raised-button color="primary" type="submit" *ngIf="!isViewMode"
[disabled]="extensionForm.invalid || changeInProgress">
Save
</button>
</div>
</form>
</div>
</div>

View File

@ -1,51 +0,0 @@
.extension-container {
padding: 0.5rem;
.general-section,
.extension-section {
margin-bottom: 24px;
h3 {
margin: 0;
margin-bottom: 0.5rem;
color: var(--mat-sys-primary);
font-weight: 500;
}
}
.detail-row {
font-size: 0.875rem;
margin-bottom: 0.5rem;
label {
display: inline-block;
width: 120px;
}
span {
font-weight: 500;
}
}
.form-row {
display: flex;
gap: 16px;
margin-bottom: 16px;
align-items: start;
mat-form-field {
flex: 1;
}
.current-port {
grid-column: span 2;
}
}
.form-actions {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
}

View File

@ -1,172 +0,0 @@
import { Component, EventEmitter, inject, Input, Output } from '@angular/core';
import { FormGroup, FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import { finalize, Subject, takeUntil } from 'rxjs';
import { Country } from '../../core/models/country';
import { CommonService } from '../../core/services/common/common.service';
import { Replacement } from '../../core/models/carnet/replacement';
import { ReplacementService } from '../../core/services/carnet/replacement.service';
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
import { NotificationService } from '../../core/services/common/notification.service';
import { CommonModule } from '@angular/common';
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { ExtensionReason } from '../../core/models/extension-reason';
@Component({
selector: 'app-replacement-set',
imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule],
templateUrl: './replacement-set.component.html',
styleUrl: './replacement-set.component.scss'
})
export class ReplacementSetComponent {
@Input() headerid: number = 0;
@Input() isViewMode = false;
@Input() isEditMode: boolean = false;
@Output() completed = new EventEmitter<boolean>();
extensionForm: FormGroup;
countries: Country[] = [];
extensionReasons: ExtensionReason[] = [];
replacementDetails: Replacement | undefined;
// UI state
isEditing = false;
isLoading = false;
changeInProgress = false;
private destroy$ = new Subject<void>();
private fb = inject(FormBuilder);
private commonService = inject(CommonService);
private notificationService = inject(NotificationService);
private replacementService = inject(ReplacementService);
private errorHandler = inject(ApiErrorHandlerService);
constructor(
) {
this.extensionForm = this.createForm();
}
ngOnInit(): void {
this.loadCountries();
this.loadExtensionReasons();
if (this.headerid > 0) {
this.isLoading = true;
this.replacementService.getExtensionData(this.headerid).pipe(finalize(() => {
this.isLoading = false;
})).subscribe({
next: (replacementData: Replacement) => {
if (replacementData) {
this.patchFormData(replacementData);
this.replacementDetails = replacementData;
this.completed.emit(this.extensionForm.valid || this.extensionForm.disabled);
}
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load extension details');
this.notificationService.showError(errorMessage);
console.error('Error loading extension details:', error);
}
});
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
patchFormData(data: Replacement): void {
this.extensionForm.patchValue({
portName: data.portName,
portCountry: data.portCountry,
reasonForExtension: data.reasonForExtension,
duration: data.duration
});
if (this.isViewMode) {
this.extensionForm.disable();
}
}
createForm(): FormGroup {
return this.fb.group({
portName: [''],
portCountry: [''],
reasonForExtension: ['', Validators.required],
duration: ['', [Validators.max(12)]]
});
}
loadCountries(): void {
this.commonService.getCountries()
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (countries) => {
this.countries = countries;
},
error: (error) => {
console.error('Failed to load countries', error);
}
});
}
loadExtensionReasons(): void {
this.commonService.getExtensionReasons()
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (extensionReasons) => {
this.extensionReasons = extensionReasons;
},
error: (error) => {
console.error('Failed to load extension reasons', error);
}
});
}
onSubmit(): void {
if (this.extensionForm.invalid) {
this.extensionForm.markAllAsTouched();
return;
}
const formData = this.extensionForm.value;
this.changeInProgress = true;
this.replacementService.saveExtensionDetails(this.headerid, formData).pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: () => {
this.notificationService.showSuccess('Extension details saved successfully');
this.completed.emit(this.extensionForm.valid);
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to save extension information');
this.notificationService.showError(errorMessage);
console.error('Error saving extension information:', error);
}
});
}
get f() {
return this.extensionForm.controls;
}
getAddressLabel(): string {
if (!this.replacementDetails) {
return 'No contact information available';
}
// Build name parts, filtering out null/undefined/empty strings
const allParts = [
this.replacementDetails.address1,
this.replacementDetails.address2,
this.replacementDetails.city,
this.replacementDetails.state,
this.replacementDetails.country,
this.replacementDetails.zip
].filter(part => part != null && part.trim() !== '');
return allParts.join(', ') || '';
}
}

View File

@ -1,86 +0,0 @@
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: `
<h2 mat-dialog-title>Select Contact</h2>
<mat-dialog-content>
<mat-radio-group [(ngModel)]="selectedContact">
<mat-radio-button *ngFor="let contact of contacts" [value]="contact">
{{getContactLabel(contact)}}
</mat-radio-button>
</mat-radio-group>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-raised-button color="primary" (click)="onSelect()" [disabled]="!selectedContact">
Select
</button>
<button mat-button (click)="onCancel()">Cancel</button>
</mat-dialog-actions>
`,
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<ContactDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { contacts: any[], currentContact: any }
) {
this.selectedContact = data.currentContact;
}
get contacts() {
return this.data.contacts;
}
onSelect(): void {
this.dialogRef.close(this.selectedContact);
}
onCancel(): void {
this.dialogRef.close();
}
getContactLabel(contact: any): string {
if (!contact) {
return 'No contact information available';
}
// Build name parts, filtering out null/undefined/empty strings
const nameParts = [
contact.firstName,
contact.middleInitial,
contact.lastName
].filter(part => part != null && part.trim() !== '');
// Build contact info parts
const contactInfoParts = [
contact.email,
contact.phone
].filter(part => part != null && part.trim() !== '');
// Combine all non-empty parts
const allParts = [
nameParts.join(' '), // Join name parts with single spaces
...contactInfoParts // Add email and phone if they exist
].filter(part => part.trim() !== '');
return allParts.join(', ') || '';
}
}

View File

@ -1,116 +0,0 @@
import { Component, inject, 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';
import { CarnetService } from '../../core/services/carnet/carnet.service';
import { Fees } from '../../core/models/carnet/fee';
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
import { NotificationService } from '../../core/services/common/notification.service';
@Component({
selector: 'app-fees-dialog',
imports: [AngularMaterialModule, CommonModule, FormsModule],
template: `
<h3 mat-dialog-title>Fees and Charges</h3>
<mat-dialog-content>
<p> The following fees apply:</p>
<ul>
<li *ngIf="estimatedFees.basicFee">Basic fee: <span> {{estimatedFees.basicFee | currency}} </span></li>
<li *ngIf="estimatedFees.counterFoilFee">Counterfoil Fee: <span> {{estimatedFees.counterFoilFee | currency}}
</span></li>
<li *ngIf="estimatedFees.continuationSheetFee">Continuation sheet fee:
<span> {{estimatedFees.continuationSheetFee | currency}}</span></li>
<li *ngIf="estimatedFees.expeditedFee">Expedited fee: <span> {{estimatedFees.expeditedFee | currency}}</span></li>
<li *ngIf="estimatedFees.shippingFee">Shipping fee: <span> {{estimatedFees.shippingFee | currency}}</span></li>
<li *ngIf="estimatedFees.bondPremium">{{bondPremiumLabel}}: <span> {{estimatedFees.bondPremium | currency}}</span></li>
<li *ngIf="estimatedFees.cargoPremium">Cargo Premium: <span> {{estimatedFees.cargoPremium | currency}}</span></li>
<li *ngIf="estimatedFees.ldiPremium">LDI Premium: <span> {{estimatedFees.ldiPremium | currency}}</span></li>
</ul>
<hr/>
<ul>
<li class="total">Total: <span>{{total | currency}}</span></li>
</ul>
<hr/>
<ul>
<li *ngIf="estimatedFees.securityAmount">Security Amount: <span>{{estimatedFees.securityAmount | currency}}</span></li>
<li *ngIf="estimatedFees.insuredValue">Insured Value: <span>{{estimatedFees.insuredValue | currency}}</span></li>
</ul>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-raised-button color="primary" (click)="onClose()">
Ok
</button>
</mat-dialog-actions>
`,
styles: [`
li {
margin-bottom: 8px;
display: flex;
justify-content: space-between;
}
.total {
font-weight: bold;
margin-top: 12px;
}
`]
})
export class FeesDialogComponent {
estimatedFees: Fees = {};
headerid: number | null = null;
bondPremiumLabel: string = 'Bond Premium';
total: number = 0;
private carnetService = inject(CarnetService);
private notificationService = inject(NotificationService);
private errorHandler = inject(ApiErrorHandlerService);
constructor(
public dialogRef: MatDialogRef<FeesDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { headerid: any }
) {
this.headerid = data.headerid;
}
ngOnInit(): void {
if (this.headerid && this.headerid > 0) {
this.carnetService.getEstimatedFees(this.headerid).subscribe({
next: (data: Fees) => {
if (data) {
this.estimatedFees = data;
if (this.estimatedFees.securityType === 'B') {
this.bondPremiumLabel = 'Bond Premium';
} else if (this.estimatedFees.securityType === 'C') {
this.bondPremiumLabel = 'Cash Deposit';
} else if (this.estimatedFees.securityType === 'G') {
this.bondPremiumLabel = 'Refundable claim deposit';
}
this.calculateTotal();
}
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to get estimated fees');
this.notificationService.showError(errorMessage);
console.error('Error getting estimated fees:', error);
}
});
}
}
calculateTotal(): void {
const excludedProperties = ['securityAmount', 'insuredValue', 'securityType'];
const validFees = Object.entries(this.estimatedFees)
.filter(([key]) => !excludedProperties.includes(key))
.map(([, value]) => value)
.filter((fee): fee is number => typeof fee === 'number');
this.total = validFees.reduce((total, fee) => total + fee, 0);
}
onClose(): void {
this.dialogRef.close();
}
}

View File

@ -1,31 +1,15 @@
<div class="shipping-container">
<div class="loading-shade" *ngIf="isLoading">
<mat-spinner diameter="50"></mat-spinner>
</div>
<form [formGroup]="shippingForm" (ngSubmit)="onSubmit(false)">
<form [formGroup]="shippingForm" (ngSubmit)="onSubmit()">
<!-- Insurance Section -->
<div class="section">
<h3>Insurance & Bond</h3>
<div class="checkbox-group">
<!-- <mat-checkbox formControlName="needsBond">Do you need Bond from
us?</mat-checkbox> -->
<mat-checkbox formControlName="needsInsurance">Do you need insurance for your
<mat-checkbox labelPosition="before" formControlName="needsBond">Do you need Bond from
us?</mat-checkbox>
<mat-checkbox labelPosition="before" formControlName="needsInsurance">Do you need insurance for your
goods?</mat-checkbox>
<mat-checkbox formControlName="needsLostDocProtection">Do you need Lost document
<mat-checkbox labelPosition="before" formControlName="needsLostDocProtection">Do you need Lost document
protection?</mat-checkbox>
<mat-form-field appearance="outline" class="formofsecurity">
<mat-label>Form Of Security</mat-label>
<mat-select formControlName="formOfSecurity">
<mat-option *ngFor="let formOfSecurity of formOfSecurities" [value]="formOfSecurity.value">
{{ formOfSecurity.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="shippingForm.get('formOfSecurity')?.errors?.['required']">
Form Of Security is required
</mat-error>
</mat-form-field>
</div>
</div>
@ -33,45 +17,22 @@
<div class="section">
<h3>Ship To</h3>
<mat-radio-group formControlName="shipTo" class="radio-group" (change)="onShipToChange($event)">
<mat-radio-button value="PREPARER">Preparer</mat-radio-button>
<mat-radio-button value="HOLDER">Holder</mat-radio-button>
<mat-radio-button value="3RDPARTY">3rd Party</mat-radio-button>
<mat-radio-button value="preparer">Preparer</mat-radio-button>
<mat-radio-button value="holder">Holder</mat-radio-button>
<mat-radio-button value="thirdParty">3rd Party</mat-radio-button>
</mat-radio-group>
<mat-card appearance="outlined" *ngIf="!showAddressForm && !showContactForm">
<mat-card-content>
<div class="presaved-address">
<div class="presaved-address-content">
<p>{{getAddressLabel()}}</p>
<p>{{getContactLabel()}}</p>
</div>
<div class="presaved-address-actions">
<!-- <button type="button" mat-icon-button color="primary" matTooltip="Edit">
<mat-icon>edit</mat-icon>
</button> -->
<button type="button" mat-icon-button color="primary" *ngIf="!isViewMode"
(click)="selectContact()" matTooltip="Change contact">
<mat-icon>compare_arrows</mat-icon>
</button>
</div>
</div>
<p>{{getAddressLabel()}}</p>
</mat-card-content>
<mat-card-actions>
<button mat-button (click)="editAddressForm()">Edit</button>
</mat-card-actions>
</mat-card>
<div *ngIf="showAddressForm" class="address-form" formGroupName="address">
<h4>Shipping Address</h4>
<div class="form-row">
<mat-form-field appearance="outline" class="companyName">
<mat-label>Company Name</mat-label>
<input matInput formControlName="companyName" required>
<mat-error *ngIf="shippingForm.get('address.companyName')?.errors?.['required']">
Company name is required
</mat-error>
<mat-error *ngIf="shippingForm.get('address.companyName')?.errors?.['maxlength']">
Maximum 100 characters allowed
</mat-error>
</mat-form-field>
</div>
<!-- Address Information -->
<div class="form-row">
<mat-form-field appearance="outline" class="address1">
@ -151,7 +112,7 @@
</mat-form-field>
</div>
<div class="form-actions" *ngIf="showAddressForm && shippingForm.get('shipTo')?.value !== '3RDPARTY'">
<div class="form-actions" *ngIf="showAddressForm && shippingForm.get('shipTo')?.value !== 'thirdParty'">
<button mat-button type="button" (click)="cancelEditAddressForm()">Cancel</button>
</div>
</div>
@ -268,20 +229,16 @@
<textarea matInput formControlName="notes" rows="2"></textarea>
</mat-form-field>
</div>
<div class="form-actions" *ngIf="showAddressForm && shippingForm.get('shipTo')?.value !== '3RDPARTY'">
<button mat-button type="button" (click)="cancelEditAddressForm()">Cancel</button>
</div>
</div>
</div>
<!-- Delivery Section -->
<div class="section">
<h3>Delivery</h3>
<h3>Delivery Method</h3>
<div class="form-row">
<mat-form-field appearance="outline">
<mat-label>Delivery Type</mat-label>
<mat-select formControlName="deliveryType" required (change)="onDeliveryTypeChange()">
<mat-select formControlName="deliveryType" required>
<mat-option *ngFor="let deliveryType of deliveryTypes" [value]="deliveryType.value">
{{ deliveryType.name }}
</mat-option>
@ -289,14 +246,10 @@
<mat-error *ngIf="shippingForm.get('deliveryType')?.errors?.['required']">
Delivery Type is required
</mat-error>
<mat-hint align="start" *ngIf="deliveryEstimate && !isViewMode" class="delivery-estimate">
<span>{{ deliveryEstimate }}</span>
</mat-hint>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Delivery Method</mat-label>
<mat-select formControlName="deliveryMethod" required
(selectionChange)="onDeliveryMethodChange($event.value)">
<mat-select formControlName="deliveryMethod" required>
<mat-option *ngFor="let deliveryMethod of deliveryMethods" [value]="deliveryMethod.value">
{{ deliveryMethod.name }}
</mat-option>
@ -306,14 +259,11 @@
</mat-error>
</mat-form-field>
</div>
<div *ngIf="shippingForm.get('deliveryMethod')?.value === 'CLC'" class="form-row">
<div *ngIf="shippingForm.get('deliveryMethod')?.value === 'customerCourier'" class="form-row">
<mat-form-field appearance="outline" class="full-width">
<mat-label>Courier Account Number</mat-label>
<input matInput formControlName="courierAccount">
<mat-error *ngIf="shippingForm.get('courierAccount')?.errors?.['required']">
Required when using customer courier
</mat-error>
<mat-error>Required when using customer courier</mat-error>
</mat-form-field>
</div>
</div>
@ -343,36 +293,10 @@
</div>
<div class="form-actions">
<div class="error-message">
<mat-icon *ngIf="missingSectionMessage">error</mat-icon>
<span *ngIf="missingSectionMessage">{{ missingSectionMessage }}</span>
</div>
<div class="action-buttons">
<button mat-raised-button color="primary" type="button" (click)="showEstimatedFees()"
*ngIf="showEstimatedFeesButton && !isViewMode">
<span>Estimated Fees</span>
</button>
<button mat-raised-button color="primary" type="submit" *ngIf="!isViewMode"
[disabled]="shippingForm.invalid || changeInProgress">
Save
</button>
<button mat-raised-button color="primary" type="button" (click)="processApplication()"
[disabled]="!enableSubmitButton" *ngIf="showProcessButton && !isViewMode">
<span>Process Application</span>
</button>
<button mat-raised-button color="primary" type="button" (click)="submitApplication()"
[disabled]="!enableSubmitButton" *ngIf="showSubmitButton && !isViewMode">
<span>Submit Application</span>
</button>
<button mat-raised-button color="primary" type="button" (click)="returnToHome()" *ngIf="isViewMode">
Back
</button>
</div>
<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>
</div>
</form>
</div>

View File

@ -1,14 +1,14 @@
.shipping-container {
padding-top: 0.5rem;
padding: 16px;
.section {
margin-bottom: 1rem;
margin-bottom: 24px;
padding: 16px;
border: 1px solid #e0e0e0;
border-radius: 4px;
h3 {
margin: 0 0 0.9rem 0;
margin: 0px 0px 16px 0px;
color: var(--mat-sys-primary);
font-weight: 500;
}
@ -20,32 +20,25 @@
}
.mat-mdc-card-content:first-child {
padding: 0 16px;
padding: 0 16px;
}
}
.checkbox-group {
display: flex;
flex-direction: row;
flex-direction: column;
gap: 8px;
.formofsecurity {
margin-left: 1rem;
min-width: 300px;
}
}
.radio-group {
display: flex;
gap: 16px;
margin-bottom: 0.9rem;
margin-bottom: 16px;
}
.form-row {
display: flex;
gap: 16px;
margin-bottom: 0.5rem;
align-items: start;
mat-form-field {
flex: 1;
@ -63,54 +56,9 @@
.form-actions {
display: flex;
justify-content: space-between;
align-items: center;
justify-content: flex-end;
gap: 16px;
margin-top: 0.9rem;
.error-message {
display: flex;
align-items: center;
color: var(--mat-form-field-error-text-color, var(--mat-sys-error));
-webkit-font-smoothing: antialiased;
font-family: var(--mat-form-field-subscript-text-font, var(--mat-sys-body-small-font));
line-height: var(--mat-form-field-subscript-text-line-height, var(--mat-sys-body-small-line-height));
font-size: var(--mat-form-field-subscript-text-size, var(--mat-sys-body-small-size));
letter-spacing: var(--mat-form-field-subscript-text-tracking, var(--mat-sys-body-small-tracking));
font-weight: var(--mat-form-field-subscript-text-weight, var(--mat-sys-body-small-weight));
mat-icon {
margin-right: 4px;
}
}
.action-buttons {
display: flex;
align-items: center;
gap: 16px;
}
}
.presaved-address {
padding: 10px;
display: flex;
flex-direction: row;
gap: 8px;
font-size: 0.875rem;
p {
margin: 0;
margin-bottom: 4px;
}
.presaved-address-actions {
margin-top: 8px;
}
}
.delivery-estimate {
color: #28a745;
font-weight: 500;
margin-top: 16px;
}
}

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';
@ -10,23 +10,10 @@ import { Shipping } from '../../core/models/carnet/shipping';
import { Country } from '../../core/models/country';
import { State } from '../../core/models/state';
import { ZipCodeValidator } from '../../shared/validators/zipcode-validator';
import { finalize, forkJoin, map, of, Subject, switchMap, takeUntil, throwError } from 'rxjs';
import { Subject, takeUntil } from 'rxjs';
import { DeliveryType } from '../../core/models/delivery-type';
import { DeliveryMethod } from '../../core/models/delivery-method';
import { PaymentType } from '../../core/models/payment-type';
import { format, isAfter, isWeekend, addBusinessDays } 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';
import { HolderService } from '../../core/services/carnet/holder.service';
import { Holder } from '../../core/models/carnet/holder';
import { NavigationService } from '../../core/services/common/navigation.service';
import { StorageService } from '../../core/services/common/storage.service';
import { FormOfSecurity } from '../../core/models/formofsecurity';
import { CarnetService } from '../../core/services/carnet/carnet.service';
import { environment } from '../../../environments/environment';
import { FeesDialogComponent } from './fees-dialog.component';
@Component({
selector: 'app-shipping',
@ -37,39 +24,18 @@ import { FeesDialogComponent } from './fees-dialog.component';
export class ShippingComponent {
@Input() headerid: number = 0;
@Input() isEditMode = false;
@Input() isViewMode = false;
@Input() missingSectionMessage = '';
@Input() applicationName: string = '';
@Input() applicationType: 'new' | 'edit' | 'additional' | 'duplicate' | 'extend' | null = 'edit';
@Output() completed = new EventEmitter<boolean>();
private fb = inject(FormBuilder);
private dialog = inject(MatDialog);
private shippingService = inject(ShippingService);
private notificationService = inject(NotificationService);
private errorHandler = inject(ApiErrorHandlerService);
private commonService = inject(CommonService);
private holderService = inject(HolderService);
private navigationService = inject(NavigationService);
private storageService = inject(StorageService);
private carnetService = inject(CarnetService);
shippingForm: FormGroup;
isLoading = false;
changeInProgress = false;
showAddressForm = false;
showContactForm = false;
deliveryEstimate: string = '';
enableSubmitButton: boolean = false;
needsInsurance = true;
holderid: number = 0;
holder: Holder | undefined | null = null;
showSubmitButton: boolean = environment.appType === 'client';
showEstimatedFeesButton: boolean = environment.appType === 'client';
showProcessButton: boolean = environment.appType === 'service-provider';
countriesHasStates = ['US', 'CA', 'MX'];
countries: Country[] = [];
@ -77,45 +43,73 @@ export class ShippingComponent {
deliveryTypes: DeliveryType[] = [];
deliveryMethods: DeliveryMethod[] = [];
paymentTypes: PaymentType[] = [];
formOfSecurities: FormOfSecurity[] = [];
govFormOfSecurities: FormOfSecurity[] = [];
nonGovFormOfSecurities: FormOfSecurity[] = [];
shippingFromDB: Shipping | null = null;
private destroy$ = new Subject<void>();
preparerAddress: ShippingAddress | null | undefined = null;
holderAddress: ShippingAddress | null | undefined = null;
preparerContact: ShippingContact | null | undefined = null;
preparerContacts: ShippingContact[] = [];
holderContact: ShippingContact | null | undefined = null;
holderContacts: ShippingContact[] = [];
// 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',
}
preparerAddress = {
address1: '123 Main St',
address2: 'Suite 100',
city: 'Anytown',
state: 'CA',
zip: '12345',
country: 'US'
};
holderAddress = {
address1: '456 Holder St',
address2: 'Apt 200',
city: 'Othertown',
state: 'NY',
zip: '67890',
country: 'US'
};
holderContact = {
firstName: 'Jane',
lastName: 'Smith',
middleInitial: 'B',
title: 'Ms.',
phone: '2345678901',
mobile: '1098765432',
fax: '2345678901',
email: 'jan@sm.cm'
};
constructor() {
this.shippingForm = this.fb.group({
// needsBond: [false],
needsBond: [false],
needsInsurance: [false],
needsLostDocProtection: [false],
formOfSecurity: ['', Validators.required],
shipTo: ['PREPARER', Validators.required],
shipTo: ['preparer', Validators.required],
address: this.fb.group({
companyName: [''],
address1: [''],
address2: [''],
city: [''],
state: [''],
country: ['US'],
zip: [''],
address1: ['', [Validators.required, Validators.maxLength(100)]],
address2: ['', [Validators.maxLength(100)]],
city: ['', [Validators.required, Validators.maxLength(50)]],
state: ['', Validators.required],
country: ['', Validators.required],
zip: ['', [Validators.required, ZipCodeValidator('country')]],
}),
contact: this.fb.group({
firstName: [''],
lastName: [''],
middleInitial: [''],
title: [''],
phone: [''],
mobile: [''],
fax: [''],
email: [''],
firstName: ['', [Validators.required, Validators.maxLength(50)]],
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}$/)]],
email: ['', [Validators.required, Validators.email, Validators.maxLength(100)]],
refNumber: [''],
notes: ['']
}),
@ -123,11 +117,26 @@ export class ShippingComponent {
deliveryMethod: ['', Validators.required],
courierAccount: [''],
paymentMethod: ['', Validators.required],
paymentNotes: ['']
notes: ['']
});
this.shippingForm.get('deliveryType')?.valueChanges.subscribe(() => {
this.calculateDeliveryEstimate();
// 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 === 'customerCourier') {
courierControl?.setValidators([Validators.required]);
} else {
courierControl?.clearValidators();
}
courierControl?.updateValueAndValidity();
});
this.shippingForm.valueChanges.subscribe(() => {
this.completed.emit(this.shippingForm.valid);
});
}
@ -136,101 +145,17 @@ export class ShippingComponent {
this.loadDeliveryTypes();
this.loadDeliveryMethods();
this.loadPaymentTypes();
this.loadFormOfSecurities();
if (this.headerid && this.headerid > 0) {
if (this.headerid && this.isEditMode) {
this.loadShippingData();
}
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['missingSectionMessage']) {
this.enableSubmitButton = this.missingSectionMessage === '';
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
onSubmit(submitApp: boolean): void {
if (this.shippingForm.invalid) {
this.shippingForm.markAllAsTouched();
return;
}
this.changeInProgress = true;
const shippingData: Shipping = this.shippingForm.value;
if (shippingData.shipTo !== '3RDPARTY') {
shippingData.address = shippingData.shipTo === 'PREPARER' ? this.preparerAddress ?? undefined : this.holderAddress ?? undefined;
shippingData.contact = shippingData.shipTo === 'PREPARER' ? this.preparerContact ?? undefined : this.holderContact ?? undefined;
}
if (this.holder?.holderType.trim() === 'GOV') {
shippingData.formOfSecurity = this.govFormOfSecurities?.[0].value;
}
if (this.applicationType === 'duplicate' && this.shippingFromDB) {
shippingData.needsInsurance = this.shippingFromDB.needsInsurance;
shippingData.needsLostDocProtection = this.shippingFromDB.needsLostDocProtection;
shippingData.formOfSecurity = this.shippingFromDB.formOfSecurity;
}
if (this.applicationType === 'additional' && this.shippingFromDB) {
shippingData.needsLostDocProtection = this.shippingFromDB.needsLostDocProtection;
shippingData.formOfSecurity = this.shippingFromDB.formOfSecurity;
if (!this.needsInsurance) {
shippingData.needsInsurance = this.shippingFromDB.needsInsurance;
}
}
if (this.applicationType === 'extend' && this.shippingFromDB) {
shippingData.needsLostDocProtection = this.shippingFromDB.needsLostDocProtection;
shippingData.formOfSecurity = this.shippingFromDB.formOfSecurity;
}
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);
if (this.enableSubmitButton && this.headerid && submitApp) {
let currentApplicationDetails = {
headerid: this.headerid,
applicationName: this.applicationName,
applicationType: this.applicationType
}
this.storageService.set("currentapplication", currentApplicationDetails);
this.navigationService.navigate(["terms"])
}
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to save shipping and payment information');
this.notificationService.showError(errorMessage);
}
});
}
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();
@ -241,60 +166,8 @@ export class ShippingComponent {
this.shippingForm.get('address.zip')?.updateValueAndValidity();
}
public refreshShippingData(): void {
if (this.headerid > 0) {
this.loadAddressContactData();
}
}
public refreshInsuranceData(needsInsurance: boolean): void {
this.needsInsurance = needsInsurance;
if (this.applicationType === 'additional') {
if (this.needsInsurance) {
this.shippingForm.get('needsInsurance')?.enable();
} else {
this.shippingForm.get('needsInsurance')?.disable();
}
}
}
editAddressForm(): void {
this.showAddressForm = true;
let shipTo = this.shippingForm.get('shipTo')?.value;
if (shipTo === 'PREPARER' && this.preparerAddress) {
this.shippingForm.get('address')?.patchValue(this.preparerAddress);
this.loadStates(this.preparerAddress.country);
} else if (shipTo === 'HOLDER' && this.holderAddress) {
this.shippingForm.get('address')?.patchValue(this.holderAddress);
this.loadStates(this.holderAddress.country);
}
}
cancelEditAddressForm(): void {
this.showAddressForm = false;
this.shippingForm.get('address')?.reset({ country: 'US' });
this.loadStates('US');
}
onShipToChange(event: any): void {
const shipTo = event.value;
this.showAddressForm = false;
this.showContactForm = false;
this.updateAddressContactValidation(shipTo);
if (shipTo === '3RDPARTY') {
this.shippingForm.get('contact')?.reset();
this.shippingForm.get('address')?.reset({ country: 'US' });
this.loadStates('US');
this.showAddressForm = true;
this.showContactForm = true;
}
}
loadCountries(): void {
this.commonService.getCountries()
this.commonService.getCountries(0)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (countries) => {
@ -302,12 +175,13 @@ export class ShippingComponent {
},
error: (error) => {
console.error('Failed to load countries', error);
this.isLoading = false;
}
});
}
loadDeliveryTypes(): void {
this.commonService.getDeliveryTypes()
this.commonService.getDeliveryTypes(0)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (deliveryTypes) => {
@ -315,12 +189,13 @@ export class ShippingComponent {
},
error: (error) => {
console.error('Failed to load delivery types', error);
this.isLoading = false;
}
});
}
loadDeliveryMethods(): void {
this.commonService.getDeliveryMethods()
this.commonService.getDeliveryMethods(0)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (deliveryMethods) => {
@ -328,12 +203,13 @@ export class ShippingComponent {
},
error: (error) => {
console.error('Failed to load delivery methods', error);
this.isLoading = false;
}
});
}
loadPaymentTypes(): void {
this.commonService.getPaymentTypes()
this.commonService.getPaymentTypes(0)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (paymentTypes) => {
@ -341,6 +217,7 @@ export class ShippingComponent {
},
error: (error) => {
console.error('Failed to load payment types', error);
this.isLoading = false;
}
});
}
@ -348,436 +225,152 @@ export class ShippingComponent {
loadStates(country: string): void {
this.isLoading = true;
country = this.countriesHasStates.includes(country) ? country : 'FN';
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');
if (this.countriesHasStates.includes(country)) {
stateControl?.enable();
} else {
stateControl?.disable();
stateControl?.setValue('FN');
}
},
error: (error) => {
console.error('Failed to load states', error);
}
});
}
loadFormOfSecurities(): void {
this.commonService.getFormOfSecurities()
this.commonService.getStates(country, 0)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (fos) => {
this.govFormOfSecurities = fos.filter(f => f.isGov);
this.nonGovFormOfSecurities = fos.filter(f => !f.isGov);
next: (states) => {
this.states = states;
const stateControl = this.shippingForm.get('contact.state');
if (this.countriesHasStates.includes(country)) {
stateControl?.enable();
} else {
stateControl?.disable();
stateControl?.setValue('FN');
}
this.isLoading = false;
},
error: (error) => {
console.error('Failed to load form of securities', error);
console.error('Failed to load states', error);
this.isLoading = false;
}
});
}
loadShippingData(): void {
this.isLoading = true;
this.shippingService.getShippingData(this.headerid).pipe(
finalize(() => {
this.shippingService.getShippingData(this.headerid).subscribe({
next: (data: Shipping) => {
this.patchShippingData(data);
this.isLoading = false;
})
).subscribe({
next: (results: Shipping) => {
if (!results) { // do nothing if empty
return;
}
this.shippingFromDB = results;
this.patchShippingData();
this.loadAddressContactData();
},
error: (error: any) => {
console.error('Error loading shipping data', error);
const errorMessage = this.errorHandler.handleApiError(error, 'Failed to load shipping data');
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load shipping and payments data');
this.notificationService.showError(errorMessage);
this.isLoading = false;
}
});
}
loadAddressContactData(): void {
this.isLoading = true;
this.holderService.getHolder(this.headerid).pipe(
switchMap((holder: Holder) => {
if (!holder?.holderid) {
return of(undefined);
}
this.holder = holder;
this.formOfSecurities = holder.holderType?.trim() === 'GOV' ? this.govFormOfSecurities : this.nonGovFormOfSecurities;
return forkJoin({
holderContacts: this.shippingService.getHolderContactsById(holder.holderid),
holderAddress: this.shippingService.getHolderAddress(holder),
preparerAddress: this.shippingService.getPreparerAddress(),
preparerContacts: this.shippingService.getPreparerContacts()
}).pipe(
// Combine the forkJoin results with the original holder object
map((apiResults: any) => ({ ...apiResults, holder }))
);
}),
finalize(() => this.isLoading = false)
).subscribe({
next: (results) => {
if (!results || !this.shippingFromDB) { // do nothing if empty
return;
}
this.holderid = results.holder.holderid;
this.holderContacts = results.holderContacts;
this.holderAddress = results.holderAddress;
this.preparerContacts = results.preparerContacts;
this.preparerAddress = results.preparerAddress;
// Set holder contact
if (this.shippingFromDB?.shipTo === 'HOLDER' && this.shippingFromDB?.contact?.contactid) {
this.holderContact = this.holderContacts.find(hc => hc.contactid === this.shippingFromDB?.contact?.contactid);
} else {
this.holderContact = this.holderContacts?.[0];
}
// Set preparer contact
if (this.shippingFromDB?.shipTo === 'PREPARER' && this.shippingFromDB?.contact?.contactid) {
this.preparerContact = this.preparerContacts.find(pc => pc.contactid === this.shippingFromDB?.contact?.contactid);
} else {
this.preparerContact = this.preparerContacts.find(pc => pc.defaultContact);
}
this.patchAddressContactData();
},
error: (error: any) => {
console.error('Error loading shipping data', error);
const errorMessage = this.errorHandler.handleApiError(error, 'Failed to load shipping data');
this.notificationService.showError(errorMessage);
}
patchShippingData(shipping: Shipping): void {
this.shippingForm.patchValue({
needsBond: shipping.needsBond,
needsInsurance: shipping.needsInsurance,
needsLostDocProtection: shipping.needsLostDocProtection,
shipTo: shipping.shipTo,
address: shipping.address,
contact: shipping.contact,
deliveryType: shipping.deliveryType,
deliveryMethod: shipping.deliveryMethod,
courierAccount: shipping.courierAccount,
paymentMethod: shipping.paymentMethod,
notes: shipping.notes
});
if (shipping.address?.country) {
this.loadStates(shipping.address?.country);
}
}
patchShippingData(): void {
if (!this.shippingFromDB) {
updateShippingValidation(shipTo: string): void {
// const addressGroup = this.shippingForm.get('address') as FormGroup;
// const contactGroup = this.shippingForm.get('contact') as FormGroup;
// if (shipTo === 'thirdParty') {
// Object.keys(addressGroup.controls).forEach(key => {
// addressGroup.get(key)?.setValidators(Validators.required);
// });
// Object.keys(contactGroup.controls).forEach(key => {
// if (key !== 'middleInitial' && key !== 'fax' && key !== 'notes' && key !== 'refNumber') {
// contactGroup.get(key)?.setValidators(Validators.required);
// }
// });
// } else {
// Object.keys(addressGroup.controls).forEach(key => {
// addressGroup.get(key)?.clearValidators();
// });
// Object.keys(contactGroup.controls).forEach(key => {
// contactGroup.get(key)?.clearValidators();
// });
// }
// addressGroup.updateValueAndValidity();
// contactGroup.updateValueAndValidity();
}
onSubmit(): void {
if (this.shippingForm.invalid) {
this.shippingForm.markAllAsTouched();
return;
}
this.shippingForm.patchValue({
// needsBond: shipping.needsBond,
needsInsurance: this.shippingFromDB.needsInsurance,
needsLostDocProtection: this.shippingFromDB.needsLostDocProtection,
formOfSecurity: this.shippingFromDB.formOfSecurity,
shipTo: this.shippingFromDB.shipTo,
deliveryType: this.shippingFromDB.deliveryType,
deliveryMethod: this.shippingFromDB.deliveryMethod,
courierAccount: this.shippingFromDB.courierAccount,
paymentMethod: this.shippingFromDB.paymentMethod
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;
}
});
if (this.shippingFromDB.deliveryMethod === 'CLC') {
this.onDeliveryMethodChange(this.shippingFromDB.deliveryMethod);
}
this.shippingForm.markAsUntouched();
this.completed.emit(this.shippingForm.valid);
if (this.isViewMode) {
this.shippingForm.disable();
}
if (this.applicationType === 'duplicate' || this.applicationType === 'additional'
|| this.applicationType === 'extend') {
const formOfSecurityControl = this.shippingForm.get('formOfSecurity');
formOfSecurityControl?.disable();
formOfSecurityControl?.clearValidators();
formOfSecurityControl?.updateValueAndValidity();
this.shippingForm.get('needsLostDocProtection')?.disable();
}
if (this.applicationType === 'duplicate' || this.applicationType === 'additional') {
this.shippingForm.get('needsInsurance')?.disable();
}
}
patchAddressContactData(): void {
if (!this.shippingFromDB) {
return
}
if (this.shippingFromDB.address?.country) {
this.loadStates(this.shippingFromDB.address?.country);
}
let formOfSecurityControl = this.shippingForm.get('formOfSecurity');
if (this.holder?.holderType.trim() === 'GOV') {
formOfSecurityControl?.setValue(this.govFormOfSecurities?.[0].value);
formOfSecurityControl?.disable();
} else if (!this.isViewMode && this.applicationType === 'edit') {
formOfSecurityControl?.enable();
}
this.updateAddressContactValidation(this.shippingFromDB.shipTo);
if (this.shippingFromDB.shipTo === '3RDPARTY') {
this.showAddressForm = true;
this.showContactForm = true;
const addressGroup = this.shippingForm.get('address') as FormGroup;
const contactGroup = this.shippingForm.get('contact') as FormGroup;
addressGroup.patchValue({
addressid: this.shippingFromDB.address?.addressid,
companyName: this.shippingFromDB.address?.companyName,
address1: this.shippingFromDB.address?.address1,
address2: this.shippingFromDB.address?.address2,
city: this.shippingFromDB.address?.city,
state: this.shippingFromDB.address?.state,
zip: this.shippingFromDB.address?.zip,
country: this.shippingFromDB.address?.country
})
contactGroup.patchValue({
contactid: this.shippingFromDB.contact?.contactid,
firstName: this.shippingFromDB.contact?.firstName,
lastName: this.shippingFromDB.contact?.lastName,
middleInitial: this.shippingFromDB.contact?.middleInitial,
title: this.shippingFromDB.contact?.title,
phone: this.shippingFromDB.contact?.phone,
mobile: this.shippingFromDB.contact?.mobile,
fax: this.shippingFromDB.contact?.fax,
email: this.shippingFromDB.contact?.email,
refNumber: this.shippingFromDB.contact?.refNumber,
notes: this.shippingFromDB.contact?.notes,
})
}
this.shippingForm.markAsUntouched();
this.completed.emit(this.shippingForm.valid);
}
updateAddressContactValidation(shipTo: string): void {
const addressGroup = this.shippingForm.get('address') as FormGroup;
const contactGroup = this.shippingForm.get('contact') as FormGroup;
if (shipTo === '3RDPARTY') {
Object.keys(addressGroup.controls).forEach(key => {
if (key === 'companyName') {
addressGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(100)]);
} else if (key === 'address1') {
addressGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(100)]);
} else if (key === 'address2') {
addressGroup.get(key)?.setValidators([Validators.maxLength(100)]);
} else if (key === 'city') {
addressGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(50)]);
} else if (key === 'state') {
addressGroup.get(key)?.setValidators(Validators.required);
} else if (key === 'country') {
addressGroup.get(key)?.setValidators(Validators.required);
} 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') {
contactGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(50)]);
} else if (key === 'lastName') {
contactGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(50)]);
} else if (key === 'middleInitial') {
contactGroup.get(key)?.setValidators([Validators.maxLength(1)]);
} 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}$/)]);
} else if (key === 'mobile') {
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}$/)]);
} else if (key === 'email') {
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();
});
}
}
getAddressLabel(): string {
let shipTo = this.shippingForm.get('shipTo')?.value;
if (shipTo === 'PREPARER' && this.preparerAddress) {
return `${this.preparerAddress.companyName}, ${this.preparerAddress.address1},
${this.preparerAddress.city}, ${this.preparerAddress.state}, ${this.preparerAddress.zip},
${this.preparerAddress.country}`;
if (shipTo === 'preparer') {
return `${this.preparerContact.firstName} ${this.preparerContact.lastName}, ${this.preparerAddress.address1}, ${this.preparerAddress.city}, ${this.preparerAddress.state}, ${this.preparerAddress.zip}, ${this.preparerAddress.country}`;
}
if (shipTo === 'HOLDER' && this.holderAddress) {
return `${this.holderAddress.companyName}, ${this.holderAddress.address1},
${this.holderAddress.city}, ${this.holderAddress.state}, ${this.holderAddress.zip},
${this.holderAddress.country}`;
if (shipTo === 'holder') {
return `${this.holderContact.firstName} ${this.holderContact.lastName}, ${this.holderAddress.address1}, ${this.holderAddress.city}, ${this.holderAddress.state}, ${this.holderAddress.zip}, ${this.holderAddress.country}`;
}
return '';
}
getContactLabel(): string {
const shipTo = this.shippingForm.get('shipTo')?.value;
const contact = shipTo === 'PREPARER' ? this.preparerContact :
shipTo === 'HOLDER' ? this.holderContact : null;
if (!contact) {
return 'No contact information available';
}
// Build name parts, filtering out null/undefined/empty strings
const nameParts = [
contact.firstName,
contact.middleInitial,
contact.lastName
].filter(part => part != null && part.trim() !== '');
// Build contact info parts
const contactInfoParts = [
contact.email,
contact.phone
].filter(part => part != null && part.trim() !== '');
// Combine all non-empty parts
const allParts = [
nameParts.join(' '), // Join name parts with single spaces
...contactInfoParts // Add email and phone if they exist
].filter(part => part.trim() !== '');
return allParts.join(', ') || '';
}
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;
const cutOffTime: number = Number(deliveryTypeObj?.cutOffTime) !== 0 ? Number(deliveryTypeObj?.cutOffTime) : 16;
const now = new Date();
const cutoffTime = new Date();
cutoffTime.setHours(cutOffTime, 0, 0, 0);
let deliveryDate: Date;
let message = 'Estimated delivery: ';
const isAfterCutoff = isAfter(now, cutoffTime);
const isWeekendDay = isWeekend(now);
switch (deliveryType) {
case 'SAME':
if (isAfterCutoff || isWeekendDay) {
deliveryDate = addBusinessDays(now, 1);
message += format(deliveryDate, 'EEEE, MMMM do, yyyy');
} else {
message += format(now, 'EEEE, MMMM do, yyyy');
}
break;
case 'STD':
// Standard is 3 business days
if (isAfterCutoff || isWeekendDay) {
// If after cutoff or weekend, add 4 business days
deliveryDate = addBusinessDays(now, daysToDelivery);
} else {
// Before cutoff on weekday, add 3 business days
deliveryDate = addBusinessDays(now, daysToDelivery - 1);
}
message += format(deliveryDate, 'EEEE, MMMM do, yyyy');
break;
case 'NBD':
// Next business day for pickup
if (isAfterCutoff || isWeekendDay) {
// If after cutoff or weekend, add 2 business days
deliveryDate = addBusinessDays(now, daysToDelivery);
} else {
// Before cutoff on weekday, add 1 business day
deliveryDate = addBusinessDays(now, daysToDelivery);
}
message += format(deliveryDate, 'EEEE, MMMM do, yyyy');
break;
default:
message = '';
}
this.deliveryEstimate = message;
}
selectContact(): void {
editAddressForm(): void {
this.showAddressForm = true;
let shipTo = this.shippingForm.get('shipTo')?.value;
const contacts = shipTo === 'PREPARER' ? this.preparerContacts : this.holderContacts;
const currentContact = shipTo === 'PREPARER' ? this.preparerContact : this.holderContact;
const dialogRef = this.dialog.open(ContactDialogComponent, {
width: '500px',
data: { contacts: contacts, currentContact: currentContact }
});
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;
}
}
});
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);
}
}
showEstimatedFees(): void {
const dialogRef = this.dialog.open(FeesDialogComponent, {
width: '500px',
data: { headerid: this.headerid }
});
cancelEditAddressForm(): void {
this.showAddressForm = false;
this.shippingForm.get('address')?.reset();
}
submitApplication(): void {
this.onSubmit(true);
}
onShipToChange(event: any): void {
const shipTo = event.value;
this.showAddressForm = false;
this.showContactForm = false;
returnToHome(): void {
this.navigationService.navigate(["home"])
}
processApplication(): void {
this.changeInProgress = true;
this.carnetService.processApplication(this.headerid).pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: () => {
this.notificationService.showSuccess('Application processed successfully');
this.navigationService.navigate(["home"]);
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to submit application');
this.notificationService.showError(errorMessage);
console.error('Error submitting the application', error);
}
});
if (shipTo === 'thirdParty') {
this.shippingForm.get('contact')?.reset();
this.shippingForm.get('address')?.reset();
this.showAddressForm = true;
this.showContactForm = true;
}
}
}

View File

@ -1,95 +0,0 @@
<div class="terms-container">
<mat-card class="terms-card">
<mat-card-header>
<mat-card-title>ATA Carnet Terms and Conditions</mat-card-title>
</mat-card-header>
<mat-divider></mat-divider>
<mat-card-content class="terms-content">
<p>Agreement language will go here</p>
<h3>Fees and Charges</h3>
<p> The following fees apply:</p>
<!-- Fee List with Table-like Structure -->
<div class="fee-table">
<div class="fee-row" *ngIf="estimatedFees.basicFee">
<span class="fee-label">Basic fee:</span>
<span class="fee-value">{{ estimatedFees.basicFee | currency }}</span>
</div>
<div class="fee-row" *ngIf="estimatedFees.counterFoilFee">
<span class="fee-label">Counterfoil Fee:</span>
<span class="fee-value">{{ estimatedFees.counterFoilFee | currency }}</span>
</div>
<div class="fee-row" *ngIf="estimatedFees.continuationSheetFee">
<span class="fee-label">Continuation sheet fee:</span>
<span class="fee-value">{{ estimatedFees.continuationSheetFee | currency }}</span>
</div>
<div class="fee-row" *ngIf="estimatedFees.expeditedFee">
<span class="fee-label">Expedited fee:</span>
<span class="fee-value">{{ estimatedFees.expeditedFee | currency }}</span>
</div>
<div class="fee-row" *ngIf="estimatedFees.shippingFee">
<span class="fee-label">Shipping fee:</span>
<span class="fee-value">{{ estimatedFees.shippingFee | currency }}</span>
</div>
<div class="fee-row" *ngIf="estimatedFees.bondPremium">
<span class="fee-label">{{ bondPremiumLabel }}:</span>
<span class="fee-value">{{ estimatedFees.bondPremium | currency }}</span>
</div>
<div class="fee-row" *ngIf="estimatedFees.cargoPremium">
<span class="fee-label">Cargo Premium:</span>
<span class="fee-value">{{ estimatedFees.cargoPremium | currency }}</span>
</div>
<div class="fee-row" *ngIf="estimatedFees.ldiPremium">
<span class="fee-label">LDI Premium:</span>
<span class="fee-value">{{ estimatedFees.ldiPremium | currency }}</span>
</div>
</div>
<hr />
<!-- Total -->
<div class="fee-table">
<div class="fee-row total">
<span class="fee-label">Total:</span>
<span class="fee-value">{{ total | currency }}</span>
</div>
</div>
<hr />
<!-- Security & Insurance -->
<div class="fee-table">
<div class="fee-row" *ngIf="estimatedFees.securityAmount">
<span class="fee-label">Security Amount:</span>
<span class="fee-value">{{ estimatedFees.securityAmount | currency }}</span>
</div>
<div class="fee-row" *ngIf="estimatedFees.insuredValue">
<span class="fee-label">Insured Value:</span>
<span class="fee-value">{{ estimatedFees.insuredValue | currency }}</span>
</div>
</div>
<mat-checkbox [(ngModel)]="hasReadAllTerms" color="primary" class="read-checkbox">
I have read and agree to all terms and conditions above
</mat-checkbox>
</mat-card-content>
<mat-divider></mat-divider>
<mat-card-actions align="end">
<button mat-raised-button color="primary" (click)="onAccept()"
[disabled]="!hasReadAllTerms || changeInProgress">
Accept
</button>
<button mat-button (click)="onDecline()">Decline</button>
</mat-card-actions>
</mat-card>
</div>

View File

@ -1,105 +0,0 @@
.terms-container {
display: flex;
justify-content: center;
align-items: center;
}
.terms-card {
width: 100%;
max-height: 90vh;
display: flex;
flex-direction: column;
}
mat-card-header {
justify-content: center;
padding: 16px 0;
}
.terms-content {
flex: 1;
overflow-y: auto;
padding: 16px;
font-size: 0.875rem;
h3 {
color: var(--mat-sys-primary);
margin-top: 24px;
margin-bottom: 12px;
}
p {
padding-left: 16px;
}
.fee-table {
width: 100%;
max-width: 325px;
}
.fee-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
padding-left: 2rem;
}
.fee-label {
text-align: left;
padding-right: 20px;
}
.fee-value {
text-align: right;
min-width: 120px;
white-space: nowrap;
}
strong {
font-weight: 500;
}
.total {
font-weight: bold;
margin-top: 12px;
}
}
.read-checkbox {
margin: 24px 0;
display: block;
padding: 8px 0;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
}
// Custom scrollbar
.terms-content::-webkit-scrollbar {
width: 8px;
}
.terms-content::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.terms-content::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.terms-content::-webkit-scrollbar-thumb:hover {
background: #555;
}
// Responsive adjustments
@media (max-width: 768px) {
.terms-container {
padding: 12px;
}
.terms-card {
max-height: 95vh;
}
}

View File

@ -1,155 +0,0 @@
import { Component, inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
import { NavigationService } from '../../core/services/common/navigation.service';
import { NotificationService } from '../../core/services/common/notification.service';
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';
import { ShippingService } from '../../core/services/carnet/shipping.service';
import { Shipping } from '../../core/models/carnet/shipping';
@Component({
selector: 'app-terms-conditions',
imports: [MatCardModule,
MatButtonModule,
MatDividerModule,
MatCheckboxModule,
CommonModule,
FormsModule],
templateUrl: './terms-conditions.component.html',
styleUrl: './terms-conditions.component.scss'
})
export class TermsConditionsComponent {
isLoading: boolean = false;
currentDate = new Date();
hasReadAllTerms = false;
currentApplicationDetails: { headerid: number, applicationName: string, applicationType: string } | null = null;
changeInProgress: boolean = false;
estimatedFees: Fees = {};
needCheckOut: boolean = false;
bondPremiumLabel: string = 'Bond Premium';
total: number = 0;
private notificationService = inject(NotificationService);
private errorHandler = inject(ApiErrorHandlerService);
private storageService = inject(StorageService);
private navigationService = inject(NavigationService);
private carnetService = inject(CarnetService);
private shippingService = inject(ShippingService);
constructor() {
this.currentApplicationDetails = this.storageService.get<{ headerid: number, applicationName: string, applicationType: string }>('currentapplication')
}
ngOnInit(): void {
if (this.currentApplicationDetails?.headerid) {
this.carnetService.getEstimatedFees(this.currentApplicationDetails?.headerid).subscribe({
next: (data: Fees) => {
if (data) {
this.estimatedFees = data;
if (this.estimatedFees.securityType === 'B') {
this.bondPremiumLabel = 'Bond Premium';
} else if (this.estimatedFees.securityType === 'C') {
this.bondPremiumLabel = 'Cash Deposit';
} else if (this.estimatedFees.securityType === 'G') {
this.bondPremiumLabel = 'Refundable claim deposit';
}
this.calculateTotal();
}
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to get estimated fees');
this.notificationService.showError(errorMessage);
console.error('Error getting estimated fees:', error);
}
});
this.getPaymentType(this.currentApplicationDetails?.headerid);
}
}
onAccept(): void {
if (this.needCheckOut) {
this.navigationService.navigate(['checkout']);
} else {
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"]);
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to submit application');
this.notificationService.showError(errorMessage);
console.error('Error submitting the application', error);
}
});
}
}
onDecline(): void {
this.storageService.removeItem('currentapplication');
let route: string = 'edit-carnet';
if (this.currentApplicationDetails?.applicationType === 'duplicate') {
route = 'duplicate-carnet';
}
else if (this.currentApplicationDetails?.applicationType === 'extend') {
route = 'extend-carnet';
} else if (this.currentApplicationDetails?.applicationType === 'additional') {
route = 'additional-sets';
}
this.navigationService.navigate([route, this.currentApplicationDetails?.headerid],
{
state: { isEditMode: true },
queryParams: {
applicationname: this.currentApplicationDetails?.applicationName,
return: 'shipping'
}
})
}
getPaymentType(headerid: number): void {
this.isLoading = true;
this.shippingService.getShippingData(headerid).pipe(
finalize(() => {
this.isLoading = false;
})
).subscribe({
next: (shippingData: Shipping) => {
if (!shippingData) { // do nothing if empty
return;
}
this.needCheckOut = shippingData.paymentMethod !== 'ACH' && shippingData.paymentMethod !== 'INV';
},
error: (error: any) => {
console.error('Error loading shipping data', error);
this.errorHandler.handleApiError(error, 'Failed to load shipping data');
}
});
}
calculateTotal(): void {
const excludedProperties = ['securityAmount', 'insuredValue', 'securityType'];
const validFees = Object.entries(this.estimatedFees)
.filter(([key]) => !excludedProperties.includes(key))
.map(([, value]) => value)
.filter((fee): fee is number => typeof fee === 'number');
this.total = validFees.reduce((total, fee) => total + fee, 0);
}
}

View File

@ -1,157 +1 @@
<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">
<mat-form-field appearance="outline" class="usa-entries">
<mat-label>No of times entering & leaving USA</mat-label>
<input matInput type="number" formControlName="usaEntries" min="1" max="99">
<mat-error *ngIf="travelForm.get('usaEntries')?.hasError('required')">
Required
</mat-error>
<mat-error
*ngIf="travelForm.get('usaEntries')?.hasError('min') || travelForm.get('usaEntries')?.hasError('max')">
Must be between 1-99
</mat-error>
</mat-form-field>
</div>
<div class="country-selection-container">
<!-- Available Countries Listbox -->
<div class="available-countries">
<h3>Visit Countries</h3>
<mat-selection-list [multiple]="false" hideSingleSelectionIndicator>
<mat-list-option *ngFor="let country of countries" [value]="country"
[disabled]="isVisitCountrySelected(country)"
(click)="onAvailableVisitCountrySelection(visitavailableoption)" #visitavailableoption>
{{country.name}}
</mat-list-option>
</mat-selection-list>
</div>
<!-- Controls -->
<div class="controls">
<mat-form-field appearance="outline">
<mat-label>No of times</mat-label>
<input matInput type="number" formControlName="visitsInput" min="1" max="99"
[(ngModel)]="visitsCount">
<mat-error
*ngIf="travelForm.get('visitsInput')?.hasError('min') || travelForm.get('visitsInput')?.hasError('max')">
Must be between 1-99
</mat-error>
</mat-form-field>
<div class="action-buttons">
<button mat-raised-button color="primary" type="button" (click)="addVisitCountry()"
*ngIf="!isViewMode" class="icon-end">
<mat-icon>keyboard_double_arrow_right</mat-icon>
<span> Add</span>
</button>
<button mat-raised-button type="button" (click)="removeVisitCountry()" *ngIf="!isViewMode">
<mat-icon>keyboard_double_arrow_left</mat-icon>
<span> Remove</span>
</button>
</div>
</div>
<!-- Selected Countries -->
<div class="selected-countries">
<div class="visit-countries">
<h3>Selected</h3>
<mat-selection-list [multiple]="false" hideSingleSelectionIndicator>
<mat-list-option *ngFor="let country of selectedVisitCountries" [value]="country"
(click)="onVisitCountrySelection(visitoption)" #visitoption>
{{country.name}} ({{country.visits}})
</mat-list-option>
<mat-list-item *ngIf="selectedVisitCountries.length === 0">
<span class="empty-message">No visit countries selected</span>
</mat-list-item>
</mat-selection-list>
</div>
</div>
</div>
<div class="country-selection-container">
<!-- Available Countries Listbox -->
<div class="available-countries">
<h3>Transit Countries</h3>
<mat-selection-list [multiple]="false" hideSingleSelectionIndicator>
<mat-list-option *ngFor="let country of countries" [value]="country"
[disabled]="isTransitCountrySelected(country)"
(click)="onAvailableTransitCountrySelection(transitavailableoption)" #transitavailableoption>
{{country.name}}
</mat-list-option>
</mat-selection-list>
</div>
<!-- Controls -->
<div class="controls">
<mat-form-field appearance="outline">
<mat-label>No of times</mat-label>
<input matInput type="number" formControlName="transitsInput" min="1" max="99"
[(ngModel)]="transitsCount">
<mat-error
*ngIf="travelForm.get('transitsInput')?.hasError('min') || travelForm.get('transitsInput')?.hasError('max')">
Must be between 1-99
</mat-error>
</mat-form-field>
<div class="action-buttons">
<button mat-raised-button color="primary" type="button" (click)="addTransitCountry()"
*ngIf="!isViewMode" class="icon-end">
<mat-icon>keyboard_double_arrow_right</mat-icon>
<span> Add</span>
</button>
<button mat-raised-button type="button" (click)="removeTransitCountry()" *ngIf="!isViewMode">
<mat-icon>keyboard_double_arrow_left</mat-icon>
<span> Remove</span>
</button>
</div>
</div>
<!-- Selected Countries -->
<div class="selected-countries">
<div class="transit-countries">
<h3>Selected</h3>
<mat-selection-list [multiple]="false" hideSingleSelectionIndicator>
<mat-list-option *ngFor="let country of selectedTransitCountries" [value]="country"
(click)="onTransitCountrySelection(transitoption)" #transitoption>
{{country.name}} ({{country.visits}})
</mat-list-option>
<mat-list-item *ngIf="selectedTransitCountries.length === 0">
<span class="empty-message">No transit countries selected</span>
</mat-list-item>
</mat-selection-list>
</div>
</div>
</div>
<div class="form-actions">
<!-- Totals -->
<div class="totals-section">
<div class="total-item">
<span>Total Visits:</span>
<span class="total-value">{{totalVisits}}</span>
</div>
<div class="total-item">
<span>Total Transits:</span>
<span class="total-value">{{totalTransits}}</span>
</div>
</div>
<div class="message-section" *ngIf="showAdditionalSetsValidationMessage">
<mat-error>
At least one of the sets to be entered. (either number of times entering & leaving, Foreign sets or
Transit sets)
</mat-error>
</div>
<button mat-raised-button color="primary" type="submit" *ngIf="!isViewMode" [disabled]="changeInProgress">
Save
</button>
</div>
</form>
</div>
<p>travel-plan works!</p>

View File

@ -1,147 +0,0 @@
.travel-plan-container {
padding: 0.75rem 0 0 0;
// max-width: 1200px;
margin: 0 auto;
// .form-row {
// margin-bottom: 24px;
// max-width: 300px;
// }
.form-row {
// display: flex;
// gap: 16px;
margin-bottom: 16px;
//align-items: start;
// mat-form-field {
// flex: 1;
// }
}
.usa-entries {
width: 300px;
}
.country-selection-container {
display: flex;
gap: 24px;
margin-bottom: 12px;
border: 1px solid;
border-radius: 4px;
padding: 20px;
@media (max-width: 768px) {
flex-direction: column;
}
}
.available-countries,
.selected-countries {
flex: 1;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 16px;
overflow: hidden;
h3 {
margin: 0 0 16px 0;
font-size: 0.875rem;
font-weight: 500;
}
mat-selection-list {
height: 150px;
overflow-y: auto;
padding: 0;
mat-list-item {
.empty-message {
font-style: italic;
}
}
}
}
.controls {
display: flex;
flex-direction: column;
gap: 16px;
width: 150px;
padding: 16px;
.action-buttons {
display: flex;
flex-direction: column;
gap: 8px;
button {
width: 100%;
display: flex;
// gap: 8px;
align-items: center;
}
mat-icon {
margin: 0;
}
button.icon-end {
flex-direction: row-reverse;
}
}
}
.selected-countries {
display: flex;
flex-direction: column;
gap: 16px;
.visit-countries,
.transit-countries {
flex: 1;
}
}
.form-actions {
display: flex;
justify-content: flex-end;
padding-top: 12px;
border-top: 1px solid #e0e0e0;
justify-content: space-between;
.totals-section {
display: flex;
gap: 24px;
font-size: 0.875rem;
.total-item {
display: flex;
gap: 8px;
align-items: center;
.total-value {
font-weight: 500;
}
}
}
.message-section {
font-size: 0.875rem;
}
}
}
@media (max-width: 768px) {
.travel-container {
.form-row {
flex-direction: column;
gap: 8px;
mat-form-field {
width: 100%;
}
}
}
}

View File

@ -1,355 +1,12 @@
import { Component, EventEmitter, inject, Input, Output, SimpleChanges } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
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';
import { Country, TravelEntry, TravelPlan } from '../../core/models/carnet/travel-plan';
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { CommonModule } from '@angular/common';
import { MatListModule } from '@angular/material/list';
import { MatDialog } from '@angular/material/dialog';
import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component';
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-travel-plan',
imports: [AngularMaterialModule, ReactiveFormsModule, CommonModule, MatListModule],
imports: [],
templateUrl: './travel-plan.component.html',
styleUrl: './travel-plan.component.scss'
})
export class TravelPlanComponent {
@Input() headerid: number = 0;
@Input() isViewMode = false;
@Input() applicationType: 'new' | 'edit' | 'additional' | 'duplicate' | 'extend' | null = 'edit';
@Output() completed = new EventEmitter<boolean>();
@Output() needsInsurance = new EventEmitter<boolean>();
private fb = inject(FormBuilder);
private dialog = inject(MatDialog);
private travelPlanService = inject(TravelPlanService);
private notificationService = inject(NotificationService);
private errorHandler = inject(ApiErrorHandlerService);
travelForm: FormGroup;
isLoading = false;
changeInProgress = false;
showAdditionalSetsValidationMessage: boolean = false;
visitsCount = 1;
transitsCount = 1;
countries: Country[] = [];
selectedVisitCountries: TravelEntry[] = [];
selectedTransitCountries: TravelEntry[] = [];
// Currently selected country in the listbox
selectedAvailableVisitCountry: Country | null = null;
selectedAvailableTransitCountry: Country | null = null;
selectedVisitCountry: Country | null = null;
selectedTransitCountry: Country | null = null;
constructor() {
this.travelForm = this.fb.group({
usaEntries: ['', [Validators.required, Validators.min(1), Validators.max(99)]],
visitsInput: ['', [Validators.min(1), Validators.max(99)]],
transitsInput: ['', [Validators.min(1), Validators.max(99)]],
});
}
ngOnInit(): void {
if (this.headerid) {
this.loadTravelPlan();
}
}
ngOnChanges(changes: SimpleChanges) {
if (changes['headerid'] && this.headerid > 0) {
this.loadTravelPlan();
}
}
onAvailableVisitCountrySelection(event: any) {
this.selectedAvailableVisitCountry = event.value;
}
onVisitCountrySelection(event: any) {
this.selectedVisitCountry = event.value;
}
onAvailableTransitCountrySelection(event: any) {
this.selectedAvailableTransitCountry = event.value;
}
onTransitCountrySelection(event: any) {
this.selectedTransitCountry = event.value;
}
// Add selected country to visits
addVisitCountry(): void {
if (!this.selectedAvailableVisitCountry) return;
if (this.selectedAvailableVisitCountry.actionType === 'FYI') {
this.fyiAction(this.selectedAvailableVisitCountry);
} else if (this.selectedAvailableVisitCountry.actionType === 'WARN') {
this.warningAction(this.selectedAvailableVisitCountry);
} else if (this.selectedAvailableVisitCountry.actionType === 'ACTION') {
this.takeAction(this.selectedAvailableVisitCountry);
} else {
this.addVisitCountryToCollection();
}
}
addVisitCountryToCollection(): void {
if (!this.selectedAvailableVisitCountry) return;
const existingIndex = this.selectedVisitCountries.findIndex(
c => c.value === this.selectedAvailableVisitCountry!.value
);
if (existingIndex === -1) {
this.selectedVisitCountries.push({
...this.selectedAvailableVisitCountry,
visits: this.visitsCount
});
if (this.applicationType === 'additional' || this.applicationType === 'extend') {
this.needsInsurance.emit(this.selectedVisitCountries.length > 0);
}
}
this.visitsCount = 1;
}
fyiAction(country: Country): void {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
width: '560px',
data: {
title: 'For your information',
message: country.actionMessage,
confirmText: 'Ok',
hideCancel: true
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.addVisitCountryToCollection();
}
});
}
warningAction(country: Country): void {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
width: '560px',
data: {
title: 'Warning',
message: country.actionMessage,
confirmText: 'Yes',
cancelText: 'No'
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.addVisitCountryToCollection();
}
});
}
takeAction(country: Country): void {
if (this.IsCountryExists('transit', country)) {
this.addVisitCountryToCollection();
return;
}
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
width: '560px',
data: {
title: 'Act',
message: country.actionMessage,
confirmText: 'Yes',
cancelText: 'No'
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.transitsCount = this.visitsCount;
this.selectedAvailableTransitCountry = this.selectedAvailableVisitCountry;
this.addVisitCountryToCollection();
this.addTransitCountryToCollection();
}
});
}
// Add selected country to transits
addTransitCountry(): void {
if (!this.selectedAvailableTransitCountry) return;
this.addTransitCountryToCollection();
}
addTransitCountryToCollection(): void {
if (!this.selectedAvailableTransitCountry) return;
const existingIndex = this.selectedTransitCountries.findIndex(
c => c.value === this.selectedAvailableTransitCountry!.value
);
if (existingIndex === -1) {
this.selectedTransitCountries.push({
...this.selectedAvailableTransitCountry,
visits: this.transitsCount
});
}
this.transitsCount = 1;
}
IsCountryExists(countryType: string, country: Country): boolean {
const existingIndex = countryType === 'transit' ? this.selectedTransitCountries.findIndex(
c => c.value === country!.value
) : this.selectedVisitCountries.findIndex(
c => c.value === country!.value
);
return existingIndex > -1;
}
// Remove country from visits
removeVisitCountry(): void {
if (!this.selectedVisitCountry) return;
this.selectedVisitCountries = this.selectedVisitCountries.filter(
c => c.value !== this.selectedVisitCountry!.value
);
if (this.applicationType === 'additional' || this.applicationType === 'extend') {
this.needsInsurance.emit(this.selectedVisitCountries.length > 0);
}
}
// Remove country from transits
removeTransitCountry(): void {
if (!this.selectedTransitCountry) return;
this.selectedTransitCountries = this.selectedTransitCountries.filter(
c => c.value !== this.selectedTransitCountry!.value
);
}
// Calculate total visits
get totalVisits(): number {
return this.selectedVisitCountries.reduce((sum, country) => sum + country.visits, 0);
}
// Calculate total transits
get totalTransits(): number {
return this.selectedTransitCountries.reduce((sum, country) => sum + country.visits, 0);
}
// Check if a country is already selected (visit)
isVisitCountrySelected(country: Country): boolean {
return this.selectedVisitCountries.some(c => c.value === country.value)
}
// Check if a country is already selected (transit)
isTransitCountrySelected(country: Country): boolean {
return this.selectedTransitCountries.some(c => c.value === country.value);
}
loadTravelPlan(): void {
this.isLoading = true;
forkJoin({
countries: this.travelPlanService.getCountriesAndMessages(),
travelPlanData: this.travelPlanService.getTravelPlan(this.headerid)
}).pipe(finalize(() => {
this.isLoading = false;
})).subscribe({
next: (results) => {
this.countries = results.countries as Country[];
this.patchTravelPlanData(results.travelPlanData);
},
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);
}
});
}
private patchTravelPlanData(plan: TravelPlan): void {
this.selectedVisitCountries = plan?.entries;
this.selectedTransitCountries = plan?.transits;
if (this.applicationType === 'additional' || this.applicationType === 'extend') {
this.needsInsurance.emit(this.selectedVisitCountries.length > 0)
}
// Patch form values
this.travelForm.patchValue({
usaEntries: plan?.usSets === 0 ? '' : plan?.usSets,
visitsInput: 1,
transitsInput: 1 // Reset to default
});
if (this.isViewMode) {
this.travelForm.disable();
}
if (this.applicationType === 'additional' || this.applicationType === 'extend') {
const usentriesControl = this.travelForm.get('usaEntries');
usentriesControl?.setValidators([Validators.min(0), Validators.max(99)]);
usentriesControl?.updateValueAndValidity();
}
this.completed.emit(this.travelForm.valid || this.travelForm.disabled
|| ((this.applicationType === 'additional' || this.applicationType === 'extend')
&& (this.travelForm.value.usaEntries
|| this.selectedVisitCountries.length > 0 || this.selectedTransitCountries.length > 0)));
}
onSubmit(): void {
if (this.travelForm.invalid) {
this.travelForm.markAllAsTouched();
return;
}
this.showAdditionalSetsValidationMessage = false;
if ((this.applicationType === 'additional' || this.applicationType === 'extend')
&& (!this.travelForm.value.usaEntries &&
this.selectedVisitCountries.length === 0 &&
this.selectedTransitCountries.length === 0
)) {
this.showAdditionalSetsValidationMessage = true;
return;
}
const travelPlan: TravelPlan = {
usSets: this.travelForm.value.usaEntries,
entries: this.selectedVisitCountries,
transits: this.selectedTransitCountries
};
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);
},
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);
}
});
}
}

View File

@ -1,51 +0,0 @@
<div class="carnet-action-buttons">
<button mat-button (click)="accordion().openAll()">Expand All</button>
<button mat-button (click)="accordion().closeAll()">Collapse All</button>
</div>
<mat-accordion class="carnet-headers-align" multi>
<mat-expansion-panel *ngIf="applicationType==='extend'">
<mat-expansion-panel-header>
<mat-panel-title> Extension Application </mat-panel-title>
</mat-expansion-panel-header>
<app-replacement-set [headerid]=" headerid" [isViewMode]="isViewMode">
</app-replacement-set>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title> Application Name </mat-panel-title>
</mat-expansion-panel-header>
<app-application [isViewMode]="isViewMode" [applicationName]="applicationName">
</app-application>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title> Holder Selection </mat-panel-title>
</mat-expansion-panel-header>
<app-holder [headerid]="headerid" [isViewMode]="isViewMode">
</app-holder>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title> Goods Section </mat-panel-title>
</mat-expansion-panel-header>
<app-goods [headerid]="headerid" [isViewMode]="isViewMode" [userPreferences]="userPreferences">
</app-goods>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title> Travel Plan </mat-panel-title>
</mat-expansion-panel-header>
<app-travel-plan [headerid]="headerid" [isViewMode]="isViewMode"></app-travel-plan>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title> Shipping & Payment </mat-panel-title>
</mat-expansion-panel-header>
<app-shipping [headerid]="headerid" [isViewMode]="isViewMode">
</app-shipping>
</mat-expansion-panel>
</mat-accordion>

View File

@ -1,8 +0,0 @@
.carnet-headers-align .mat-expansion-panel-header-description {
justify-content: space-between;
align-items: center;
}
.carnet-headers-align .mat-mdc-form-field+.mat-mdc-form-field {
margin-left: 8px;
}

View File

@ -1,55 +0,0 @@
import { afterNextRender, Component, inject, viewChild } from '@angular/core';
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { MatAccordion } from '@angular/material/expansion';
import { UserPreferences } from '../../core/models/user-preference';
import { CommonModule } from '@angular/common';
import { ActivatedRoute } from '@angular/router';
import { UserPreferencesService } from '../../core/services/user-preference.service';
import { ReactiveFormsModule } from '@angular/forms';
import { ApplicationComponent } from '../application/application.component';
import { GoodsComponent } from '../goods/goods.component';
import { HolderComponent } from '../holder/holder.component';
import { ShippingComponent } from '../shipping/shipping.component';
import { TravelPlanComponent } from '../travel-plan/travel-plan.component';
import { ReplacementSetComponent } from '../replacement-set/replacement-set.component';
@Component({
selector: 'app-view-carnet',
imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule,
ApplicationComponent, HolderComponent, GoodsComponent, TravelPlanComponent, ShippingComponent, ReplacementSetComponent],
templateUrl: './view-carnet.component.html',
styleUrl: './view-carnet.component.scss'
})
export class ViewCarnetComponent {
accordion = viewChild.required(MatAccordion);
isViewMode = true;
headerid: number = 0;
applicationName: string = '';
userPreferences: UserPreferences;
applicationType: 'new' | 'edit' | 'additional' | 'duplicate' | 'extend' | null = 'edit';
private route = inject(ActivatedRoute);
constructor(userPrefenceService: UserPreferencesService) {
this.userPreferences = userPrefenceService.getPreferences();
// set application type based on the route url
const urlSegments = this.route.snapshot.url.map(segment => segment.path);
if (urlSegments.includes('view-extend-carnet')) {
this.applicationType = 'extend';
}
afterNextRender(() => {
// Open all panels
this.accordion().openAll();
});
}
ngOnInit(): void {
const idParam = this.route.snapshot.paramMap.get('headerid');
this.headerid = idParam ? parseInt(idParam, 10) : 0;
this.applicationName = this.route.snapshot.queryParamMap.get('applicationname') || '';
}
}

View File

@ -1,4 +1,4 @@
<footer class="footer" [ngClass]="{'general': !isUserLoggedIn}">
<footer class="footer">
<div class="footer-content">
<div class="footer-links">
<a href="#">Privacy Policy</a>
@ -7,7 +7,7 @@
</div>
<div class="copyright">
&copy; {{ currentYear }} Alpha Omega Infosys. All rights reserved.
&copy; {{ currentYear }} USCIB Carnet Portal. All rights reserved.
</div>
</div>
</footer>

View File

@ -1,8 +1,9 @@
.footer {
color: var(--mat-sys-on-primary);
background: var(--mat-sys-primary);
color: var(--mat-sys-primary);
background: var(--mat-sys-primary-container);
padding: 8px;
border-top: 1px solid var(--mat-sys-outline-variant);
backdrop-filter: blur(10px);
/* Fixed footer styles */
position: fixed;
@ -19,7 +20,6 @@
flex-direction: column;
align-items: center;
gap: 8px;
background: var(--mat-sys-primary);
}
.footer-links {
@ -27,46 +27,22 @@
gap: 24px;
a {
color: var(--mat-sys-on-primary);
background: var(--mat-sys-primary);
color: var(--mat-sys-secondary);
text-decoration: none;
transition: color 0.3s ease;
&:hover {
transform: scale(103%);
color: var(--mat-sys-primary);
}
}
}
.copyright {
color: var(--mat-sys-secondary);
font-size: 12px;
}
}
/* footer styles - not logged in users */
.footer.general {
color: #000000;
background: #ffffff;
.footer-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
background: #ffffff;
}
.footer-links {
a {
color: #000000;
background: #ffffff;
}
}
}
@media (max-width: 600px) {
.footer {
padding: 16px;

View File

@ -1,15 +1,12 @@
import { Component, Input } from '@angular/core';
import { Component } from '@angular/core';
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-footer',
imports: [AngularMaterialModule, CommonModule],
imports: [AngularMaterialModule],
templateUrl: './footer.component.html',
styleUrl: './footer.component.scss'
})
export class FooterComponent {
currentYear = new Date().getFullYear();
@Input() isUserLoggedIn = false;
}

View File

@ -6,7 +6,18 @@
<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">
<mat-icon>account_circle</mat-icon>
@ -18,11 +29,6 @@
<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

@ -4,9 +4,9 @@
align-items: center;
padding: 0 24px;
height: 64px;
color: var(--custom-nav-color);
background: var(--custom-nav-background-color);
border-bottom: 1px solid var(--mat-sys-outline-variant);
color: var(--mat-sys-primary);
background: var(--mat-sys-primary-container);
box-shadow: var(--mat-sys-level2);
position: relative;
z-index: 100; // Higher than footer's z-index
@ -31,20 +31,6 @@
align-items: center;
gap: 24px;
button {
color: var(--custom-nav-color);
background-color: var(--custom-nav-background-color);
&:hover {
color: var(--custom-nav-background-color);
background-color: var(--custom-nav-color);
}
}
.mat-mdc-button:hover>.mat-mdc-button-persistent-ripple::before {
opacity: 0;
}
// .nav-link {
// cursor: pointer;
// padding: 8px 12px;
@ -62,21 +48,15 @@
position: relative;
.profile-button {
color: var(--custom-nav-background-color);
background-color: var(--custom-nav-color);
&:hover {
color: var(--custom-nav-color);
background-color: var(--custom-nav-background-color);
}
color: var(--mat-sys-primary);
background: var(--mat-sys-primary-container);
}
.profile-menu {
position: absolute;
right: 0;
top: 48px;
color: var(--mat-sys-on-primary-container);
background-color: var(--mat-sys-primary-container);
background-color: var(--mat-sys-secondary-container);
border-radius: 4px;
box-shadow: var(--mat-sys-level3);
min-width: 200px;
@ -88,12 +68,12 @@
align-items: center;
gap: 8px;
padding: 12px 16px;
color: var(--mat-sys-on-primary-container);
background-color: var(--mat-sys-primary-container);
background-color: #fff;
color: var(--mat-sys-secondary);
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
mat-icon {
color: var(--mat-sys-on-primary-container);
color: var(--mat-sys-secondary);
}
}
@ -105,16 +85,15 @@
padding: 8px 16px;
border: none;
cursor: pointer;
color: var(--mat-sys-on-primary-container);
background-color: var(--mat-sys-primary-container);
background-color: var(--mat-sys-surface-container);
color: var(--mat-sys-secondary);
// mat-icon {
// color: var(--mat-sys-on-primary-container);
// }
mat-icon {
color: var(--mat-sys-secondary);
}
&:hover {
color: var(--mat-sys-on-primary);
background-color: var(--mat-sys-primary);
background-color: var(--mat-sys-secondary-container);
}
}
}

View File

@ -1,13 +0,0 @@
export interface Fees {
basicFee?: number | null | undefined;
counterFoilFee?: number | null | undefined;
continuationSheetFee?: number | null | undefined;
expeditedFee?: number | null | undefined;
shippingFee?: number | null | undefined;
bondPremium?: number | null | undefined;
cargoPremium?: number | null | undefined;
ldiPremium?: number | null | undefined;
securityAmount?: number | null | undefined;
securityType?: string | null | undefined;
insuredValue?: number | null | undefined;
}

View File

@ -2,8 +2,8 @@ export interface Goods {
commercialSample: boolean;
professionalEquipment: boolean;
exhibitionsFair: boolean;
roadVehiclesUsed: string;
horseUsed: string;
roadVehiclesUsed: boolean;
horseUsed: boolean;
authorizedRepresentatives: string;
items?: GoodsItem[] | null;
}

View File

@ -1,16 +1,2 @@
export interface Holder {
holderid: number;
locationid?: number;
holderName: string;
dbaName?: string | null;
holderNumber: string;
holderType: string;
uscibMember: boolean;
govAgency?: boolean;
address1: string;
address2: string;
city: string;
state: string;
country: string;
zip: string;
}

View File

@ -1,9 +0,0 @@
export interface PaymentDetail {
headerid?: number;
applicationName?: string;
price?: string;
description?: string;
orderId?: string;
paymentStatus?: string;
paymentErrorDetails?: string;
}

View File

@ -1,15 +0,0 @@
export interface Replacement {
replacementCarnetNumber: string;
issueDate: Date;
preparerName: string;
address1: string;
address2?: string | null;
city: string;
state: string;
country: string;
zip: string;
portName: string;
portCountry: string;
reasonForExtension: string;
duration: number;
}

View File

@ -1,6 +1,4 @@
export interface ShippingAddress {
companyName: string;
addressid: number;
address1: string;
address2?: string | null;
city: string;

View File

@ -1,5 +1,4 @@
export interface ShippingContact {
contactid: number;
firstName: string;
lastName: string;
middleInitial?: string | null;
@ -10,6 +9,4 @@ export interface ShippingContact {
email: string;
refNumber?: string | null;
notes?: string | null;
defaultContact?: boolean;
isInactive?: boolean;
}

View File

@ -3,10 +3,9 @@ import { ShippingContact } from "./shipping-contact";
export interface Shipping {
// Insurance details
// needsBond: boolean;
needsBond: boolean;
needsInsurance: boolean;
needsLostDocProtection: boolean;
formOfSecurity: string;
// Shipping details
shipTo: string;
@ -20,5 +19,5 @@ export interface Shipping {
// Payment details
paymentMethod: string;
paymentNotes?: string;
notes?: string;
}

View File

@ -1,17 +1,2 @@
export interface TravelPlan {
usSets?: number;
entries: TravelEntry[];
transits: TravelEntry[];
}
export interface Country {
name?: string;
value: string;
actionMessage?: string | null;
actionType?: string | null;
action?: string | null;
}
export interface TravelEntry extends Country {
visits: number;
}

View File

@ -1,5 +0,0 @@
export interface ContactTitle {
name: string;
id: string;
value: string;
}

View File

@ -2,6 +2,4 @@ export interface DeliveryType {
name: string;
id: string;
value: string;
daysToDelivery: string;
cutOffTime: string;
}

View File

@ -1,5 +0,0 @@
export interface ExtensionReason {
name: string;
id: string;
value: string;
}

View File

@ -1,6 +0,0 @@
export interface FormOfSecurity {
name: string;
id: string;
value: string;
isGov: boolean;
}

View File

@ -1,18 +0,0 @@
export interface BasicDetail {
holderid?: number;
spid: number;
locationid: number;
holderName: string;
dbaName?: string | null;
holderNumber: string;
holderType: string;
uscibMember: boolean;
govAgency?: boolean;
address1: string;
address2: string;
city: string;
state: string;
country: string;
zip: string;
isInactive?: boolean | null;
}

View File

@ -1,19 +1,30 @@
export interface Contact {
holdercontactid: number;
holderid: number;
spid: number;
export interface HolderContact {
HOLDERCONTACTID: number;
HOLDERID: number;
SPID: number;
FIRSTNAME: string;
LASTNAME: string;
MIDDLEINITIAL: string | null;
TITLE: string;
PHONE: string;
MOBILE: string;
FAX: string | null;
EMAILADDRESS: string;
DATECREATED: string;
CREATEDBY: string;
INACTIVEFLAG: string;
INACTIVEDATE: string | null;
LASTUPDATEDBY: string | null;
LASTUPDATEDDATE: string | null;
}
export interface ContactFormModel {
firstName: string;
lastName: string;
middleInitial: string | null;
middleInitial: string;
title: string;
phone: string;
mobile: string;
fax?: string | null;
fax: string;
email: string;
dateCreated?: Date | null;
createdBy?: string | null;
lastUpdatedBy?: string | null;
lastUpdatedDate?: Date | null;
isInactive?: boolean | null;
inactivatedDate?: Date | null;
}
};

View File

@ -0,0 +1,40 @@
export interface HolderDetail {
HOLDERID?: number;
SPID: number;
LOCATIONID: number;
HOLDERNO: string;
HOLDERTYPE: string;
USCIBMEMBERFLAG: string;
GOVAGENCYFLAG: string;
HOLDERNAME: string;
NAMEQUALIFIER: string | null;
ADDLNAME: string | null;
ADDRESS1: string;
ADDRESS2: string | null;
CITY: string;
STATE: string;
ZIP: string;
COUNTRY: string;
DATECREATED: string;
CREATEDBY: string;
INACTIVEFLAG: string;
INACTIVEDATE: string | null;
LASTUPDATEDBY: string | null;
LASTUPDATEDDATE: string | null;
}
export interface HolderDetailFormType {
holderName: string;
DBAName: string;
HolderNumber: string;
holderType: string;
USCIBMember: string;
govAgency: string;
address1: string;
address2: string;
city: string;
state: string;
country: string;
zip: string;
}

View File

@ -1,4 +0,0 @@
export interface HolderFilter {
holderName?: string;
holderId?: number;
}

View File

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

View File

@ -3,7 +3,7 @@ import { environment } from '../../../../environments/environment';
import { HttpClient } from '@angular/common/http';
import { UserService } from '../common/user.service';
import { ApplicationDetail } from '../../models/carnet/application-detail';
import { Observable } from 'rxjs';
import { filter, map, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
@ -15,6 +15,21 @@ export class ApplicationDetailService {
private http = inject(HttpClient);
private userService = inject(UserService);
// getApplicationDetailsById(id: number): ApplicationDetail | any {
// return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetPreparerByClientid/${this.userService.getUserSpid()}/${id}`).pipe(
// filter(response => response.length > 0),
// map(response => this.mapToApplicationDetail(response?.[0])));
// }
// private mapToApplicationDetail(applicationDetails: any): ApplicationDetail {
// return {
// clientid: applicationDetails.CLIENTID,
// spid: applicationDetails.SPID,
// name: applicationDetails.P_APPLICATIONNAME,
// applicationId: applicationDetails.P_HEADERID
// };
// }
createApplicationDetails(data: ApplicationDetail): Observable<any> {
const userDetails = this.userService.getUserDetails();

View File

@ -0,0 +1,16 @@
// appli.service.ts
import { Injectable, signal } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CarnetStateStore {
headerid = signal<number>(0);
applicationName = signal<string>('');
resetState() {
this.headerid.set(0);
this.applicationName.set('');
}
}

View File

@ -1,140 +0,0 @@
import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { Observable, map } from 'rxjs';
import { environment } from '../../../../environments/environment';
import { UserService } from '../common/user.service';
import { Fees } from '../../models/carnet/fee';
@Injectable({
providedIn: 'root'
})
export class CarnetService {
private apiUrl = environment.apiUrl;
private apiDb = environment.apiDb;
private http = inject(HttpClient);
private userService = inject(UserService);
getEstimatedFees(headerid: number): Observable<Fees> {
return this.http.get<any>(`${this.apiUrl}/${this.apiDb}/EstimatedFees/${this.userService.getUserSpid()}/${this.userService.getUser()}/${headerid}`).pipe(
map(response => this.mapToFeesData(response)));
}
private mapToFeesData(estimatedFeesDetails: any): Fees {
let estimatedFeesData: Fees = {
basicFee: estimatedFeesDetails.BASICFEE,
counterFoilFee: estimatedFeesDetails.CFFEE,
continuationSheetFee: estimatedFeesDetails.CSFEE,
expeditedFee: estimatedFeesDetails.EFFEE,
shippingFee: estimatedFeesDetails.SHIPFEE,
bondPremium: estimatedFeesDetails.BONDPREMIUM,
cargoPremium: estimatedFeesDetails.CARGOPREMIUM,
ldiPremium: estimatedFeesDetails.LDIPREMIUM,
securityAmount: estimatedFeesDetails.SECURITYAMOUNT,
securityType: estimatedFeesDetails.SECURITYTYPE,
insuredValue: estimatedFeesDetails.INSUREDVALUE
};
return estimatedFeesData;
}
submitApplication(headerid: number): Observable<any> {
let appData = {
P_SPID: this.userService.getUserSpid(),
P_USERID: this.userService.getUser(),
P_HEADERID: headerid
}
return this.http.post<any>(`${this.apiUrl}/${this.apiDb}/TransmitApplicationtoProcess`, appData);
}
processApplication(headerid: number): Observable<any> {
let appData = {
// P_SPID: this.userService.getUserSpid(),
P_USERID: this.userService.getUser(),
P_HEADERID: headerid
}
return this.http.patch<any>(`${this.apiUrl}/${this.apiDb}//ProcessCarnet`, appData);
}
deleteCarnet(headerid: number): Observable<any> {
const data = {
P_USERID: this.userService.getUser(),
P_HEADERID: headerid
}
return this.http.delete(`${this.apiUrl}/${this.apiDb}/DeleteCarnet`, { body: data });
}
printCarnet(headerid: number): Observable<any> {
const data = {
P_SPID: this.userService.getUserSpid(),
P_HEADERID: headerid,
P_PRINTGL: 'Y'
}
return this.http.post(`${this.apiUrl}/${this.apiDb}/PrintCarnet`, data, {
responseType: 'blob', // Important: Set responseType to 'blob'
});
}
printGeneralList(headerid: number): Observable<any> {
const data = {
P_SPID: this.userService.getUserSpid(),
P_HEADERID: headerid
}
return this.http.post(`${this.apiUrl}/${this.apiDb}/PrintGL`, data, {
responseType: 'blob', // Important: Set responseType to 'blob'
});
}
duplicateCarnet(carnetNumber: string): Observable<any> {
const data = {
P_USERID: this.userService.getUser(),
P_CARNETNO: carnetNumber
}
return this.http.patch(`${this.apiUrl}/${this.apiDb}/DuplicateCarnet`, data);
}
extendCarnet(carnetNumber: string): Observable<any> {
const data = {
P_USERID: this.userService.getUser(),
P_CARNETNO: carnetNumber
}
return this.http.patch(`${this.apiUrl}/${this.apiDb}/ExtendCarnet`, data);
}
additionalSets(carnetNumber: string): Observable<any> {
const data = {
P_USERID: this.userService.getUser(),
P_CARNETNO: carnetNumber
}
return this.http.patch(`${this.apiUrl}/${this.apiDb}/AddlSets`, data);
}
copyCarnet(headerid: number, applicationName: string): Observable<any> {
const data = {
P_USERID: this.userService.getUser(),
P_HEADERID: headerid,
P_APPLICATIONNAME: applicationName
}
return this.http.patch(`${this.apiUrl}/${this.apiDb}/CopyCarnet`, data);
}
resetCarnet(headerid: number): Observable<any> {
const data = {
P_USERID: this.userService.getUser(),
P_HEADERID: headerid
}
return this.http.patch(`${this.apiUrl}/${this.apiDb}/ResetCarnet`, data);
}
voidCarnet(headerid: number): Observable<any> {
const data = {
P_USERID: this.userService.getUser(),
P_HEADERID: headerid
}
return this.http.patch(`${this.apiUrl}/${this.apiDb}/VoidCarnet`, data);
}
}

View File

@ -3,7 +3,7 @@ import { environment } from '../../../../environments/environment';
import { HttpClient } from '@angular/common/http';
import { UserService } from '../common/user.service';
import { Goods, GoodsItem } from '../../models/carnet/goods';
import { map, Observable } from 'rxjs';
import { filter, map, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
@ -17,13 +17,14 @@ export class GoodsService {
getGoodDetailsByHeaderId(headerid: number): Goods | any {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetGoodsDetailstoEdit/${this.userService.getUserSpid()}/${this.userService.getUser()}/${headerid}`).pipe(
map(response => this.mapToGoods(response)));
filter(response => response.length > 0),
map(response => this.mapToGoods(response?.[0])));
}
private mapToGoods(goodDetails: any): Goods {
return {
roadVehiclesUsed: goodDetails.AUTOFLAG,
horseUsed: goodDetails.HORSEFLAG,
roadVehiclesUsed: goodDetails.AUTOFLAG === 'Y',
horseUsed: goodDetails.HORSEFLAG === 'Y',
exhibitionsFair: goodDetails.EXHIBITIONSFAIRFLAG === 'Y',
professionalEquipment: goodDetails.PROFEQUIPMENTFLAG === 'Y',
commercialSample: goodDetails.COMMERCIALSAMPLEFLAG === 'Y',
@ -56,8 +57,8 @@ export class GoodsService {
P_COMMERCIALSAMPLEFLAG: goodsData.commercialSample ? 'Y' : 'N',
P_PROFEQUIPMENTFLAG: goodsData.professionalEquipment ? 'Y' : 'N',
P_EXHIBITIONSFAIRFLAG: goodsData.exhibitionsFair ? 'Y' : 'N',
P_AUTOFLAG: goodsData.roadVehiclesUsed,
P_HORSEFLAG: goodsData.horseUsed,
P_AUTOFLAG: goodsData.roadVehiclesUsed ? 'Y' : 'N',
P_HORSEFLAG: goodsData.horseUsed ? 'Y' : 'N',
P_AUTHREP: goodsData.authorizedRepresentatives,
}
@ -108,20 +109,19 @@ export class GoodsService {
deleteGoodsItem(headerid: number, item: GoodsItem): Observable<any> {
const goodsItem = {
ITEMNO: item.itemNumber,
ITEMDESCRIPTION: item.description,
NOOFPIECES: item.pieces,
ITEMWEIGHT: item.weight,
ITEMWEIGHTUOM: item.unitOfMeasure,
ITEMVALUE: item.value,
GOODSORIGINCOUNTRY: item.countryOfOrigin,
}
const goodsItemsObj = {
P_HEADERID: headerid,
P_ITEMNO: item.itemNumber,
P_USERID: this.userService.getUser(),
};
// delete request with body
return this.http.delete(`${this.apiUrl}/${this.apiDb}/DeleteGenerallistItems`, { body: goodsItemsObj });
}
deleteAllGoodsItems(headerid: number): Observable<any> {
const goodsItemsObj = {
P_HEADERID: headerid,
P_ITEMNO: 99999, // special value to indicate all items
P_GLTABLE: [goodsItem],
P_USERID: this.userService.getUser(),
};

View File

@ -1,51 +1,9 @@
import { inject, Injectable } from '@angular/core';
import { environment } from '../../../../environments/environment';
import { HttpClient } from '@angular/common/http';
import { UserService } from '../common/user.service';
import { Holder } from '../../models/carnet/holder';
import { map, Observable } from 'rxjs';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class HolderService {
private apiUrl = environment.apiUrl;
private apiDb = environment.apiDb;
private http = inject(HttpClient);
private userService = inject(UserService);
getHolder(id: number): Observable<Holder> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetHolderstoEdit/${this.userService.getUserSpid()}/${this.userService.getUser()}/${id}`).pipe(
map(response => this.mapToHolder(response)));
}
private mapToHolder(holder: any): Holder {
return {
holderid: holder.HOLDERID,
locationid: holder.LOCATIONID,
holderNumber: holder.HOLDERNO,
holderType: holder.HOLDERTYPE,
uscibMember: holder.USCIBMEMBERFLAG === 'Y',
// govAgency: holder.GOVAGENCYFLAG === 'Y',
holderName: holder.NAMEOF,
//NAMEQUALIFIER: holder.NAMEQUALIFIER || null,
dbaName: holder.ADDLNAME || null,
address1: holder.ADDRESS1,
address2: holder.ADDRESS2 || null,
city: holder.CITY,
state: holder.STATE,
zip: holder.ZIP,
country: holder.COUNTRY
};
}
saveApplicationHolder(headerId: number, holderId: number) {
const applicationHolder = {
P_HEADERID: headerId,
P_HOLDERID: holderId
}
return this.http.patch(`${this.apiUrl}/${this.apiDb}/update-holder`, applicationHolder);
}
constructor() { }
}

View File

@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class InsuranceService {
constructor() { }
}

View File

@ -1,56 +0,0 @@
import { inject, Injectable } from '@angular/core';
import { environment } from '../../../../environments/environment';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import { PaymentDetail } from '../../models/carnet/payment-details';
import { UserService } from '../common/user.service';
@Injectable({
providedIn: 'root'
})
export class PaymentService {
private apiUrl = environment.apiUrl;
private apiDb = environment.apiDb;
private http = inject(HttpClient);
private userService = inject(UserService);
createOrder(paymentDetails: PaymentDetail): Promise<string> {
const data = {
P_APPLICATIONNAME: paymentDetails.applicationName,
P_PRICE: paymentDetails.price,
P_DESCRIPTION: paymentDetails.description,
P_USERID: this.userService.getUser()
}
return firstValueFrom(
this.http.post<{ id: string }>(`${this.apiUrl}/${this.apiDb}/InitiatePayment`, data)
).then(order => order.id);
}
completePayment(paymentDetails: PaymentDetail): Promise<any> {
const data = {
P_ORDERID: paymentDetails.orderId,
P_APPLICATIONNAME: paymentDetails.applicationName,
P_PRICE: paymentDetails.price,
P_USERID: this.userService.getUser()
}
return firstValueFrom(
this.http.post(`${this.apiUrl}/${this.apiDb}/CompletePayment`, data));
}
logTransactionDetails(paymentDetails: PaymentDetail): Promise<any> {
const data = {
P_APPLICATIONNAME: paymentDetails.applicationName,
P_ORDERID: paymentDetails.orderId,
P_PAYMENTAMOUNT: paymentDetails.price,
P_STATUS: paymentDetails.paymentStatus,
P_USERID: this.userService.getUser(),
P_PAYMENTERROR: paymentDetails.paymentErrorDetails
}
return firstValueFrom(
this.http.post(`${this.apiUrl}/${this.apiDb}/SaveHistory`, data));
}
}

View File

@ -1,59 +0,0 @@
import { inject, Injectable } from '@angular/core';
import { environment } from '../../../../environments/environment';
import { HttpClient } from '@angular/common/http';
import { map, Observable } from 'rxjs';
import { UserService } from '../common/user.service';
import { Replacement } from '../../models/carnet/replacement';
@Injectable({
providedIn: 'root'
})
export class ReplacementService {
private apiUrl = environment.apiUrl;
private apiDb = environment.apiDb;
private http = inject(HttpClient);
private userService = inject(UserService);
getExtensionData(headerid: number): Replacement | any {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetExtendedSection/${this.userService.getUserSpid()}/${headerid}`).pipe(
map(response => this.mapToExtensionData(response)));
}
private mapToExtensionData(extensionDataCollection: any): Replacement {
let extensionData = extensionDataCollection;
let replacementData: Replacement = {
replacementCarnetNumber: extensionData.REPLACECARNETNO,
issueDate: extensionData.ISSUEDATE,
preparerName: extensionData.PREPARERNAME,
address1: extensionData.ADDRESS1,
address2: extensionData.ADDRESS2,
city: extensionData.CITY,
state: extensionData.STATE,
country: extensionData.COUNTRY,
zip: extensionData.ZIP,
portName: extensionData.GOODSPORT,
portCountry: extensionData.GOODSCOUNTRY,
reasonForExtension: extensionData.REASONCODE,
duration: extensionData.EXTENSIONPERIOD,
};
return replacementData;
}
saveExtensionDetails(headerid: number, extensionData: Replacement): Observable<any> {
const extensionDetails = {
P_HEADERID: headerid,
P_GOODSPORT: extensionData.portName,
P_GOODSCOUNTRY: extensionData.portCountry,
P_REASONCODE: extensionData.reasonForExtension,
P_EXTENSIONPERIOD: extensionData.duration,
P_USERID: this.userService.getUser(),
P_SPID: this.userService.getUserSpid()
}
return this.http.post(`${this.apiUrl}/${this.apiDb}/SaveExtensionApplication`, extensionDetails);
}
}

View File

@ -1,12 +1,9 @@
import { inject, Injectable } from '@angular/core';
import { environment } from '../../../../environments/environment';
import { HttpClient } from '@angular/common/http';
import { filter, map, Observable, of } from 'rxjs';
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';
import { Holder } from '../../models/carnet/holder';
@Injectable({
providedIn: 'root'
@ -20,7 +17,8 @@ export class ShippingService {
getShippingData(headerid: number): Shipping | any {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetShipPaymentDetailstoEdit/${this.userService.getUserSpid()}/${this.userService.getUser()}/${headerid}`).pipe(
map(response => this.mapToShippingData(response)));
filter(response => response.length > 0),
map(response => this.mapToShippingData(response?.[0])));
}
private mapToShippingData(shippingDetails: any): Shipping {
@ -32,35 +30,35 @@ export class ShippingService {
courierAccount: shippingDetails.CUSTCOURIERNO,
paymentMethod: shippingDetails.PAYMENTMETHOD,
//needsBond: shippingDetails.BONDPROTECTION === 'Y',
needsInsurance: shippingDetails.INSPROTECTION === 'ACCEPT',
needsLostDocProtection: shippingDetails.LDIPROTECTION === 'ACCEPT',
formOfSecurity: shippingDetails.FORMOFSECURITY
notes: shippingDetails.NOTES,
needsBond: shippingDetails.INSPROTECTION === 'Y',
needsInsurance: shippingDetails.LDIPROTECTION === 'Y',
needsLostDocProtection: shippingDetails.LDIPROTECTION === 'Y'
};
shippingData.address = {
companyName: shippingDetails.SHIPNAME,
addressid: shippingDetails.SHIPADDRESSID,
address1: shippingDetails.ADDRESS1,
address2: shippingDetails.ADDRESS2,
city: shippingDetails.CITY,
state: shippingDetails.STATE,
zip: shippingDetails.ZIP,
country: shippingDetails.COUNTRY
if (shippingDetails.address) {
shippingData.address = {
address1: shippingDetails.ADDRESS1,
address2: shippingDetails.ADDRESS2,
city: shippingDetails.CITY,
state: shippingDetails.STATE,
zip: shippingDetails.ZIP,
country: shippingDetails.COUNTRY
}
}
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,
if (shippingDetails.contact) {
shippingData.contact = {
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,
}
}
return shippingData;
@ -75,83 +73,31 @@ export class ShippingService {
P_DELIVERYMETHOD: shippingData.deliveryMethod,
P_CUSTCOURIERNO: shippingData.courierAccount,
P_PAYMENTMETHOD: shippingData.paymentMethod,
P_NOTES: shippingData.notes,
// P_BONDPROTECTION: shippingData.needsBond ? 'Y' : 'N',
P_INSPROTECTION: shippingData.needsInsurance ? 'ACCEPT' : 'DECLINE',
P_LDIPROTECTION: shippingData.needsLostDocProtection ? 'ACCEPT' : 'DECLINE',
P_FORMOFSECURITY: shippingData.formOfSecurity,
P_FORMOFSECURITY: shippingData.needsBond ? 'Y' : 'N',
P_INSPROTECTION: shippingData.needsInsurance ? 'Y' : 'N',
P_LDIPROTECTION: shippingData.needsLostDocProtection ? 'Y' : 'N',
P_SHIPNAME: shippingData.address?.companyName,
P_ADDRESS1: shippingData.address?.address1 || null,
P_ADDRESS2: shippingData.address?.address2 || null,
P_CITY: shippingData.address?.city || null,
P_STATE: shippingData.address?.state || null,
P_ZIP: shippingData.address?.zip || null,
P_COUNTRY: shippingData.address?.country || null,
address1: shippingData.address?.address1 || '',
address2: shippingData.address?.address2 || null,
city: shippingData.address?.city || '',
state: shippingData.address?.state || '',
zip: shippingData.address?.zip || '',
country: shippingData.address?.country || '',
P_SHIPCONTACTID: shippingData.contact?.contactid || 0,
P_FIRSTNAME: shippingData.contact?.firstName || '',
P_LASTNAME: shippingData.contact?.lastName || '',
P_MIDDLEINITIAL: shippingData.contact?.middleInitial || null,
P_TITLE: shippingData.contact?.title || '',
P_PHONENO: shippingData.contact?.phone || '',
P_MOBILENO: shippingData.contact?.mobile || '',
P_FAXNO: shippingData.contact?.fax || null,
P_EMAILADDRESS: shippingData.contact?.email || '',
P_REFNO: shippingData.contact?.refNumber || '',
P_NOTES: shippingData.contact?.notes,
firstName: shippingData.contact?.firstName || '',
lastName: shippingData.contact?.lastName || '',
middleInitial: shippingData.contact?.middleInitial || null,
title: shippingData.contact?.title || '',
phone: shippingData.contact?.phone || '',
mobile: shippingData.contact?.mobile || '',
fax: shippingData.contact?.fax || null,
emailAddress: shippingData.contact?.email || '',
refNo: shippingData.contact?.refNumber || '',
P_USERID: this.userService.getUser()
}
return this.http.patch(`${this.apiUrl}/${this.apiDb}/UpdateShippingDetails`, shippingDetails);
}
getPreparerContacts(): ShippingContact[] | any {
return this.http.get<any[]>(`${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<any[]>(`${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 ?? contact.HOLDERCONTACTID,
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<any[]>(`${this.apiUrl}/${this.apiDb}/GetPreparerLocByClientid/${this.userService.getUserSpid()}/${this.userService.getUserClientid()}`).pipe(
filter(response => response.length > 0),
map(response => this.mapToAddress(response?.[0])));
}
getHolderAddress(holder: Holder): ShippingAddress | any {
return of(this.mapToAddress(holder));
}
private mapToAddress(address: any): ShippingAddress {
return {
companyName: address.NAMEOF ?? address.holderName,
addressid: address.LOCATIONID ?? address.locationid,
address1: address.ADDRESS1 ?? address.address1,
address2: address.ADDRESS2 ?? address.address2,
city: address.CITY ?? address.city,
state: address.STATE ?? address.state,
zip: address.ZIP ?? address.zip,
country: address.COUNTRY ?? address.country
};
}
}

View File

@ -1,93 +1,9 @@
import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { Observable, map } from 'rxjs';
import { environment } from '../../../../environments/environment';
import { Country, TravelPlan } from '../../models/carnet/travel-plan';
import { UserService } from '../common/user.service';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class TravelPlanService {
private apiUrl = environment.apiUrl;
private apiDb = environment.apiDb;
private http = inject(HttpClient);
private userService = inject(UserService);
getCountriesAndMessages(): Observable<Country[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetCountriesAndMessages/${this.userService.getUserSpid()}`).pipe(
map((response) =>
response.map((item) => ({
name: item.COUNTRYNAME,
value: item.COUNTRYCODE,
actionMessage: item.COUNTRYMESSAGE,
actionType: item.ADDLPARAMVALUE1,
action: item.ADDLPARAMVALUE2
}))
)
);
}
getTravelPlan(headerid: number): Observable<TravelPlan> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetCountryDetailstoEdit/${this.userService.getUserSpid()}/${this.userService.getUser()}/${headerid}`).pipe(
map(response => this.mapToTravelPlanData(response)));
}
private mapToTravelPlanData(travelPlanDetails: any): TravelPlan {
if (!travelPlanDetails?.P_COUNTRYTABLE && !travelPlanDetails?.NOOFUSSETS) {
return {
entries: [],
transits: []
} as TravelPlan;
}
const countryTable: any[] = travelPlanDetails.P_COUNTRYTABLE;
let travelPlanData: TravelPlan = {
usSets: travelPlanDetails.NOOFUSSETS,
entries: countryTable
.filter((item) => item.VISISTTRANSITIND === 'V')
.map(item => ({
name: item.COUNTRYNAME,
value: item.COUNTRYCODE,
visits: item.NOOFTIMESENTLEAVE
})),
transits: countryTable
.filter(item => item.VISISTTRANSITIND === 'T')
.map(item => ({
name: item.COUNTRYNAME,
value: item.COUNTRYCODE,
visits: item.NOOFTIMESENTLEAVE
}))
};
return travelPlanData;
}
saveTravelPlan(headerid: number, travelPlan: TravelPlan): Observable<TravelPlan> {
const countryTable: any[] = [
...travelPlan.entries.map(entry => ({
P_VISISTTRANSITIND: 'V' as const,
COUNTRYCODE: entry.value,
NOOFTIMESENTLEAVE: entry.visits
})),
...travelPlan.transits.map(transit => ({
P_VISISTTRANSITIND: 'T' as const,
COUNTRYCODE: transit.value,
NOOFTIMESENTLEAVE: transit.visits
}))
];
let travelPlanData = {
P_HEADERID: headerid,
P_USSETS: travelPlan.usSets,
P_COUNTRYTABLE: countryTable,
P_USERID: this.userService.getUser()
}
return this.http.post<TravelPlan>(`${this.apiUrl}/${this.apiDb}/AddCountries`, travelPlanData);
}
constructor() { }
}

View File

@ -36,12 +36,7 @@ export class ApiErrorHandlerService {
return this.genericErrorMessage;
}
// slice off the last pipe character only if the last character is a pipe
if (messageString.endsWith('|')) {
return messageString.slice(0, -1);
}
return messageString;
return messageString.slice(0, -1);
// .split('|')
// .map(msg => msg.trim())
// .filter(msg => msg.length > 0);

View File

@ -14,8 +14,6 @@ import { ApiErrorHandlerService } from './api-error-handler.service';
})
export class AuthService {
private apiUrl = environment.apiUrl;
private applicationName = environment.appType;
private apiDb = environment.apiDb;
private http = inject(HttpClient);
private userService = inject(UserService);
@ -26,16 +24,16 @@ export class AuthService {
private errorHandler = inject(ApiErrorHandlerService);
login(username: string, password: string): Observable<any> {
return this.http.post(`${this.apiUrl}/${this.apiDb}/login`, { p_emailaddr: username, p_password: password, P_APPLICATIONNAME: this.applicationName });
return this.http.post(`${this.apiUrl}/login`, { p_emailaddr: username, p_password: password });
}
refreshToken(): Observable<any> {
return this.http.get(`${this.apiUrl}/${this.apiDb}/refresh-tokens`, {});
return this.http.get(`${this.apiUrl}/refresh-tokens`, {});
}
logout(): void {
if (this.userService.isLoggedIn()) {
this.http.post(`${this.apiUrl}/${this.apiDb}/logout`, {}).subscribe({
this.http.post(`${this.apiUrl}/logout`, {}).subscribe({
next: (response) => {
this.userService.clearUser();
this.storageService.clear();
@ -54,7 +52,7 @@ export class AuthService {
register(email: string, password: string): Observable<any> {
return this.http.post(
`${this.apiUrl}/${this.apiDb}/register`,
`${this.apiUrl}/register`,
{ P_EMAILADDR: email, P_PASSWORD: password }
);
}
@ -67,7 +65,7 @@ export class AuthService {
}
return this.http.put(
`${this.apiUrl}/${this.apiDb}/forgot-password`,
`${this.apiUrl}/forgot-password`,
{ P_EMAILADDR: email, P_PASSWORD: password },
{ headers }
);
@ -75,7 +73,7 @@ export class AuthService {
sendEmail(email: string, type: string): Observable<any> {
return this.http.post(
`${this.apiUrl}/${this.apiDb}/sendmail`,
`${this.apiUrl}/sendmail`,
{ P_TO: email, P_MAIL_TYPE: type }
);
}

View File

@ -15,10 +15,6 @@ import { Country } from '../../models/country';
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';
import { ExtensionReason } from '../../models/extension-reason';
import { ContactTitle } from '../../models/contact-title';
@Injectable({
providedIn: 'root'
@ -28,10 +24,9 @@ export class CommonService {
private apiDb = environment.apiDb;
private http = inject(HttpClient);
private userService = inject(UserService);
getCountries(): Observable<Country[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=002&P_SPID=${this.userService.getUserSpid()}`).pipe(
getCountries(spid: number = 0): Observable<Country[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=002&P_SPID=0`).pipe(
map((response) =>
response.map((item) => ({
name: item.PARAMDESC,
@ -42,8 +37,8 @@ export class CommonService {
);
}
getStates(country: string): Observable<State[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=001&P_SPID=${this.userService.getUserSpid()}`).pipe(
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(
map((response) =>
response.map((item) => ({
name: item.PARAMDESC,
@ -60,7 +55,7 @@ export class CommonService {
}
getRegions(): Observable<Region[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetRegions/${this.userService.getUserSpid()}`).pipe(
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetRegions`).pipe(
map((response) =>
response.map((item) => ({
id: item.REGIONID,
@ -71,22 +66,8 @@ export class CommonService {
);
}
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,
id: item.PARAMID,
value: item.PARAMVALUE,
daysToDelivery: item.ADDLPARAMVALUE1,
cutOffTime: item.ADDLPARAMVALUE2,
}))
)
);
}
getTimezones(): Observable<TimeZone[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=010&P_SPID=${this.userService.getUserSpid()}`).pipe(
getDeliveryTypes(spid: number = 0): Observable<DeliveryType[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=006&P_SPID=0`).pipe(
map((response) =>
response.map((item) => ({
name: item.PARAMDESC,
@ -97,8 +78,8 @@ export class CommonService {
);
}
getFeeTypes(): Observable<FeeType[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=009&P_SPID=${this.userService.getUserSpid()}`).pipe(
getTimezones(spid: number = 0): Observable<TimeZone[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=010&P_SPID=0`).pipe(
map((response) =>
response.map((item) => ({
name: item.PARAMDESC,
@ -109,8 +90,8 @@ export class CommonService {
);
}
getBondSuretys(): Observable<BondSurety[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=003&P_SPID=${this.userService.getUserSpid()}`).pipe(
getFeeTypes(spid: number = 0): Observable<FeeType[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=009&P_SPID=0`).pipe(
map((response) =>
response.map((item) => ({
name: item.PARAMDESC,
@ -121,8 +102,8 @@ export class CommonService {
);
}
getCargoPolicies(): Observable<CargoPolicy[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=004&P_SPID=${this.userService.getUserSpid()}`).pipe(
getBondSuretys(spid: number = 0): Observable<BondSurety[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=003&P_SPID=0`).pipe(
map((response) =>
response.map((item) => ({
name: item.PARAMDESC,
@ -133,8 +114,8 @@ export class CommonService {
);
}
getCargoSuretys(): Observable<CargoSurety[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=005&P_SPID=${this.userService.getUserSpid()}`).pipe(
getCargoPolicies(spid: number = 0): Observable<CargoPolicy[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=004&P_SPID=0`).pipe(
map((response) =>
response.map((item) => ({
name: item.PARAMDESC,
@ -145,8 +126,20 @@ export class CommonService {
);
}
getCarnetStatuses(): Observable<CarnetStatus[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=011&P_SPID=${this.userService.getUserSpid()}`).pipe(
getCargoSuretys(spid: number = 0): Observable<CargoSurety[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=005&P_SPID=0`).pipe(
map((response) =>
response.map((item) => ({
name: item.PARAMDESC,
id: item.PARAMID,
value: item.PARAMVALUE
}))
)
);
}
getCarnetStatuses(spid: number = 0): Observable<CarnetStatus[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=011&P_SPID=0`).pipe(
map((response) =>
response.map((item) => ({
name: item.PARAMDESC,
@ -158,8 +151,8 @@ export class CommonService {
);
}
getUnitOfMeasures(): Observable<UnitOfMeasure[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=013&P_SPID=${this.userService.getUserSpid()}`).pipe(
getUnitOfMeasures(_spid: number = 0): Observable<UnitOfMeasure[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=013&P_SPID=0`).pipe(
map((response) =>
response.map((item) => ({
name: item.PARAMDESC,
@ -170,8 +163,8 @@ export class CommonService {
);
}
getDeliveryMethods(): Observable<DeliveryMethod[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=007&P_SPID=${this.userService.getUserSpid()}`).pipe(
getDeliveryMethods(_spid: number = 0): Observable<DeliveryMethod[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=007&P_SPID=0`).pipe(
map((response) =>
response.map((item) => ({
name: item.PARAMDESC,
@ -182,45 +175,8 @@ export class CommonService {
);
}
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,
id: item.PARAMID,
value: item.PARAMVALUE,
}))
)
);
}
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,
id: item.PARAMID,
value: item.PARAMVALUE,
isGov: item.ADDLPARAMVALUE1 === 'GOV',
}))
)
);
}
getExtensionReasons(): Observable<ExtensionReason[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=015&P_SPID=${this.userService.getUserSpid()}`).pipe(
map((response) =>
response.map((item) => ({
name: item.PARAMDESC,
id: item.PARAMID,
value: item.PARAMVALUE,
}))
)
);
}
getContactTitles(): Observable<ContactTitle[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=012&P_SPID=${this.userService.getUserSpid()}`).pipe(
getPaymentTypes(_spid: number = 0): Observable<PaymentType[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=008&P_SPID=0`).pipe(
map((response) =>
response.map((item) => ({
name: item.PARAMDESC,

View File

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

View File

@ -1,90 +0,0 @@
import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { UserService } from '../common/user.service';
import { environment } from '../../../../environments/environment';
import { map, Observable, } from 'rxjs';
import { BasicDetail } from '../../models/holder/basic-detail';
@Injectable({
providedIn: 'root'
})
export class BasicDetailService {
private apiUrl = environment.apiUrl;
private apiDb = environment.apiDb;
private http = inject(HttpClient);
private userService = inject(UserService);
getBasicDetailByHolderId(id: number): Observable<BasicDetail> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetHolderRecord/${this.userService.getUserSpid()}/${id}`).pipe(
map(response => this.mapToBasicDetail(response)));
}
private mapToBasicDetail(basicDetails: any): BasicDetail {
return {
holderid: basicDetails.HOLDERID,
spid: basicDetails.SPID,
locationid: basicDetails.LOCATIONID,
holderNumber: basicDetails.HOLDERNO,
holderType: basicDetails.HOLDERTYPE,
uscibMember: basicDetails.USCIBMEMBERFLAG === 'Y',
// govAgency: basicDetails.GOVAGENCYFLAG === 'Y',
holderName: basicDetails.HOLDERNAME,
//NAMEQUALIFIER: basicDetails.NAMEQUALIFIER || null,
dbaName: basicDetails.ADDLNAME || null,
address1: basicDetails.ADDRESS1,
address2: basicDetails.ADDRESS2 || null,
city: basicDetails.CITY,
state: basicDetails.STATE,
zip: basicDetails.ZIP,
country: basicDetails.COUNTRY
};
}
createBasicDetail(data: BasicDetail): Observable<any> {
const basicDetail = {
P_SPID: this.userService.getUserSpid(),
P_CLIENTLOCATIONID: this.userService.getUserLocationid(),
P_HOLDERNO: data.holderNumber,
P_HOLDERTYPE: data.holderType,
P_USCIBMEMBERFLAG: data.uscibMember ? 'Y' : 'N',
P_GOVAGENCYFLAG: 'N',
P_HOLDERNAME: data.holderName,
P_NAMEQUALIFIER: null,
P_ADDLNAME: data.dbaName,
P_ADDRESS1: data.address1,
P_ADDRESS2: data.address2,
P_CITY: data.city,
P_STATE: data.state,
P_ZIP: data.zip,
P_COUNTRY: data.country,
P_USERID: this.userService.getUser()
};
return this.http.post(`${this.apiUrl}/${this.apiDb}/CreateHolderData`, basicDetail);
}
updateBasicDetails(id: number, data: BasicDetail): Observable<any> {
const basicDetail = {
P_HOLDERID: id,
P_SPID: this.userService.getUserSpid(),
P_LOCATIONID: this.userService.getUserLocationid(),
P_HOLDERNO: data.holderNumber,
P_HOLDERTYPE: data.holderType,
P_USCIBMEMBERFLAG: data.uscibMember ? 'Y' : 'N',
P_GOVAGENCYFLAG: 'N',
P_HOLDERNAME: data.holderName,
P_NAMEQUALIFIER: null,
P_ADDLNAME: data.dbaName,
P_ADDRESS1: data.address1,
P_ADDRESS2: data.address2,
P_CITY: data.city,
P_STATE: data.state,
P_ZIP: data.zip,
P_COUNTRY: data.country,
P_USERID: this.userService.getUser()
};
return this.http.put(`${this.apiUrl}/${this.apiDb}/UpdateHolder`, basicDetail);
}
}

View File

@ -1,9 +1,9 @@
import { inject, Injectable } from '@angular/core';
import { Injectable } from '@angular/core';
import { UserService } from '../common/user.service';
import { HttpClient } from '@angular/common/http';
import { environment } from '../../../../environments/environment';
import { map, Observable, of } from 'rxjs';
import { Contact } from '../../models/holder/contact';
import { ContactFormModel, HolderContact } from '../../models/holder/contact';
@Injectable({
providedIn: 'root'
@ -12,40 +12,39 @@ export class ContactService {
private apiUrl = environment.apiUrl;
private apiDb = environment.apiDb;
private http = inject(HttpClient);
private userService = inject(UserService);
constructor(private http: HttpClient, private userService: UserService) { }
getContactsById(id: number): Observable<Contact[]> {
getHolderContactsByHolderId(id: number): Observable<HolderContact[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetHolderContacts/${this.userService.getUserSpid()}/${id}`).pipe(
map(response => this.mapToContacts(response)));
}
private mapToContacts(data: any[]): Contact[] {
private mapToContacts(data: any[]): HolderContact[] {
return data.map(contact => ({
holdercontactid: contact.HOLDERCONTACTID,
holderid: contact.HOLDERID,
spid: contact.SPID,
firstName: contact.FIRSTNAME,
lastName: contact.LASTNAME,
middleInitial: contact.MIDDLEINITIAL || null,
title: contact.TITLE,
phone: contact.PHONE,
mobile: contact.MOBILE,
fax: contact.FAX || null,
email: contact.EMAILADDRESS,
createdBy: contact.CREATEDBY || null,
dateCreated: contact.DATECREATED || null,
lastUpdatedBy: contact.LASTUPDATEDBY || null,
lastUpdatedDate: contact.LASTUPDATEDDATE || null,
isInactive: contact.INACTIVEFLAG === 'Y' || false,
inactivatedDate: contact.INACTIVEDATE || null
HOLDERCONTACTID: contact.HOLDERCONTACTID,
HOLDERID: contact.HOLDERID,
SPID: contact.SPID,
FIRSTNAME: contact.FIRSTNAME,
LASTNAME: contact.LASTNAME,
MIDDLEINITIAL: contact.MIDDLEINITIAL || null,
TITLE: contact.TITLE,
PHONE: contact.PHONE,
MOBILE: contact.MOBILE,
FAX: contact.FAX || null,
EMAILADDRESS: contact.EMAILADDRESS,
DATECREATED: contact.DATECREATED || null,
CREATEDBY: contact.CREATEDBY || null,
INACTIVEFLAG: contact.INACTIVEFLAG || 'N',
INACTIVEDATE: contact.INACTIVEDATE || null,
LASTUPDATEDBY: contact.LASTUPDATEDBY || null,
LASTUPDATEDDATE: contact.LASTUPDATEDDATE || null
}));
}
createContact(holderid: number, data: Contact): Observable<any> {
createHolderContact(HOLDERID: number, data: ContactFormModel): Observable<any> {
const contact = {
P_SPID: this.userService.getUserSpid(),
P_HOLDERID: holderid,
P_HOLDERID: HOLDERID,
P_CONTACTSTABLE: [{
P_FIRSTNAME: data.firstName,
P_LASTNAME: data.lastName,
@ -62,9 +61,9 @@ export class ContactService {
return this.http.post(`${this.apiUrl}/${this.apiDb}/CreateHoldercontact`, contact);
}
updateContact(holdercontactid: number, data: Contact): Observable<any> {
updateHolderContact(P_HOLDERCONTACTID: number, data: ContactFormModel): Observable<any> {
const contact = {
P_HOLDERCONTACTID: holdercontactid,
P_HOLDERCONTACTID,
P_SPID: this.userService.getUserSpid(),
P_FIRSTNAME: data.firstName,
P_LASTNAME: data.lastName,
@ -77,14 +76,17 @@ export class ContactService {
P_USERID: this.userService.getUser()
}
console.log("update body : ", contact);
return this.http.put(`${this.apiUrl}/${this.apiDb}/UpdateHolderContact`, contact);
}
inactivateHolderContact(holdercontactid: number) {
return this.http.patch(`${this.apiUrl}/${this.apiDb}/InactivateHolderContact/${this.userService.getUserSpid()}/${holdercontactid}/${this.userService.getSafeUser()}`, {});
InactivateHolderContact(HOLDERCONTACTID: number) {
return this.http.patch(`${this.apiUrl}/${this.apiDb}/InactivateHolderContact/${this.userService.getUserSpid()}/${HOLDERCONTACTID}/${this.userService.getSafeUser()}`, {});
}
reactivateHolderContact(holdercontactid: number) {
return this.http.patch(`${this.apiUrl}/${this.apiDb}/ReactivateHolderContact/${this.userService.getUserSpid()}/${holdercontactid}/${this.userService.getSafeUser()}`, {});
ReactivateHolderContact(HOLDERCONTACTID: number) {
return this.http.patch(`${this.apiUrl}/${this.apiDb}/ReactivateHolderContact/${this.userService.getUserSpid()}/${HOLDERCONTACTID}/${this.userService.getSafeUser()}`, {});
}
}

View File

@ -0,0 +1,101 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { UserService } from '../common/user.service';
import { environment } from '../../../../environments/environment';
import { HolderDetail, HolderDetailFormType } from '../../models/holder/holder-details';
import { filter, map, Observable, of, tap } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class HolderDetailService {
private apiUrl = environment.apiUrl;
private apiDb = environment.apiDb;
constructor(private http: HttpClient, private userService: UserService) { }
getHolderDetailByHolderId(id: number): Observable<HolderDetail> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetHolderRecord/${this.userService.getUserSpid()}/${id}`).pipe(
tap(response => console.log('Raw response got is :', response)),
// filter(response => response.length > 0),
map(response => this.mapToBasicDetail(response)));
}
private mapToBasicDetail(basicDetails: any): HolderDetail {
return {
HOLDERID: basicDetails.HOLDERID,
SPID: basicDetails.SPID,
LOCATIONID: basicDetails.LOCATIONID,
HOLDERNO: basicDetails.HOLDERNO,
HOLDERTYPE: basicDetails.HOLDERTYPE,
USCIBMEMBERFLAG: basicDetails.USCIBMEMBERFLAG,
GOVAGENCYFLAG: basicDetails.GOVAGENCYFLAG,
HOLDERNAME: basicDetails.HOLDERNAME,
NAMEQUALIFIER: basicDetails.NAMEQUALIFIER || null,
ADDLNAME: basicDetails.ADDLNAME || null,
ADDRESS1: basicDetails.ADDRESS1,
ADDRESS2: basicDetails.ADDRESS2 || null,
CITY: basicDetails.CITY,
STATE: basicDetails.STATE,
ZIP: basicDetails.ZIP,
COUNTRY: basicDetails.COUNTRY,
DATECREATED: basicDetails.DATECREATED,
CREATEDBY: basicDetails.CREATEDBY,
INACTIVEFLAG: basicDetails.INACTIVEFLAG,
INACTIVEDATE: basicDetails.INACTIVEDATE || null,
LASTUPDATEDBY: basicDetails.LASTUPDATEDBY || null,
LASTUPDATEDDATE: basicDetails.LASTUPDATEDDATE || null
};
}
createHolderDetail(data: HolderDetailFormType): Observable<any> {
const holderDetail = {
P_SPID: this.userService.getUserSpid(),
P_CLIENTLOCATIONID: this.userService.getUserLocationid(),
P_HOLDERNO: data.HolderNumber,
P_HOLDERTYPE: data.holderType,
P_USCIBMEMBERFLAG: data.USCIBMember,
P_GOVAGENCYFLAG: data.govAgency,
P_HOLDERNAME: data.holderName,
P_NAMEQUALIFIER: null,
P_ADDLNAME: data.DBAName,
P_ADDRESS1: data.address1,
P_ADDRESS2: data.address2,
P_CITY: data.city,
P_STATE: data.state,
P_ZIP: data.zip,
P_COUNTRY: data.country,
P_USERID: this.userService.getUser()
};
console.log("body : ",holderDetail);
return this.http.post(`${this.apiUrl}/${this.apiDb}/CreateHolderData`, holderDetail);
}
updateHolderDetails(id: number, data: HolderDetailFormType): Observable<any> {
const holderDetail = {
P_HOLDERID: id,
P_SPID: this.userService.getUserSpid(),
P_LOCATIONID: this.userService.getUserLocationid(),
P_HOLDERNO: data.HolderNumber,
P_HOLDERTYPE: data.holderType,
P_USCIBMEMBERFLAG: data.USCIBMember,
P_GOVAGENCYFLAG: data.govAgency,
P_HOLDERNAME: data.holderName,
P_NAMEQUALIFIER: null,
P_ADDLNAME: data.DBAName,
P_ADDRESS1: data.address1,
P_ADDRESS2: data.address2,
P_CITY: data.city,
P_STATE: data.state,
P_ZIP: data.zip,
P_COUNTRY: data.country,
P_USERID: this.userService.getUser()
};
return this.http.put(`${this.apiUrl}/${this.apiDb}/UpdateHolder`, holderDetail);
}
}

View File

@ -3,8 +3,7 @@ import { environment } from '../../../../environments/environment';
import { HttpClient } from '@angular/common/http';
import { UserService } from '../common/user.service';
import { map, Observable } from 'rxjs';
import { BasicDetail } from '../../models/holder/basic-detail';
import { HolderFilter } from '../../models/holder/holder-filter';
import { HolderDetail } from '../../models/holder/holder-details';
@Injectable({
providedIn: 'root'
@ -16,49 +15,54 @@ export class HolderService {
private http = inject(HttpClient);
private userService = inject(UserService);
getHolders(filter: HolderFilter): Observable<BasicDetail[]> {
// add query parameters to the URL based on the filter
let queryParams = ``;
if (filter.holderName) {
queryParams += `P_HOLDERNAME=${filter.holderName}`;
}
if (filter.holderId) {
queryParams += `P_HOLDERID=${filter.holderId}`;
}
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/SearchHolder/${this.userService.getUserSpid()}/${this.userService.getSafeUser()}?${queryParams}`).pipe(
searchHolders(filter: { holderName: string }): Observable<HolderDetail[]> {
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/SearchHolder/${this.userService.getUserSpid()}/${filter.holderName}`).pipe(
map(response => this.mapToHolders(response))
)
}
inactivateHolder(holderid: number) {
return this.http.patch(`${this.apiUrl}/${this.apiDb}/InactivateHolder/${this.userService.getUserSpid()}/${holderid}/${this.userService.getSafeUser()}`, {});
updateApplicationHolder(headerId: number, holderId: number) {
const applicationDetail = {
P_HEADERID: headerId,
P_HOLDERID: holderId
}
return this.http.patch(`${this.apiUrl}/${this.apiDb}/update-holder`, applicationDetail);
}
reactivateHolder(holderid: number) {
return this.http.patch(`${this.apiUrl}/${this.apiDb}/ReactivateHolder/${this.userService.getUserSpid()}/${holderid}/${this.userService.getSafeUser()}`, {});
InactivateHolder(HOLDERID: number) {
return this.http.patch(`${this.apiUrl}/${this.apiDb}/InactivateHolder/${this.userService.getUserSpid()}/${HOLDERID}/${this.userService.getSafeUser()}`, {});
}
private mapToHolders(data: any[]): BasicDetail[] {
return data.map((holderDetail) => ({
holderid: holderDetail.HOLDERID,
spid: holderDetail.SPID,
locationid: holderDetail.LOCATIONID,
holderNumber: holderDetail.HOLDERNO,
holderType: holderDetail.HOLDERTYPE,
uscibMember: holderDetail.USCIBMEMBERFLAG === 'Y',
// govAgency: holderDetail.GOVAGENCYFLAG === 'Y',
holderName: holderDetail.HOLDERNAME,
//: holderDetail.NAMEQUALIFIER,
dbaName: holderDetail.ADDLNAME,
address1: holderDetail.ADDRESS1,
address2: holderDetail.ADDRESS2,
city: holderDetail.CITY,
state: holderDetail.STATE,
zip: holderDetail.ZIP,
country: holderDetail.COUNTRY,
isInactive: holderDetail.INACTIVEFLAG === 'Y'
ReactivateHolder(HOLDERID: number) {
return this.http.patch(`${this.apiUrl}/${this.apiDb}/ReactivateHolder/${this.userService.getUserSpid()}/${HOLDERID}/${this.userService.getSafeUser()}`, {});
}
private mapToHolders(data: any[]): HolderDetail[] {
return data.map((holderDetail: HolderDetail) => ({
HOLDERID: holderDetail.HOLDERID,
SPID: holderDetail.SPID,
LOCATIONID: holderDetail.LOCATIONID,
HOLDERNO: holderDetail.HOLDERNO,
HOLDERTYPE: holderDetail.HOLDERTYPE,
USCIBMEMBERFLAG: holderDetail.USCIBMEMBERFLAG,
GOVAGENCYFLAG: holderDetail.GOVAGENCYFLAG,
HOLDERNAME: holderDetail.HOLDERNAME,
NAMEQUALIFIER: holderDetail.NAMEQUALIFIER,
ADDLNAME: holderDetail.ADDLNAME,
ADDRESS1: holderDetail.ADDRESS1,
ADDRESS2: holderDetail.ADDRESS2,
CITY: holderDetail.CITY,
STATE: holderDetail.STATE,
ZIP: holderDetail.ZIP,
COUNTRY: holderDetail.COUNTRY,
DATECREATED: holderDetail.DATECREATED,
CREATEDBY: holderDetail.CREATEDBY,
INACTIVEFLAG: holderDetail.INACTIVEFLAG,
INACTIVEDATE: holderDetail.INACTIVEDATE,
LASTUPDATEDBY: holderDetail.LASTUPDATEDBY,
LASTUPDATEDDATE: holderDetail.LASTUPDATEDDATE
}))
}
}

View File

@ -38,11 +38,7 @@ export class HomeService {
expiryDate: item.EXPDATE || null,
orderType: item.ORDERTYPE,
carnetStatus: item.CARNETSTATUS,
headerid: item.HEADERID,
printGLStatus: item.PRINTGLSTATUS,
securityType: item.SECURITYTYPE,
deliveryType: item.DELIVERYTYPE,
clientName: item.CLIENTNAMNE
headerId: item.HEADERID,
}));
}
}

View File

@ -1,20 +0,0 @@
<div class="client-carnet-container">
<!-- Stepper Section (shown after questions are answered) -->
<mat-stepper orientation="vertical" [linear]="isLinear" (selectionChange)="onStepChange($event)"
[selectedIndex]="currentStep">
<!-- Holder Detail Step -->
<mat-step [completed]="stepsCompleted.holderDetails" [editable]="stepsCompleted.holderDetails">
<ng-template matStepLabel>Holder Details</ng-template>
<app-basic-detail [isEditMode]="isEditMode" (holderIdCreated)="onHolderIdCreated($event)">
</app-basic-detail>
</mat-step>
<!-- Holder Contact Step -->
<mat-step [completed]="stepsCompleted.holderContacts" [editable]="stepsCompleted.holderContacts">
<ng-template matStepLabel>Holder Contacts</ng-template>
<app-contacts [holderid]="holderid" [isEditMode]="isEditMode"
[userPreferences]="userPreferences"></app-contacts>
</mat-step>
</mat-stepper>
</div>

View File

@ -0,0 +1,26 @@
<button mat-stroked-button color="accent" class="back-btn" (click)="goToEditCarnet()" [hidden]="!isEditMode">Back</button>
<div class="client-carnet-container">
<!-- Stepper Section (shown after questions are answered) -->
<mat-stepper orientation="vertical" [linear]="isLinear" (selectionChange)="onStepChange($event)"
[selectedIndex]="currentStep">
<!-- Holder Detail Step -->
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.holderDetails"
[editable]="stepsCompleted.holderDetails">
<ng-template matStepLabel>Holder Details</ng-template>
<!-- <app-application [isEditMode]="isEditMode" (headerIdCreated)="onApplicationDetailCreated($event)">
</app-application> -->
<app-holder-detail [isEditMode]="isEditMode" (holderIdCreated)="onHolderIdCreated($event)"></app-holder-detail>
</mat-step>
<!-- Holder Contact Step -->
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.holderContacts"
[editable]="stepsCompleted.holderContacts">
<ng-template matStepLabel>Holder Contacts</ng-template>
<!-- <app-application [isEditMode]="isEditMode" (headerIdCreated)="onApplicationDetailCreated($event)">
</app-application> -->
<app-holder-contacts [holderid]="holderid" [isEditMode]="isEditMode" [userPreferences]="userPreferences"></app-holder-contacts>
</mat-step>
</mat-stepper>
</div>

View File

@ -0,0 +1,4 @@
.back-btn{
padding: 0 10px !important;
margin-bottom: 10px !important;
}

View File

@ -1,47 +1,50 @@
import { Component } from '@angular/core';
import { Component, inject } from '@angular/core';
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { StepperSelectionEvent } from '@angular/cdk/stepper';
import { BasicDetailComponent } from '../basic-details/basic-details.component';
import { HolderDetailComponent } from '../holder-detail/holder-detail.component';
import { MatStepperModule } from '@angular/material/stepper';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { ContactsComponent } from '../contacts/contacts.component';
import { HolderContactsComponent } from '../holder-contacts/holder-contacts.component';
import { UserPreferences } from '../../core/models/user-preference';
import { UserPreferencesService } from '../../core/services/user-preference.service';
import { CarnetStateStore } from '../../core/services/carnet/carnet-application.service';
import { NavigationService } from '../../core/services/common/navigation.service';
@Component({
selector: 'app-add-holder',
selector: 'app-add',
imports: [
AngularMaterialModule,
CommonModule,
ReactiveFormsModule,
BasicDetailComponent,
HolderDetailComponent,
MatStepperModule,
MatFormFieldModule,
MatInputModule,
ContactsComponent
HolderContactsComponent
],
templateUrl: './add-holder.component.html',
styleUrl: './add-holder.component.scss'
templateUrl: './add.component.html',
styleUrl: './add.component.scss'
})
export class AddHolderComponent {
export class AddComponent {
store = inject(CarnetStateStore);
currentStep = 0;
isLinear = true;
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = 'new';
isEditMode = false;
holderid: number = 0;
userPreferences: UserPreferences;
stepsCompleted = {
holderDetails: false,
holderContacts: false
};
constructor(
userPrefenceService: UserPreferencesService
private userPrefenceService: UserPreferencesService,
private navigationService: NavigationService,
) {
this.userPreferences = userPrefenceService.getPreferences();
this.userPreferences = this.userPrefenceService.getPreferences();
}
onStepChange(event: StepperSelectionEvent): void {
@ -52,4 +55,13 @@ export class AddHolderComponent {
this.holderid = holderid;
this.stepsCompleted.holderDetails = true;
}
goToEditCarnet(): void {
this.navigationService.navigate(["edit-carnet", this.store.headerid()],
{
state: { isEditMode: true } ,
queryParams: { applicationname: this.store.applicationName() }
})
}
}

View File

@ -1,252 +0,0 @@
import { Component, EventEmitter, inject, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { State } from '../../core/models/state';
import { Region } from '../../core/models/region';
import { NotificationService } from '../../core/services/common/notification.service';
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 { 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';
import { StorageService } from '../../core/services/common/storage.service';
import { NavigationService } from '../../core/services/common/navigation.service';
import { environment } from '../../../environments/environment';
import { HolderService } from '../../core/services/carnet/holder.service';
@Component({
selector: 'app-basic-detail',
imports: [AngularMaterialModule, ReactiveFormsModule, CommonModule],
templateUrl: './basic-details.component.html',
styleUrl: './basic-details.component.scss'
})
export class BasicDetailComponent implements OnInit, OnDestroy {
@Input() isEditMode = false;
@Input() holderid: number = 0;
@Output() holderIdCreated = new EventEmitter<number>();
basicDetailsForm: FormGroup;
countries: Country[] = [];
regions: Region[] = [];
states: State[] = [];
currentApplicationDetails: { headerid: number, applicationName: string } | null = null;
isLoading = false;
changeInProgress = false;
countriesHasStates = ['US', 'CA', 'MX'];
returnTo: string = environment.appType === 'client' ? 'edit-carnet' : 'process-carnet';
private destroy$ = new Subject<void>();
private fb = inject(FormBuilder);
private basicDetailService = inject(BasicDetailService);
private notificationService = inject(NotificationService);
private commonService = inject(CommonService);
private errorHandler = inject(ApiErrorHandlerService);
private storageService = inject(StorageService);
private navigationService = inject(NavigationService);
private carnetHolderService = inject(HolderService);
constructor() {
this.basicDetailsForm = this.createForm();
this.currentApplicationDetails = this.storageService.get<{ headerid: number, applicationName: string }>('currentapplication')
}
createForm(): FormGroup {
return this.fb.group({
holderName: ['', [Validators.required, Validators.maxLength(100)]],
dbaName: ['', [Validators.maxLength(20)]],
holderNumber: ['', [Validators.required, Validators.maxLength(20)]],
holderType: ['', [Validators.required, Validators.maxLength(20)]],
uscibMember: [false, [Validators.required]],
// govAgency: [false, [Validators.required]],
address1: ['', [Validators.required, Validators.maxLength(100)]],
address2: ['', Validators.maxLength(100)],
city: ['', [Validators.required, Validators.maxLength(50)]],
state: ['', Validators.required],
country: ['US', Validators.required],
zip: ['', [Validators.required, ZipCodeValidator('country')]]
});
}
ngOnInit(): void {
this.loadLookupData();
if (this.holderid > 0) {
this.isLoading = true;
this.basicDetailService.getBasicDetailByHolderId(this.holderid).pipe(finalize(() => {
this.isLoading = false;
})).subscribe({
next: (basicDetail: BasicDetail) => {
this.patchFormData(basicDetail);
// this.holderName.emit(basicDetail.holderName);
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load basic details');
this.notificationService.showError(errorMessage);
console.error('Error loading basic details:', error);
}
});
} else {
this.loadStates('US'); // Load states for default country
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
onCountryChange(country: string): void {
this.basicDetailsForm.get('state')?.reset();
if (country) {
this.loadStates(country);
}
this.basicDetailsForm.get('zip')?.updateValueAndValidity();
}
saveBasicDetails() {
if (this.basicDetailsForm.invalid) {
this.basicDetailsForm.markAllAsTouched();
return;
}
const basicDetailData: BasicDetail = this.basicDetailsForm.value;
const saveObservable = this.isEditMode && this.holderid > 0
? this.basicDetailService.updateBasicDetails(this.holderid, basicDetailData)
: this.basicDetailService.createBasicDetail(basicDetailData);
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);
// save the created holder for the current application
this.saveHolderToApplication(basicData.HOLDERID);
// change to edit mode after creation
this.isEditMode = true;
this.holderid = basicData.HOLDERID;
}
// if (this.isEditMode) {
// this.holderName.emit(basicDetailData.holderName);
// }
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, `Failed to ${this.isEditMode ? 'update' : 'add'} basic details`);
this.notificationService.showError(errorMessage);
console.error('Error saving basic details:', error);
}
});
}
updateStateControl(controlName: string, country: string): void {
const stateControl = this.basicDetailsForm.get(controlName);
if (this.countriesHasStates.includes(country)) {
stateControl?.enable();
} else {
stateControl?.disable();
stateControl?.setValue('FN');
}
}
patchFormData(data: BasicDetail): void {
this.basicDetailsForm.patchValue({
holderName: data.holderName,
dbaName: data.dbaName,
holderNumber: data.holderNumber,
holderType: data.holderType,
uscibMember: data.uscibMember,
// govAgency: data.govAgency,
address1: data.address1,
address2: data.address2,
city: data.city,
state: data.state,
country: data.country,
zip: data.zip
})
if (data.country) {
this.loadStates(data.country);
}
}
get f() {
return this.basicDetailsForm.controls;
}
loadLookupData(): void {
this.commonService.getCountries()
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (countries) => {
this.countries = countries;
},
error: (error) => {
console.error('Failed to load countries', error);
}
});
}
loadStates(country: string): void {
country = this.countriesHasStates.includes(country) ? country : 'FN';
this.commonService.getStates(country)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (states) => {
this.states = states;
this.updateStateControl('state', country);
},
error: (error) => {
console.error('Failed to load states', error);
}
});
}
goBackToCarnetApplication(): void {
this.storageService.removeItem('currentapplication')
this.navigationService.navigate([this.returnTo, this.currentApplicationDetails?.headerid],
{
state: { isEditMode: true },
queryParams: {
applicationname: this.currentApplicationDetails?.applicationName,
return: 'holder'
}
})
}
saveHolderToApplication(holderid: number): void {
this.changeInProgress = true;
const saveObservable = this.carnetHolderService.saveApplicationHolder(this.currentApplicationDetails?.headerid!, holderid);
saveObservable.pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: (basicData: any) => {
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, `Failed to save holder details`);
this.notificationService.showError(errorMessage);
console.error('Error saving holder details:', error);
}
});
}
}

View File

@ -1,183 +0,0 @@
.contacts-container {
padding: 24px;
display: flex;
flex-direction: column;
gap: 24px;
.actions-bar {
clear: both;
margin-bottom: -16px;
mat-slide-toggle {
transform: scale(0.8);
margin-left: -0.5rem;
}
button {
float: right;
margin-left: 16px;
}
}
.table-container {
position: relative;
overflow: auto;
border-radius: 8px;
.loading-shade {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.7);
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}
mat-table {
width: 100%;
mat-icon {
cursor: pointer;
transition: all 0.2s ease;
&:hover {
transform: scale(1.1);
}
}
.mat-column-actions {
width: 180px;
text-align: center;
}
.mat-column-defaultContact {
width: 80px;
text-align: center;
}
}
.no-data-message {
text-align: center;
padding: 0.9rem;
color: rgba(0, 0, 0, 0.54);
mat-icon {
font-size: 1rem;
width: 1rem;
height: 1rem;
margin-bottom: -3px;
}
}
mat-paginator {
border-top: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 0 0 8px 8px;
padding-top: 4px;
}
}
.form-container {
background-color: white;
padding: 24px;
border-radius: 8px;
margin-top: 16px;
.form-header {
margin-bottom: 24px;
h3 {
margin: 0;
color: var(--mat-sys-primary);
font-weight: 500;
}
}
form {
display: flex;
flex-direction: column;
gap: 16px;
.form-row {
display: flex;
gap: 16px;
align-items: start;
mat-form-field {
flex: 1;
}
.small-field {
max-width: 120px;
}
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 16px;
margin-top: 16px;
}
.top-divider {
padding-top: 0.5rem;
border-top: 1px solid #eee;
}
.bottom-divider {
padding-bottom: 0.5rem;
border-bottom: 1px solid #eee;
}
.readonly-section {
.readonly-fields {
display: flex;
gap: 2rem;
.field-column {
flex: 1;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
}
.readonly-field {
label {
display: block;
font-size: 0.875rem;
color: #666;
margin-bottom: 0.25rem;
}
.readonly-value {
padding: 0.25rem;
font-size: 0.875rem;
display: flex;
align-items: center;
}
}
}
}
}
}
// Responsive adjustments
@media (max-width: 768px) {
.contacts-container {
padding: 16px;
.form-row {
flex-direction: column;
gap: 16px !important;
.small-field {
max-width: 100% !important;
}
}
}
}

View File

@ -1,294 +0,0 @@
import { Component, EventEmitter, inject, Input, Output, ViewChild } from '@angular/core';
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { PhonePipe } from '../../shared/pipes/phone.pipe';
import { CommonModule } from '@angular/common';
import { MatPaginator, MatPaginatorIntl } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { UserPreferences } from '../../core/models/user-preference';
import { NotificationService } from '../../core/services/common/notification.service';
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
import { ContactService } from '../../core/services/holder/contact.service';
import { Contact } from '../../core/models/holder/contact';
import { CustomPaginator } from '../../shared/custom-paginator';
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, Subject, takeUntil } from 'rxjs';
import { environment } from '../../../environments/environment';
import { ContactTitle } from '../../core/models/contact-title';
import { CommonService } from '../../core/services/common/common.service';
@Component({
selector: 'app-contacts',
imports: [AngularMaterialModule, ReactiveFormsModule, PhonePipe, CommonModule],
templateUrl: './contacts.component.html',
styleUrl: './contacts.component.scss',
providers: [{ provide: MatPaginatorIntl, useClass: CustomPaginator }],
})
export class ContactsComponent {
@Input() isEditMode: boolean = false;
@Input() holderid: number = 0;
@Input() userPreferences: UserPreferences = {};
@Output() hasContacts = new EventEmitter<boolean>();
@ViewChild(MatPaginator, { static: false })
set paginator(value: MatPaginator) {
this.dataSource.paginator = value;
}
@ViewChild(MatSort) sort!: MatSort;
displayedColumns: string[] = ['firstName', 'middleInitial', 'lastName', 'title', 'phone', 'mobile', 'fax', 'email', 'actions'];
dataSource = new MatTableDataSource<any>();
contactForm: FormGroup;
isEditing = false;
currentContactId: number | null = null;
isLoading = false;
changeInProgress = false;
showForm = false;
showInactiveContacts = false;
contacts: Contact[] = [];
contactTitles: ContactTitle[] = [];
currentApplicationDetails: { headerid: number, applicationName: string } | null = null;
returnTo: string = environment.appType === 'client' ? 'edit-carnet' : 'process-carnet';
contactReadOnlyFields: any = {
lastChangedDate: null,
lastChangedBy: null,
isInactive: null,
inactivatedDate: null
};
private destroy$ = new Subject<void>();
private fb = inject(FormBuilder);
private dialog = inject(MatDialog);
private commonService = inject(CommonService);
private contactService = inject(ContactService);
private notificationService = inject(NotificationService);
private errorHandler = inject(ApiErrorHandlerService);
private navigationService = inject(NavigationService);
private storageService = inject(StorageService);
constructor() {
this.contactForm = this.fb.group({
firstName: ['', [Validators.required, Validators.maxLength(50)]],
lastName: ['', [Validators.required, Validators.maxLength(50)]],
middleInitial: ['', [Validators.maxLength(1)]],
title: ['', [Validators.required]],
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)]],
});
this.currentApplicationDetails = this.storageService.get<{ headerid: number, applicationName: string }>('currentapplication')
}
ngOnInit(): void {
this.loadContactTitles();
if (this.holderid > 0) {
this.loadContacts();
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
ngAfterViewInit() {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
loadContacts(): void {
this.isLoading = true;
this.contactService.getContactsById(this.holderid).pipe(finalize(() => {
this.isLoading = false;
})).subscribe({
next: (contacts: Contact[]) => {
this.contacts = contacts;
this.renderContacts();
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load contacts');
this.notificationService.showError(errorMessage);
console.error('Error loading contacts:', error);
}
});
}
loadContactTitles(): void {
this.commonService.getContactTitles()
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (contactTitles) => {
this.contactTitles = contactTitles;
},
error: (error) => {
console.error('Failed to load contact titles', error);
}
});
}
addNewContact(): void {
this.showForm = true;
this.isEditing = false;
this.currentContactId = null;
this.contactForm.reset();
}
editContact(contact: Contact): void {
this.showForm = true;
this.isEditing = true;
this.currentContactId = contact.holdercontactid;
this.contactForm.patchValue({
firstName: contact.firstName,
lastName: contact.lastName,
middleInitial: contact.middleInitial,
title: contact.title,
phone: contact.phone,
mobile: contact.mobile,
fax: contact.fax,
email: contact.email
});
this.contactReadOnlyFields.lastChangedDate = contact.lastUpdatedDate ?? contact.dateCreated;
this.contactReadOnlyFields.lastChangedBy = contact.lastUpdatedBy ?? contact.createdBy;
this.contactReadOnlyFields.isInactive = contact.isInactive;
this.contactReadOnlyFields.inactivatedDate = contact.inactivatedDate;
}
saveContact(): void {
if (this.contactForm.invalid && !(this.holderid > 0)) {
this.contactForm.markAllAsTouched();
return;
}
const contactData: Contact = this.contactForm.value;
const saveObservable = this.isEditing && (this.currentContactId! > 0)
? this.contactService.updateContact(this.currentContactId!, contactData)
: this.contactService.createContact(this.holderid, contactData);
this.changeInProgress = true;
saveObservable.pipe(finalize(() => {
this.changeInProgress = false;
})).subscribe({
next: () => {
this.notificationService.showSuccess(`Contact ${this.isEditing ? 'updated' : 'added'} successfully`);
this.loadContacts();
this.cancelEdit();
this.hasContacts.emit(true);
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, `Failed to ${this.isEditing ? 'update' : 'add'} contact`);
this.notificationService.showError(errorMessage);
console.error('Error saving contact:', error);
}
});
}
toggleShowInactiveContacts(): void {
this.showInactiveContacts = !this.showInactiveContacts;
this.renderContacts();
}
renderContacts(): void {
if (this.showInactiveContacts) {
this.dataSource.data = this.contacts.filter(contact => contact.isInactive);
} else {
this.dataSource.data = this.contacts.filter(contact => !contact.isInactive);
}
}
cancelEdit(): void {
this.showForm = false;
this.isEditing = false;
this.currentContactId = null;
this.contactForm.reset();
}
inactivateContact(contactId: number): void {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
width: '350px',
data: {
title: 'Confirm Inactivation',
message: 'Are you sure you want to inactivate this contact?',
confirmText: 'Yes',
cancelText: 'Cancel'
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.contactService.inactivateHolderContact(contactId).subscribe({
next: () => {
this.notificationService.showSuccess('Contact inactivated successfully');
this.loadContacts();
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to inactivate contact');
this.notificationService.showError(errorMessage);
console.error('Error inactivating contact:', error);
}
});
}
});
}
reactivateContact(contactId: number): void {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
width: '350px',
data: {
title: 'Confirm Reactivation',
message: 'Are you sure you want to reactivate this contact?',
confirmText: 'Yes',
cancelText: 'Cancel'
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.contactService.reactivateHolderContact(contactId).subscribe({
next: () => {
this.notificationService.showSuccess('Contact reactivated successfully');
this.loadContacts();
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to reactivate contact');
this.notificationService.showError(errorMessage);
console.error('Error reactivating contact:', error);
}
});
}
});
}
goBackToCarnetApplication(): void {
this.storageService.removeItem('currentapplication')
this.navigationService.navigate([this.returnTo, this.currentApplicationDetails?.headerid],
{
state: { isEditMode: true },
queryParams: {
applicationname: this.currentApplicationDetails?.applicationName,
return: 'holder'
}
})
}
getContactTitleLabel(value: string): string {
const type = this.contactTitles.find(t => t.value === value);
return type ? type.name : value;
}
}

View File

@ -1,23 +0,0 @@
<!-- <h2 *ngIf="this.holderName" class="page-header">Manage {{this.holderName}}</h2> -->
<div class="holder-action-buttons">
<button mat-button (click)="accordion().openAll()">Expand All</button>
<button mat-button (click)="accordion().closeAll()">Collapse All</button>
</div>
<mat-accordion class="holder-headers-align" multi>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title> Basic Details </mat-panel-title>
</mat-expansion-panel-header>
<app-basic-detail [holderid]="holderid" [isEditMode]="isEditMode"></app-basic-detail>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title> Contacts </mat-panel-title>
</mat-expansion-panel-header>
<app-contacts [userPreferences]="userPreferences" [holderid]="holderid"
[isEditMode]="isEditMode"></app-contacts>
</mat-expansion-panel>
</mat-accordion>

View File

@ -1,18 +0,0 @@
// .page-header {
// margin: 0.5rem 0px;
// color: var(--mat-sys-primary);
// font-weight: 500;
// }
.holder-action-buttons {
padding-bottom: 20px;
}
.holder-headers-align .mat-expansion-panel-header-description {
justify-content: space-between;
align-items: center;
}
.holder-headers-align .mat-mdc-form-field+.mat-mdc-form-field {
margin-left: 8px;
}

View File

@ -0,0 +1,24 @@
<h2 *ngIf="this.holderName" class="page-header">Manage {{this.holderName}}</h2>
<div class="preparer-action-buttons">
<button mat-button (click)="accordion().openAll()">Expand All</button>
<button mat-button (click)="accordion().closeAll()">Collapse All</button>
</div>
<mat-accordion class="preparer-headers-align" multi>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title> Basic Details </mat-panel-title>
</mat-expansion-panel-header>
<app-holder-detail [holderid]="holderid" [isEditMode]="isEditMode"
(clientName)="onClientNameUpdate($event)"></app-holder-detail>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title> holder Contacts </mat-panel-title>
</mat-expansion-panel-header>
<app-holder-contacts [userPreferences]="userPreferences" [holderid]="holderid" [isEditMode]="isEditMode" ></app-holder-contacts>
</mat-expansion-panel>
</mat-accordion>

View File

@ -0,0 +1,18 @@
.page-header {
margin: 0.5rem 0px;
color: var(--mat-sys-primary);
font-weight: 500;
}
.preparer-action-buttons {
padding-bottom: 20px;
}
.preparer-headers-align .mat-expansion-panel-header-description {
justify-content: space-between;
align-items: center;
}
.preparer-headers-align .mat-mdc-form-field+.mat-mdc-form-field {
margin-left: 8px;
}

View File

@ -1,32 +1,29 @@
import { CommonModule } from '@angular/common';
import { afterNextRender, Component, inject, viewChild } from '@angular/core';
import { afterNextRender, Component, viewChild } from '@angular/core';
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { MatAccordion } from '@angular/material/expansion';
import { UserPreferences } from '../../core/models/user-preference';
import { ActivatedRoute } from '@angular/router';
import { UserPreferencesService } from '../../core/services/user-preference.service';
import { BasicDetailComponent } from '../basic-details/basic-details.component';
import { ContactsComponent } from '../contacts/contacts.component';
import { HolderDetailComponent } from '../holder-detail/holder-detail.component';
import { HolderContactsComponent } from '../holder-contacts/holder-contacts.component';
@Component({
selector: 'app-edit-holder',
imports: [AngularMaterialModule, CommonModule, BasicDetailComponent, ContactsComponent],
templateUrl: './edit-holder.component.html',
styleUrl: './edit-holder.component.scss'
selector: 'app-edit',
imports: [AngularMaterialModule, CommonModule, HolderDetailComponent, HolderContactsComponent],
templateUrl: './edit.component.html',
styleUrl: './edit.component.scss'
})
export class EditHolderComponent {
export class EditComponent {
accordion = viewChild.required(MatAccordion);
isEditMode = true;
holderid = 0;
//holderName: string | null = null;
holderName: string | null = null;
userPreferences: UserPreferences;
private route = inject(ActivatedRoute);
constructor(userPrefenceService: UserPreferencesService) {
constructor(private route: ActivatedRoute, private userPrefenceService: UserPreferencesService) {
this.userPreferences = userPrefenceService.getPreferences();
afterNextRender(() => {
// Open all panels
this.accordion().openAll();
});
}
@ -36,7 +33,7 @@ export class EditHolderComponent {
this.holderid = idParam ? parseInt(idParam, 10) : 0;
}
// onHolderNameUpdate(event: string): void {
// this.holderName = event;
// }
onClientNameUpdate(event: string): void {
this.holderName = event;
}
}

View File

@ -7,64 +7,48 @@
<button mat-raised-button color="primary" (click)="addNewContact()">
<mat-icon>add</mat-icon> Add New Contact
</button>
<button mat-raised-button color="accent" *ngIf="currentApplicationDetails" type="button"
(click)="goBackToCarnetApplication()">
<mat-icon>chevron_left</mat-icon> Back to application
</button>
</div>
<div class="table-container mat-elevation-z8">
<div class="loading-shade" *ngIf="isLoading">
<mat-spinner diameter="50"></mat-spinner>
</div>
<table mat-table [dataSource]="dataSource" matSort>
<!-- First Name Column -->
<ng-container matColumnDef="firstName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>First Name</th>
<td mat-cell *matCellDef="let contact">{{ contact.firstName }}</td>
</ng-container>
<!-- Middle Initial Column -->
<ng-container matColumnDef="middleInitial">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Middle Initial</th>
<td mat-cell *matCellDef="let contact">{{ contact.middleInitial }}</td>
<td mat-cell *matCellDef="let contact">{{ contact.FIRSTNAME }}</td>
</ng-container>
<!-- Last Name Column -->
<ng-container matColumnDef="lastName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Last Name</th>
<td mat-cell *matCellDef="let contact">{{ contact.lastName }}</td>
<td mat-cell *matCellDef="let contact">{{ contact.LASTNAME }}</td>
</ng-container>
<!-- Title Column -->
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Title</th>
<td mat-cell *matCellDef="let contact">{{ getContactTitleLabel(contact.title) }}</td>
<td mat-cell *matCellDef="let contact">{{ contact.TITLE }}</td>
</ng-container>
<!-- Phone Column -->
<ng-container matColumnDef="phone">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Phone</th>
<td mat-cell *matCellDef="let contact">{{ contact.phone | phone }}</td>
<td mat-cell *matCellDef="let contact">{{ contact.PHONE | phone }}</td>
</ng-container>
<!-- Mobile Column -->
<ng-container matColumnDef="mobile">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Mobile</th>
<td mat-cell *matCellDef="let contact">{{ contact.mobile | phone }}</td>
</ng-container>
<!-- Fax Column -->
<ng-container matColumnDef="fax">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Fax</th>
<td mat-cell *matCellDef="let contact">{{ contact.fax }}</td>
<td mat-cell *matCellDef="let contact">{{ contact.MOBILE | phone }}</td>
</ng-container>
<!-- Email Column -->
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Email</th>
<td mat-cell *matCellDef="let contact">{{ contact.email }}</td>
<td mat-cell *matCellDef="let contact">{{ contact.EMAILADDRESS }}</td>
</ng-container>
<!-- Default Contact Column -->
@ -81,17 +65,20 @@
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>
<td mat-cell *matCellDef="let contact">
<div>
<div style="display: flex; justify-content: center; align-items: center;">
<button mat-icon-button color="primary" (click)="editContact(contact)" matTooltip="Edit">
<mat-icon>edit</mat-icon>
</button>
<!-- <button mat-icon-button color="primary" (click)="createLogin()" matTooltip="Login">
<mat-icon>person</mat-icon>
</button> -->
<button mat-icon-button color="warn" *ngIf="!contact.isInactive"
(click)="inactivateContact(contact.holdercontactid)" matTooltip="Inactivate">
(click)="InactivateHolderContact(contact.HOLDERCONTACTID)" matTooltip="Inactivate">
<mat-icon>delete</mat-icon>
</button>
<button mat-icon-button color="warn" *ngIf="contact.isInactive"
(click)="reactivateContact(contact.holdercontactid)" matTooltip="Reactivate">
<mat-icon>loupe</mat-icon>
(click)="ReactivateHolderContact(contact.HOLDERCONTACTID)" matTooltip="Reactivate">
<mat-icon>delete_outline</mat-icon>
</button>
</div>
<!-- <button mat-icon-button (click)="setDefaultContact(contact.contactId)"
@ -112,9 +99,8 @@
</tr>
</table>
<mat-paginator *ngIf="dataSource.data.length > userPreferences.pageSize!" [length]="dataSource.data.length"
[pageSizeOptions]="[userPreferences.pageSize || 1]" [hidePageSize]="true"
showFirstLastButtons></mat-paginator>
<mat-paginator [length]="dataSource.data.length" [pageSizeOptions]="[userPreferences.pageSize || 1]"
[hidePageSize]="true" showFirstLastButtons></mat-paginator>
</div>
<!-- Contact Form -->
@ -159,18 +145,20 @@
</div>
<div class="form-row">
<mat-form-field appearance="outline">
<mat-label>Title</mat-label>
<mat-select formControlName="title" required>
<mat-option *ngFor="let contactTitle of contactTitles" [value]="contactTitle.value">
{{ contactTitle.name }}
</mat-option>
</mat-select>
<input matInput formControlName="title" required>
<mat-icon matSuffix>work</mat-icon>
<mat-error *ngIf="contactForm.get('title')?.errors?.['required']">
Title is required
</mat-error>
<mat-error *ngIf="contactForm.get('title')?.errors?.['maxlength']">
Maximum 100 characters allowed
</mat-error>
</mat-form-field>
</div>
<div class="form-row">
<mat-form-field appearance="outline">
<mat-label>Phone</mat-label>
@ -266,10 +254,10 @@
</div>
<div class="form-actions">
<button mat-raised-button color="primary" type="submit" [disabled]="contactForm.invalid || changeInProgress">
<button mat-button type="button" (click)="cancelEdit()">Cancel</button>
<button mat-raised-button color="primary" type="submit" [disabled]="contactForm.invalid">
{{ isEditing ? 'Update' : 'Save' }}
</button>
<button mat-button type="button" (click)="cancelEdit()">Cancel</button>
</div>
</form>
</div>

View File

@ -0,0 +1,354 @@
.contacts-container {
padding: 24px;
display: flex;
flex-direction: column;
gap: 24px;
.actions-bar {
clear: both;
margin-bottom: -16px;
mat-slide-toggle {
transform: scale(0.8);
margin-left: -0.5rem;
}
button {
float: right;
}
}
.table-container {
position: relative;
overflow: auto;
border-radius: 8px;
.loading-shade {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.7);
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}
mat-table {
width: 100%;
mat-icon {
cursor: pointer;
transition: all 0.2s ease;
&:hover {
transform: scale(1.1);
}
}
.mat-column-actions {
width: 180px;
text-align: center;
}
.mat-column-defaultContact {
width: 80px;
text-align: center;
}
}
.no-data-message {
text-align: center;
padding: 0.9rem;
color: rgba(0, 0, 0, 0.54);
mat-icon {
font-size: 1rem;
width: 1rem;
height: 1rem;
margin-bottom: -3px;
}
}
mat-paginator {
border-top: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 0 0 8px 8px;
padding-top: 4px;
}
}
.form-container {
background-color: white;
padding: 24px;
border-radius: 8px;
margin-top: 16px;
.form-header {
margin-bottom: 24px;
h3 {
margin: 0;
color: var(--mat-sys-primary);
font-weight: 500;
}
}
form {
display: flex;
flex-direction: column;
gap: 16px;
.form-row {
display: flex;
gap: 16px;
mat-form-field {
flex: 1;
}
.small-field {
max-width: 120px;
}
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 16px;
margin-top: 16px;
}
.readonly-section {
padding-top: 0.5rem;
border-top: 1px solid #eee;
.readonly-fields {
display: flex;
gap: 2rem;
.field-column {
flex: 1;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
}
.readonly-field {
label {
display: block;
font-size: 0.875rem;
color: #666;
margin-bottom: 0.25rem;
}
.readonly-value {
padding: 0.25rem;
font-size: 0.9375rem;
display: flex;
align-items: center;
}
}
}
}
}
}
// Responsive adjustments
@media (max-width: 768px) {
.contacts-container {
padding: 16px;
.form-row {
flex-direction: column;
gap: 16px !important;
.small-field {
max-width: 100% !important;
}
}
}
}
.contacts-container {
padding: 24px;
display: flex;
flex-direction: column;
gap: 24px;
.actions-bar {
clear: both;
margin-bottom: -16px;
mat-slide-toggle {
transform: scale(0.8);
margin-left: -0.5rem;
}
button {
float: right;
}
}
.table-container {
position: relative;
overflow: auto;
border-radius: 8px;
.loading-shade {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.7);
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}
mat-table {
width: 100%;
.rowActions {
display: flex !important;
align-items: center;
justify-content: center;
gap: 8px;
}
mat-icon {
cursor: pointer;
transition: all 0.2s ease;
&:hover {
transform: scale(1.1);
}
}
.mat-column-actions {
width: 180px;
text-align: center;
}
.mat-column-defaultContact {
width: 80px;
text-align: center;
}
}
.no-data-message {
text-align: center;
padding: 0.9rem;
color: rgba(0, 0, 0, 0.54);
mat-icon {
font-size: 1rem;
width: 1rem;
height: 1rem;
margin-bottom: -3px;
}
}
mat-paginator {
border-top: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 0 0 8px 8px;
padding-top: 4px;
}
}
.form-container {
background-color: white;
padding: 24px;
border-radius: 8px;
margin-top: 16px;
.form-header {
margin-bottom: 24px;
h3 {
margin: 0;
color: var(--mat-sys-primary);
font-weight: 500;
}
}
form {
display: flex;
flex-direction: column;
gap: 16px;
.form-row {
display: flex;
gap: 16px;
mat-form-field {
flex: 1;
}
.small-field {
max-width: 120px;
}
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 16px;
margin-top: 16px;
}
.readonly-section {
padding-top: 0.5rem;
border-top: 1px solid #eee;
.readonly-fields {
display: flex;
gap: 2rem;
.field-column {
flex: 1;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
}
.readonly-field {
label {
display: block;
font-size: 0.875rem;
color: #666;
margin-bottom: 0.25rem;
}
.readonly-value {
padding: 0.25rem;
font-size: 0.9375rem;
display: flex;
align-items: center;
}
}
}
}
}
}
// Responsive adjustments
@media (max-width: 768px) {
.contacts-container {
padding: 16px;
.form-row {
flex-direction: column;
gap: 16px !important;
.small-field {
max-width: 100% !important;
}
}
}
}

View File

@ -0,0 +1,203 @@
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { PhonePipe } from '../../shared/pipes/phone.pipe';
import { CommonModule } from '@angular/common';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { UserPreferences } from '../../core/models/user-preference';
import { NotificationService } from '../../core/services/common/notification.service';
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
import { ContactFormModel, HolderContact } from '../../core/models/holder/contact';
import { ContactService } from '../../core/services/holder/contact.service';
@Component({
selector: 'app-holder-contacts',
imports: [AngularMaterialModule, ReactiveFormsModule, PhonePipe, CommonModule],
templateUrl: './holder-contacts.component.html',
styleUrl: './holder-contacts.component.scss'
})
export class HolderContactsComponent {
@Input() isEditMode: boolean = false;
@Input() holderid: number = 0;
@Input() userPreferences: UserPreferences = {};
@Output() hasContacts = new EventEmitter<boolean>();
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
displayedColumns: string[] = ['firstName', 'lastName', 'title', 'phone', 'mobile', 'email', 'actions'];
dataSource = new MatTableDataSource<any>();
contactForm: FormGroup;
isEditing = false;
currentContactId: number | null = null;
isLoading = false;
showForm = false;
showInactiveContacts = false;
contacts: HolderContact[] = [];
contactReadOnlyFields: any = {
lastChangedDate: null,
lastChangedBy: null,
isInactive: null,
inactivatedDate: null
};
constructor(
private fb: FormBuilder,
private contactService: ContactService,
private notificationService: NotificationService,
private errorHandler: ApiErrorHandlerService
) {
this.contactForm = this.fb.group({
firstName: ['', [Validators.required, Validators.maxLength(50)]],
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}$/)]],
email: ['', [Validators.required, Validators.email, Validators.maxLength(100)]],
});
}
ngOnInit(): void {
if (this.holderid > 0) {
this.loadContacts();
}
}
InactivateHolderContact(HOLDERCONTACTID: number): void {
this.contactService.InactivateHolderContact(HOLDERCONTACTID).subscribe(
{
next: (basicData: any) => {
this.notificationService.showSuccess(`HolderContact Inactivated successfully`);
this.loadContacts()
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, `Failed to Inactivate HolderContact`);
this.notificationService.showError(errorMessage);
console.error('Error Inactivating HolderContact:', error);
}
}
);
}
ReactivateHolderContact(HOLDERCONTACTID: number): void {
this.contactService.ReactivateHolderContact(HOLDERCONTACTID).subscribe(
{
next: (basicData: any) => {
this.notificationService.showSuccess(`HolderContact Reactivated successfully`);
this.loadContacts()
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, `Failed to Reactivate HolderContact`);
this.notificationService.showError(errorMessage);
console.error('Error Reactivating HolderContact:', error);
}
}
);
}
ngAfterViewInit() {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
loadContacts(): void {
this.isLoading = true;
this.contactService.getHolderContactsByHolderId(this.holderid).subscribe({
next: (contacts: HolderContact[]) => {
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);
}
});
}
addNewContact(): void {
this.showForm = true;
this.isEditing = false;
this.currentContactId = null;
this.contactForm.reset();
}
editContact(contact: HolderContact): void {
this.showForm = true;
this.isEditing = true;
this.currentContactId = contact.HOLDERCONTACTID;
this.contactForm.patchValue({
firstName: contact.FIRSTNAME,
lastName: contact.LASTNAME,
middleInitial: contact.MIDDLEINITIAL,
title: contact.TITLE,
phone: contact.PHONE,
mobile: contact.MOBILE,
fax: contact.FAX,
email: contact.EMAILADDRESS
});
this.contactReadOnlyFields.lastChangedDate = contact.LASTUPDATEDDATE ?? contact.DATECREATED;
this.contactReadOnlyFields.lastChangedBy = contact.LASTUPDATEDBY ?? contact.CREATEDBY;
this.contactReadOnlyFields.isInactive = contact.INACTIVEFLAG === 'Y';
this.contactReadOnlyFields.inactivatedDate = contact.INACTIVEDATE;
}
saveContact(): void {
if (this.contactForm.invalid && !(this.holderid > 0)) {
this.contactForm.markAllAsTouched();
return;
}
const contactData: ContactFormModel = this.contactForm.value;
const saveObservable = this.isEditing && (this.currentContactId! > 0)
? this.contactService.updateHolderContact(this.currentContactId!, contactData)
: this.contactService.createHolderContact(this.holderid, contactData);
saveObservable.subscribe({
next: () => {
this.notificationService.showSuccess(`Contact ${this.isEditing ? 'updated' : 'added'} successfully`);
this.loadContacts();
this.cancelEdit();
this.hasContacts.emit(true);
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, `Failed to ${this.isEditing ? 'update' : 'add'} contact`);
this.notificationService.showError(errorMessage);
console.error('Error saving contact:', error);
}
});
}
toggleShowInactiveContacts(): void {
this.showInactiveContacts = !this.showInactiveContacts;
this.renderContacts();
}
renderContacts(): void {
if (this.showInactiveContacts) {
this.dataSource.data = this.contacts.filter(contact => contact.INACTIVEFLAG === 'Y');
} else {
this.dataSource.data = this.contacts.filter(contact => contact.INACTIVEFLAG === 'N');
}
}
cancelEdit(): void {
this.showForm = false;
this.isEditing = false;
this.currentContactId = null;
this.contactForm.reset();
}
}

View File

@ -5,9 +5,10 @@
<mat-spinner diameter="50"></mat-spinner>
</div>
<form [formGroup]="basicDetailsForm" class="details-form" (ngSubmit)="saveBasicDetails()">
<form [formGroup]="holderDetailsForm" class="details-form"
(ngSubmit)="saveHolderDetails()">
<!-- Holder Information -->
<!-- Client Information -->
<div class="form-row">
<mat-form-field appearance="outline" class="name">
<mat-label>Holder Name</mat-label>
@ -22,46 +23,57 @@
<mat-form-field appearance="outline" class="lookup-code">
<mat-label>DBA Name</mat-label>
<input matInput formControlName="dbaName">
<mat-error *ngIf="f['dbaName'].errors?.['required']">
DBA name is required
<input matInput formControlName="DBAName">
<mat-error *ngIf="f['DBAName'].errors?.['required']">
Lookup code is required
</mat-error>
<mat-error *ngIf="f['dbaName'].errors?.['maxlength']">
<mat-error *ngIf="f['DBAName'].errors?.['maxlength']">
Maximum 20 characters allowed
</mat-error>
</mat-form-field>
</div>
<div class="form-row">
<mat-form-field appearance="outline" class="holder-number">
<mat-form-field appearance="outline" class="HolderNumber">
<mat-label>Tax ID No</mat-label>
<input matInput formControlName="holderNumber" required>
<mat-error *ngIf="f['holderNumber'].errors?.['required']">
Tax ID No is required
<input matInput formControlName="HolderNumber" required>
<mat-error *ngIf="f['HolderNumber'].errors?.['required']">
Lookup code is required
</mat-error>
<mat-error *ngIf="f['holderNumber'].errors?.['maxlength']">
<mat-error *ngIf="f['HolderNumber'].errors?.['maxlength']">
Maximum 20 characters allowed
</mat-error>
</mat-form-field>
</div>
<div class="form-row">
<div class="question-row">
<mat-radio-group formControlName="holderType">
<mat-label>Holder Type : </mat-label>
<mat-radio-button value="CORP">Corporation</mat-radio-button>
<mat-radio-button value="IND">Individual</mat-radio-button>
<mat-radio-button value="GOV ">Government Agency </mat-radio-button>
</mat-radio-group>
</div>
<mat-radio-group formControlName="holderType" class="radio-group">
<mat-label>Holder Type </mat-label>
<mat-radio-button value="CORP">Corporation</mat-radio-button>
<mat-radio-button value="IND">Individual</mat-radio-button>
<mat-radio-button value="GOV ">Government Agency </mat-radio-button>
</mat-radio-group>
</div>
<div class="form-row">
<mat-radio-group formControlName="uscibMember" class="radio-group">
<mat-label>Are you a member of USCIB ?</mat-label>
<mat-radio-button [value]="true">Yes</mat-radio-button>
<mat-radio-button [value]="false">No</mat-radio-button>
</mat-radio-group>
<!-- <mat-radio-group formControlName="govAgency" class="radio-group">
<mat-label>Are you belong to Government Agency ?</mat-label>
<mat-radio-button [value]="true">Yes</mat-radio-button>
<mat-radio-button [value]="false">No</mat-radio-button>
</mat-radio-group> -->
<div class="question-row">
<mat-radio-group formControlName="USCIBMember">
<mat-label>Are you a member of USCIB ?</mat-label>
<mat-radio-button [value]="'Y'">Yes</mat-radio-button>
<mat-radio-button [value]="'N'">No</mat-radio-button>
</mat-radio-group>
</div>
</div>
<div class="form-row">
<div class="question-row">
<mat-radio-group formControlName="govAgency">
<mat-label>Are you belong to Government Agency ?</mat-label>
<mat-radio-button [value]="'Y'">Yes</mat-radio-button>
<mat-radio-button [value]="'N'">No</mat-radio-button>
</mat-radio-group>
</div>
</div>
<!-- Address Information -->
@ -143,12 +155,8 @@
</mat-form-field>
</div>
<div class="form-actions">
<button mat-raised-button color="accent" *ngIf="currentApplicationDetails" type="button"
(click)="goBackToCarnetApplication()">
<mat-icon>chevron_left</mat-icon> Back to application
</button>
<button mat-raised-button color="primary" type="submit"
[disabled]="basicDetailsForm.invalid || !basicDetailsForm.dirty || changeInProgress">
[disabled]="holderDetailsForm.invalid || !holderDetailsForm.dirty">
{{ isEditMode ? 'Update' : 'Save' }}
</button>
</div>

Some files were not shown because too many files have changed in this diff Show More