holder selection flow completed

This commit is contained in:
Kallesh B S 2025-07-14 19:30:28 +05:30
parent 0c12e3fcbd
commit fa5cdfd5ee
26 changed files with 2316 additions and 148 deletions

View File

@ -14,6 +14,8 @@ export const routes: Routes = [
{ path: 'usersettings', loadComponent: () => import('./user-settings/user-settings.component').then(m => m.UserSettingsComponent) }, { 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: '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: 'edit-carnet/:headerid', loadComponent: () => import('./carnet/edit/edit-carnet.component').then(m => m.EditCarnetComponent) },
{ 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' } { path: '', redirectTo: 'home', pathMatch: 'full' }
], ],
canActivate: [AuthGuard, AppIdGuard] canActivate: [AuthGuard, AppIdGuard]

View File

@ -4,64 +4,69 @@
[selectedIndex]="currentStep"> [selectedIndex]="currentStep">
<!-- Application Name Step --> <!-- Application Name Step -->
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.applicationDetail" <mat-step *ngIf="store.applicationType() === 'new'" [completed]="store.stepsCompleted().applicationDetail"
[editable]="stepsCompleted.applicationDetail"> [editable]="store.stepsCompleted().applicationDetail">
<ng-template matStepLabel>Application Name</ng-template> <ng-template matStepLabel>Application Name</ng-template>
<app-application [isEditMode]="isEditMode" (headerIdCreated)="onApplicationDetailCreated($event)"> <app-application [isEditMode]="store.isEditMode()" [applicationName]="store.applicationName()"
[headerid]="store.headerId()" (headerIdCreated)="onApplicationDetailCreated($event)">
</app-application> </app-application>
</mat-step> </mat-step>
<!-- Holder Selection Step --> <!-- Holder Selection Step -->
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.holderSelection" <mat-step *ngIf="store.applicationType() === 'new'" [completed]="store.stepsCompleted().holderSelection"
[editable]="stepsCompleted.applicationDetail"> [editable]="store.stepsCompleted().applicationDetail">
<ng-template matStepLabel>Holder Selection</ng-template> <ng-template matStepLabel>Holder Selection</ng-template>
<!-- <app-holder (completed)="onHolderSelectionSaved($event)" [headerid]="headerid"> <!-- <app-holder (completed)="onHolderSelectionSaved($event)" [headerid]="headerid">
</app-holder> --> </app-holder> -->
<app-search (selectHolderCompleted)="onHolderSelectionSaved($event)" [headerid]="headerid"></app-search> <app-search [searchedHolder]="store.searchedHolder()" (searchedHolderEmit)="onSearchedHolder($event)"
(selectHolderCompleted)="onHolderSelectionSaved($event)" [HOLDERID]="store.holderid()" [headerid]="store.headerId()">
</app-search>
</mat-step> </mat-step>
<!-- Goods Section Step --> <!-- Goods Section Step -->
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.goodsSection" <mat-step *ngIf="store.applicationType() === 'new'" [completed]="store.stepsCompleted().goodsSection"
[editable]="stepsCompleted.applicationDetail"> [editable]="store.stepsCompleted().applicationDetail">
<ng-template matStepLabel>Goods Section</ng-template> <ng-template matStepLabel>Goods Section</ng-template>
<app-goods (completed)="onGoodsSectionSaved($event)" [headerid]="headerid"> <app-goods (completed)="onGoodsSectionSaved($event)" [headerid]="store.headerId()">
</app-goods> </app-goods>
</mat-step> </mat-step>
<!-- Travel Plan Step --> <!-- Travel Plan Step -->
<mat-step *ngIf="applicationType === 'new' || applicationType === 'extend' || applicationType === 'additional'" <mat-step
[completed]="stepsCompleted.travelPlan" [editable]="stepsCompleted.applicationDetail"> *ngIf="store.applicationType() === 'new' || store.applicationType() === 'extend' || store.applicationType() === 'additional'"
[completed]="store.stepsCompleted().travelPlan" [editable]="store.stepsCompleted().applicationDetail">
<ng-template matStepLabel>Travel Plan</ng-template> <ng-template matStepLabel>Travel Plan</ng-template>
<app-travel-plan (completed)="onTravelPlanSaved($event)" [headerid]="headerid"> <app-travel-plan (completed)="onTravelPlanSaved($event)" [headerid]="store.headerId()">
</app-travel-plan> </app-travel-plan>
</mat-step> </mat-step>
<!-- Insurance Step --> <!-- Insurance Step -->
<mat-step *ngIf="applicationType === 'new' || applicationType === 'extend' || applicationType === 'additional'" <mat-step
[completed]="stepsCompleted.insurance" [editable]="stepsCompleted.applicationDetail"> *ngIf="store.applicationType() === 'new' || store.applicationType() === 'extend' || store.applicationType() === 'additional'"
[completed]="store.stepsCompleted().insurance" [editable]="store.stepsCompleted().applicationDetail">
<ng-template matStepLabel>Insurance</ng-template> <ng-template matStepLabel>Insurance</ng-template>
<app-insurance (completed)="onInsuranceSaved($event)" [headerid]="headerid"> <app-insurance (completed)="onInsuranceSaved($event)" [headerid]="store.headerId()">
</app-insurance> </app-insurance>
</mat-step> </mat-step>
<!-- Shipping Step --> <!-- Shipping Step -->
<mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail"> <mat-step [completed]="store.stepsCompleted().shipping" [editable]="store.stepsCompleted().applicationDetail">
<ng-template matStepLabel>Shipping</ng-template> <ng-template matStepLabel>Shipping</ng-template>
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid"> <app-shipping (completed)="onShippingSaved($event)" [headerid]="store.headerId()">
</app-shipping> </app-shipping>
</mat-step> </mat-step>
<!-- Delivery Method Step --> <!-- Delivery Method Step -->
<mat-step [completed]="stepsCompleted.deliveryMethod" [editable]="stepsCompleted.applicationDetail"> <mat-step [completed]="store.stepsCompleted().deliveryMethod" [editable]="store.stepsCompleted().applicationDetail">
<ng-template matStepLabel>Delivery Method</ng-template> <ng-template matStepLabel>Delivery Method</ng-template>
<app-delivery (completed)="onDeliveryMethodSaved($event)" [headerid]="headerid"> <app-delivery (completed)="onDeliveryMethodSaved($event)" [headerid]="store.headerId()">
</app-delivery> </app-delivery>
</mat-step> </mat-step>
<!-- Payment Step --> <!-- Payment Step -->
<mat-step [completed]="stepsCompleted.payment" [editable]="stepsCompleted.applicationDetail"> <mat-step [completed]="store.stepsCompleted().payment" [editable]="store.stepsCompleted().applicationDetail">
<ng-template matStepLabel>Payment</ng-template> <ng-template matStepLabel>Payment</ng-template>
<app-payment (completed)="onPaymentSaved($event)" [headerid]="headerid"> <app-payment (completed)="onPaymentSaved($event)" [headerid]="store.headerId()">
</app-payment> </app-payment>
</mat-step> </mat-step>
</mat-stepper> </mat-stepper>

View File

@ -13,6 +13,11 @@ import { ShippingComponent } from '../shipping/shipping.component';
import { DeliveryComponent } from '../delivery/delivery.component'; import { DeliveryComponent } from '../delivery/delivery.component';
import { SearchComponent } from '../../holder/search/search.component'; import { SearchComponent } from '../../holder/search/search.component';
import { CarnetStateStore } from '../../core/services/carnet/carnet-application.service';
import { ActivatedRoute, NavigationStart, Router } from '@angular/router';
import { filter, Subscription } from 'rxjs';
import { UserService } from '../../core/services/common/user.service';
@Component({ @Component({
selector: 'app-add-carnet', selector: 'app-add-carnet',
imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule, imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule,
@ -22,59 +27,123 @@ import { SearchComponent } from '../../holder/search/search.component';
styleUrl: './add-carnet.component.scss' styleUrl: './add-carnet.component.scss'
}) })
export class AddCarnetComponent { export class AddCarnetComponent {
store = inject(CarnetStateStore);
private router = inject(Router);
private route = inject(ActivatedRoute);
private navigationSub!: Subscription;
private previousUrl: string = '';
currentStep = 0; currentStep = 0;
isLinear = true; isLinear = true;
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = 'new';
isEditMode = false;
headerid: number = 0;
// Track completion of each step stepsCompleted = this.store.stepsCompleted();
stepsCompleted = {
applicationDetail: false, constructor(
holderSelection: false, private readonly userService: UserService,
goodsSection: false, ) { }
travelPlan: false,
insurance: false, ngOnInit() {
shipping: false,
deliveryMethod: false, this.previousUrl = this.router.url; // Capture initial
payment: false
}; console.log("onit url : ", this.previousUrl);
console.log(this.store.stepsCompleted().payment);
console.log(this.store.stepsCompleted().goodsSection);
this.navigationSub = this.router.events.pipe(
filter(e => e instanceof NavigationStart)
).subscribe((event: NavigationStart) => {
const nextUrl = event.url;
const from = this.previousUrl;
const to = nextUrl;
const isFromCreateFlow = this.isCreateFlowUrl(from);
const isToCreateFlow = this.isCreateFlowUrl(to);
// Only reset if we're entering the create flow from outside it
if (!isFromCreateFlow && isToCreateFlow) {
this.store.resetState();
}
// Update previous URL for the next nav
this.previousUrl = nextUrl;
});
}
ngOnDestroy() {
this.navigationSub?.unsubscribe();
}
private isCreateFlowUrl(url: string): boolean {
const segments = url.split('?')[0].split('/').filter(Boolean); // Remove query and empty strings
// You want to check the SECOND segment, i.e., segments[1]
const secondSegment = segments[1] || ''; // Avoid undefined
return ['add-carnet', 'add-holder', 'edit-holder'].includes(secondSegment.toLowerCase());
}
onStepChange(event: StepperSelectionEvent): void { onStepChange(event: StepperSelectionEvent): void {
this.currentStep = event.selectedIndex; this.currentStep = event.selectedIndex;
} }
onApplicationDetailCreated(headerid: number): void { onSearchedHolder(searchedHolder: string): void {
this.headerid = headerid; this.store.searchedHolder.set(searchedHolder);
this.stepsCompleted.applicationDetail = true; }
onApplicationDetailCreated(data: { HEADERID: number, APPLICATION_NAME: string }): void {
// this.headerid = headerid;
this.store.headerId.set(data.HEADERID);
this.store.applicationName.set(data.APPLICATION_NAME)
console.log("logging application emits : ", data);
console.log("logging application name after setting : ", this.store.applicationName());
// this.stepsCompleted.applicationDetail = true;
this.store.stepsCompleted.update(s => ({ ...s, applicationDetail: true }))
this.isLinear = false; // Disable linear mode after application detail is created this.isLinear = false; // Disable linear mode after application detail is created
} }
onHolderSelectionSaved(completed: boolean): void { onHolderSelectionSaved(HOLDERID: number): void {
this.stepsCompleted.holderSelection = completed; // this.stepsCompleted.holderSelection = true;
this.store.holderid.set(HOLDERID);
this.store.stepsCompleted.update(s => ({ ...s, holderSelection: true }))
} }
onGoodsSectionSaved(completed: boolean): void { onGoodsSectionSaved(completed: boolean): void {
this.stepsCompleted.goodsSection = completed; // this.stepsCompleted.goodsSection = completed;
this.store.stepsCompleted.update(s => ({ ...s, goodsSection: completed }))
} }
onTravelPlanSaved(completed: boolean): void { onTravelPlanSaved(completed: boolean): void {
this.stepsCompleted.travelPlan = completed; // this.stepsCompleted.travelPlan = completed;
this.store.stepsCompleted.update(s => ({ ...s, travelPlan: completed }))
} }
onInsuranceSaved(completed: boolean): void { onInsuranceSaved(completed: boolean): void {
this.stepsCompleted.insurance = completed; // this.stepsCompleted.insurance = completed;
this.store.stepsCompleted.update(s => ({ ...s, insurance: completed }))
} }
onShippingSaved(completed: boolean): void { onShippingSaved(completed: boolean): void {
this.stepsCompleted.shipping = completed; // this.stepsCompleted.shipping = completed;
this.store.stepsCompleted.update(s => ({ ...s, shipping: completed }))
} }
onDeliveryMethodSaved(completed: boolean): void { onDeliveryMethodSaved(completed: boolean): void {
this.stepsCompleted.deliveryMethod = completed; // this.stepsCompleted.deliveryMethod = completed;
this.store.stepsCompleted.update(s => ({ ...s, deliveryMethod: completed }))
} }
onPaymentSaved(completed: boolean): void { onPaymentSaved(completed: boolean): void {
this.stepsCompleted.payment = completed; // this.stepsCompleted.payment = completed;
this.store.stepsCompleted.update(s => ({ ...s, payment: completed }))
} }
} }

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, inject, Input, Output } from '@angular/core'; import { Component, EventEmitter, inject, Input, Output, SimpleChanges } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { ApplicationDetailService } from '../../core/services/carnet/application-detail.service'; import { ApplicationDetailService } from '../../core/services/carnet/application-detail.service';
import { NotificationService } from '../../core/services/common/notification.service'; import { NotificationService } from '../../core/services/common/notification.service';
@ -20,7 +20,7 @@ export class ApplicationComponent {
@Input() headerid: number = 0; @Input() headerid: number = 0;
@Input() applicationName: string = ''; @Input() applicationName: string = '';
@Output() headerIdCreated = new EventEmitter<number>(); @Output() headerIdCreated = new EventEmitter<{ HEADERID: number, APPLICATION_NAME: string }>();
applicationDetailsForm: FormGroup; applicationDetailsForm: FormGroup;
isLoading = false; isLoading = false;
@ -38,8 +38,20 @@ export class ApplicationComponent {
this.applicationDetailsForm = this.createForm(); this.applicationDetailsForm = this.createForm();
} }
// ngOnChanges(changes: SimpleChanges) {
// if (changes['applicationName']) {
// console.log('applicationName input:', this.applicationName);
// }
// if (changes['headerid']) {
// console.log('headerid input:', this.headerid);
// }
// }
ngOnInit(): void { ngOnInit(): void {
this.applicationName = this.route.snapshot.queryParamMap.get('applicationName') || ''; // this.applicationName = this.route.snapshot.queryParamMap.get('applicationName') || '';
console.log("a-------- : ", this.applicationName);
// Patch edit form data // Patch edit form data
if (this.applicationName) { if (this.applicationName) {
@ -78,7 +90,7 @@ export class ApplicationComponent {
name: this.applicationName, name: this.applicationName,
}); });
if (this.isEditMode) { if (this.isEditMode || this.applicationName) {
this.applicationDetailsForm.get('name')?.disable(); this.applicationDetailsForm.get('name')?.disable();
this.disableSaveButton = true; this.disableSaveButton = true;
} }
@ -101,8 +113,10 @@ export class ApplicationComponent {
this.isLoading = true; this.isLoading = true;
this.applicationDetailService.createApplicationDetails(applicationDetailData).subscribe({ this.applicationDetailService.createApplicationDetails(applicationDetailData).subscribe({
next: (applicationData: any) => { next: (applicationData: any) => {
console.log("app data : ",applicationData);
this.notificationService.showSuccess(`Application details added successfully`); this.notificationService.showSuccess(`Application details added successfully`);
this.headerIdCreated.emit(applicationData.HEADERID); this.headerIdCreated.emit({ HEADERID: applicationData.HEADERID, APPLICATION_NAME: applicationDetailData.name });
this.applicationDetailsForm.get('name')?.disable(); this.applicationDetailsForm.get('name')?.disable();
this.disableSaveButton = true; this.disableSaveButton = true;
this.isLoading = false; this.isLoading = false;
@ -114,6 +128,14 @@ export class ApplicationComponent {
this.isLoading = false; this.isLoading = false;
} }
}); });
console.log("application details : ", applicationDetailData);
// this.headerIdCreated.emit({ HEADERID: 1001, APPLICATION_NAME: applicationDetailData.name });
// this.applicationDetailsForm.get('name')?.disable();
// this.disableSaveButton = true;
// this.isLoading = false;
} }
} }
} }

View File

@ -0,0 +1,30 @@
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;
title: string;
phone: string;
mobile: string;
fax: string;
email: string;
};

View File

@ -1,5 +1,5 @@
export interface HolderDetail { export interface HolderDetail {
HOLDERID: number; HOLDERID?: number;
SPID: number; SPID: number;
LOCATIONID: number; LOCATIONID: number;
HOLDERNO: string; HOLDERNO: string;
@ -22,3 +22,19 @@ export interface HolderDetail {
LASTUPDATEDBY: string | null; LASTUPDATEDBY: string | null;
LASTUPDATEDDATE: 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

@ -0,0 +1,46 @@
// appli.service.ts
import { Injectable, signal } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CarnetStateStore {
headerId = signal<number>(0);
applicationName = signal<string>('');
searchedHolder = signal<string>('');
holderid = signal<number>(0);
applicationType = signal<'new' | 'additional' | 'duplicate' | 'extend' | null>('new');
isEditMode = signal<boolean>(false);
stepsCompleted = signal({
applicationDetail: false,
holderSelection: false,
goodsSection: false,
travelPlan: false,
insurance: false,
shipping: false,
deliveryMethod: false,
payment: false
});
resetState() {
console.log("CALLED RESET STATE");
this.headerId.set(0);
this.applicationName.set('');
this.searchedHolder.set('');
this.holderid.set(0);
this.applicationType.set('new');
this.isEditMode.set(false);
this.stepsCompleted.set({
applicationDetail: false, // this is intentionally true on reset?
holderSelection: false,
goodsSection: false,
travelPlan: false,
insurance: false,
shipping: false,
deliveryMethod: false,
payment: false
});
}
}

View File

@ -0,0 +1,92 @@
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 { ContactFormModel, HolderContact } from '../../models/holder/contact';
@Injectable({
providedIn: 'root'
})
export class ContactService {
private apiUrl = environment.apiUrl;
private apiDb = environment.apiDb;
constructor(private http: HttpClient, private userService: UserService) { }
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[]): 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,
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
}));
}
createHolderContact(HOLDERID: number, data: ContactFormModel): Observable<any> {
const contact = {
P_SPID: this.userService.getUserSpid(),
P_HOLDERID: HOLDERID,
P_CONTACTSTABLE: [{
P_FIRSTNAME: data.firstName,
P_LASTNAME: data.lastName,
P_MIDDLEINITIAL: data.middleInitial,
P_TITLE: data.title,
P_EMAILADDRESS: data.email,
P_MOBILENO: data.mobile,
P_PHONENO: data.phone,
P_FAXNO: data.fax
}],
P_USERID: this.userService.getUser()
}
return this.http.post(`${this.apiUrl}/${this.apiDb}/CreateHoldercontact`, contact);
}
updateHolderContact(P_HOLDERCONTACTID: number, data: ContactFormModel): Observable<any> {
const contact = {
P_HOLDERCONTACTID,
P_SPID: this.userService.getUserSpid(),
P_FIRSTNAME: data.firstName,
P_LASTNAME: data.lastName,
P_MIDDLEINITIAL: data.middleInitial,
P_TITLE: data.title,
P_EMAILADDRESS: data.email,
P_MOBILENO: data.mobile,
P_PHONENO: data.phone,
P_FAXNO: data.fax,
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()}`, {});
}
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-detail';
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

@ -32,6 +32,14 @@ export class HolderService {
return this.http.patch(`${this.apiUrl}/${this.apiDb}/update-holder`, applicationDetail); return this.http.patch(`${this.apiUrl}/${this.apiDb}/update-holder`, applicationDetail);
} }
InactivateHolder(HOLDERID: number) {
return this.http.patch(`${this.apiUrl}/${this.apiDb}/InactivateHolder/${this.userService.getUserSpid()}/${HOLDERID}/${this.userService.getSafeUser()}`, {});
}
ReactivateHolder(HOLDERID: number) {
return this.http.patch(`${this.apiUrl}/${this.apiDb}/ReactivateHolder/${this.userService.getUserSpid()}/${HOLDERID}/${this.userService.getSafeUser()}`, {});
}
// getPreparers(filter: PreparerFilter): Observable<BasicDetail[]> { // getPreparers(filter: PreparerFilter): Observable<BasicDetail[]> {
// return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetPreparers/${this.userService.getUserSpid()}/ACTIVE?P_NAME=${filter.name}&P_LOOKUPCODE=${filter.lookupCode}&P_CITY=${filter.city}&P_STATE=${filter.state}`).pipe( // return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetPreparers/${this.userService.getUserSpid()}/ACTIVE?P_NAME=${filter.name}&P_LOOKUPCODE=${filter.lookupCode}&P_CITY=${filter.city}&P_STATE=${filter.state}`).pipe(

View File

@ -0,0 +1,24 @@
<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 (headerIdCreated)="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 [userPreferences]="userPreferences"></app-holder-contacts>
</mat-step>
</mat-stepper>
</div>

View File

View File

@ -0,0 +1,57 @@
import { Component } 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 { 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 { HolderContactsComponent } from '../holder-contacts/holder-contacts.component';
import { UserPreferences } from '../../core/models/user-preference';
import { UserPreferencesService } from '../../core/services/user-preference.service';
@Component({
selector: 'app-add',
imports: [
AngularMaterialModule,
CommonModule,
ReactiveFormsModule,
HolderDetailComponent,
MatStepperModule,
MatFormFieldModule,
MatInputModule,
HolderContactsComponent
],
templateUrl: './add.component.html',
styleUrl: './add.component.scss'
})
export class AddComponent {
currentStep = 0;
isLinear = true;
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = 'new';
isEditMode = false;
holderid: number = 0;
userPreferences: UserPreferences;
stepsCompleted = {
holderDetails: false,
holderContacts: false
};
constructor(
private userPrefenceService: UserPreferencesService,
){
this.userPreferences = this.userPrefenceService.getPreferences();
}
onStepChange(event: StepperSelectionEvent): void {
this.currentStep = event.selectedIndex;
}
onHolderIdCreated(holderid: number): void {
this.holderid = holderid;
this.stepsCompleted.holderDetails = true;
console.log("holderid got successfully :: ", holderid);
}
}

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

@ -0,0 +1,40 @@
import { CommonModule } from '@angular/common';
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 { HolderDetailComponent } from '../holder-detail/holder-detail.component';
import { HolderContactsComponent } from '../holder-contacts/holder-contacts.component';
@Component({
selector: 'app-edit',
imports: [AngularMaterialModule, CommonModule, HolderDetailComponent, HolderContactsComponent],
templateUrl: './edit.component.html',
styleUrl: './edit.component.scss'
})
export class EditComponent {
accordion = viewChild.required(MatAccordion);
isEditMode = true;
holderid = 0;
holderName: string | null = null;
userPreferences: UserPreferences;
constructor(private route: ActivatedRoute, private userPrefenceService: UserPreferencesService) {
this.userPreferences = userPrefenceService.getPreferences();
afterNextRender(() => {
// Open all panels
this.accordion().openAll();
});
}
ngOnInit(): void {
const idParam = this.route.snapshot.paramMap.get('holderid');
this.holderid = idParam ? parseInt(idParam, 10) : 0;
}
onClientNameUpdate(event: string): void {
this.holderName = event;
}
}

View File

@ -0,0 +1,264 @@
<div class="contacts-container">
<div class="actions-bar">
<mat-slide-toggle (change)="toggleShowInactiveContacts()">
Show Inactive Contacts
</mat-slide-toggle>
<button mat-raised-button color="primary" (click)="addNewContact()">
<mat-icon>add</mat-icon> Add New Contact
</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>
<!-- 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>
</ng-container>
<!-- Title Column -->
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Title</th>
<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>
</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>
<!-- Email Column -->
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Email</th>
<td mat-cell *matCellDef="let contact">{{ contact.EMAILADDRESS }}</td>
</ng-container>
<!-- Default Contact Column -->
<!-- <ng-container matColumnDef="defaultContact">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Default</th>
<td mat-cell *matCellDef="let contact">
<mat-icon [color]="contact.defaultContact ? 'primary' : ''">
{{ contact.defaultContact ? 'star' : 'star_border' }}
</mat-icon>
</td>
</ng-container> -->
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>
<td mat-cell *matCellDef="let contact">
<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)="InactivateHolderContact(contact.HOLDERCONTACTID)" matTooltip="Inactivate">
<mat-icon>delete</mat-icon>
</button>
<button mat-icon-button color="warn" *ngIf="contact.isInactive"
(click)="ReactivateHolderContact(contact.HOLDERCONTACTID)" matTooltip="Reactivate">
<mat-icon>delete_outline</mat-icon>
</button>
</div>
<!-- <button mat-icon-button (click)="setDefaultContact(contact.contactId)"
[color]="contact.defaultContact ? 'primary' : ''" matTooltip="Set as default">
<mat-icon>star</mat-icon>
</button> -->
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr matNoDataRow *matNoDataRow>
<td [colSpan]="displayedColumns.length" class="no-data-message">
<mat-icon>info</mat-icon>
<span>No records available</span>
</td>
</tr>
</table>
<mat-paginator [length]="dataSource.data.length" [pageSizeOptions]="[userPreferences.pageSize || 1]"
[hidePageSize]="true" showFirstLastButtons></mat-paginator>
</div>
<!-- Contact Form -->
<div class="form-container" *ngIf="showForm">
<form [formGroup]="contactForm" (ngSubmit)="saveContact()">
<div class="form-header">
<h3>{{ isEditing ? 'Edit Contact' : 'Add New Contact' }}</h3>
</div>
<div class="form-row">
<mat-form-field appearance="outline">
<mat-label>First Name</mat-label>
<input matInput formControlName="firstName" required>
<mat-icon matSuffix>person</mat-icon>
<mat-error *ngIf="contactForm.get('firstName')?.errors?.['required']">
First name is required
</mat-error>
<mat-error *ngIf="contactForm.get('firstName')?.errors?.['maxlength']">
Maximum 50 characters allowed
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="small-field">
<mat-label>Middle Initial</mat-label>
<input matInput formControlName="middleInitial" maxlength="1">
<mat-error *ngIf="contactForm.get('middleInitial')?.errors?.['maxlength']">
Only 1 character allowed
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Last Name</mat-label>
<input matInput formControlName="lastName" required>
<mat-icon matSuffix>person</mat-icon>
<mat-error *ngIf="contactForm.get('lastName')?.errors?.['required']">
Last name is required
</mat-error>
<mat-error *ngIf="contactForm.get('lastName')?.errors?.['maxlength']">
Maximum 50 characters allowed
</mat-error>
</mat-form-field>
</div>
<div class="form-row">
<mat-form-field appearance="outline">
<mat-label>Title</mat-label>
<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>
<input matInput formControlName="phone" required>
<mat-icon matSuffix>phone</mat-icon>
<mat-error *ngIf="contactForm.get('phone')?.errors?.['required']">
Phone is required
</mat-error>
<mat-error *ngIf="contactForm.get('phone')?.errors?.['pattern']">
Please enter a valid phone number (10-15 digits)
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Mobile</mat-label>
<input matInput formControlName="mobile">
<mat-icon matSuffix>smartphone</mat-icon>
<mat-error *ngIf="contactForm.get('mobile')?.errors?.['required']">
Mobile is required
</mat-error>
<mat-error *ngIf="contactForm.get('mobile')?.errors?.['pattern']">
Please enter a valid mobile number (10-15 digits)
</mat-error>
</mat-form-field>
</div>
<div class="form-row">
<mat-form-field appearance="outline">
<mat-label>Fax</mat-label>
<input matInput formControlName="fax">
<mat-icon matSuffix>fax</mat-icon>
<mat-error *ngIf="contactForm.get('fax')?.errors?.['pattern']">
Please enter a valid fax number (10-15 digits)
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Email</mat-label>
<input matInput formControlName="email" required>
<mat-icon matSuffix>email</mat-icon>
<mat-error *ngIf="contactForm.get('email')?.errors?.['required']">
Email is required
</mat-error>
<mat-error *ngIf="contactForm.get('email')?.errors?.['email']">
Please enter a valid email address
</mat-error>
<mat-error *ngIf="contactForm.get('email')?.errors?.['maxlength']">
Maximum 100 characters allowed
</mat-error>
</mat-form-field>
</div>
<!--
<div class="form-row">
<mat-checkbox formControlName="defaultContact">Default Contact</mat-checkbox>
</div> -->
<div *ngIf="isEditing" class="readonly-section">
<div class="readonly-fields">
<div class="field-column">
<!-- Last Changed By -->
<div class="readonly-field">
<label>Last Changed By</label>
<div class="readonly-value">
{{contactReadOnlyFields.lastChangedBy || 'N/A'}}
</div>
</div>
<!-- Inactive status -->
<div class="readonly-field">
<label>Inactive Status </label>
<div class="readonly-value">
{{contactReadOnlyFields.isInactive === true ? 'Yes' : 'No' }}
</div>
</div>
</div>
<div class="field-column">
<!-- Last Changed Date -->
<div class="readonly-field">
<label>Last Changed Date</label>
<div class="readonly-value">
{{(contactReadOnlyFields.lastChangedDate | date:'mediumDate':'UTC') || 'N/A'}}
</div>
</div>
<!-- Inactivated Date -->
<div class="readonly-field">
<label>Inactivated Date</label>
<div class="readonly-value">
{{(contactReadOnlyFields.inactivatedDate | date:'mediumDate':'UTC') || 'N/A'}}
</div>
</div>
</div>
</div>
</div>
<div class="form-actions">
<button mat-button type="button" (click)="cancelEdit()">Cancel</button>
<button mat-raised-button color="primary" type="submit" [disabled]="contactForm.invalid">
{{ isEditing ? 'Update' : 'Save' }}
</button>
</div>
</form>
</div>
</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,373 @@
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 { MatDialog } from '@angular/material/dialog';
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 dialog: MatDialog,
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)]],
// defaultContact: [false]
});
}
ngOnInit(): void {
if (this.holderid > 0) {
this.loadContacts();
}
// 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;
}
loadContactsX(): void {
this.isLoading = true;
this.contacts = [
{
HOLDERCONTACTID: 101,
HOLDERID: 3001,
SPID: 5001,
FIRSTNAME: "Alice",
LASTNAME: "Johnson",
MIDDLEINITIAL: "M",
TITLE: "Product Manager",
PHONE: "1234567890",
MOBILE: "0987654321",
FAX: "1112223333",
EMAILADDRESS: "alice.johnson@example.com",
DATECREATED: new Date().toISOString(),
CREATEDBY: "admin",
INACTIVEFLAG: "Y",
INACTIVEDATE: new Date().toISOString(),
LASTUPDATEDBY: "alice",
LASTUPDATEDDATE: new Date().toISOString()
},
{
HOLDERCONTACTID: 102,
HOLDERID: 3001,
SPID: 5002,
FIRSTNAME: "Bob",
LASTNAME: "Smith",
MIDDLEINITIAL: null,
TITLE: "Sales Associate",
PHONE: "2345678901",
MOBILE: "8765432109",
FAX: null,
EMAILADDRESS: "bob.smith@example.com",
DATECREATED: new Date().toISOString(),
CREATEDBY: "hr_user",
INACTIVEFLAG: "Y",
INACTIVEDATE: new Date().toISOString(),
LASTUPDATEDBY: "hr_user",
LASTUPDATEDDATE: new Date().toISOString()
},
{
HOLDERCONTACTID: 103,
HOLDERID: 3002,
SPID: 5003,
FIRSTNAME: "Charlie",
LASTNAME: "Brown",
MIDDLEINITIAL: "R",
TITLE: "Developer",
PHONE: "3456789012",
MOBILE: "7654321098",
FAX: "4445556666",
EMAILADDRESS: "charlie.brown@example.com",
DATECREATED: new Date().toISOString(),
CREATEDBY: "it_admin",
INACTIVEFLAG: "N",
INACTIVEDATE: null,
LASTUPDATEDBY: "charlie",
LASTUPDATEDDATE: new Date().toISOString()
},
{
HOLDERCONTACTID: 104,
HOLDERID: 3003,
SPID: 5004,
FIRSTNAME: "Diana",
LASTNAME: "Evans",
MIDDLEINITIAL: "K",
TITLE: "Marketing Director",
PHONE: "4567890123",
MOBILE: "6543210987",
FAX: "7778889999",
EMAILADDRESS: "diana.evans@example.com",
DATECREATED: new Date().toISOString(),
CREATEDBY: "marketing_admin",
INACTIVEFLAG: "N",
INACTIVEDATE: null,
LASTUPDATEDBY: "diana",
LASTUPDATEDDATE: new Date().toISOString()
},
{
HOLDERCONTACTID: 105,
HOLDERID: 3003,
SPID: 5005,
FIRSTNAME: "Edward",
LASTNAME: "Norton",
MIDDLEINITIAL: null,
TITLE: "Finance Analyst",
PHONE: "5678901234",
MOBILE: "5432109876",
FAX: null,
EMAILADDRESS: "edward.norton@example.com",
DATECREATED: new Date().toISOString(),
CREATEDBY: "finance_team",
INACTIVEFLAG: "N",
INACTIVEDATE: null,
LASTUPDATEDBY: "edward",
LASTUPDATEDDATE: new Date().toISOString()
},
{
HOLDERCONTACTID: 106,
HOLDERID: 3004,
SPID: 5006,
FIRSTNAME: "Fiona",
LASTNAME: "Griffin",
MIDDLEINITIAL: "L",
TITLE: "HR Manager",
PHONE: "6789012345",
MOBILE: "4321098765",
FAX: "3334445555",
EMAILADDRESS: "fiona.griffin@example.com",
DATECREATED: new Date().toISOString(),
CREATEDBY: "hr_admin",
INACTIVEFLAG: "N",
INACTIVEDATE: null,
LASTUPDATEDBY: "fiona",
LASTUPDATEDDATE: new Date().toISOString()
}
];
this.renderContacts();
this.isLoading = false;
}
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();
// this.contactForm.patchValue({ defaultContact: false });
}
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.contactForm.markAllAsTouched();
return;
}
// default the first contact
const contactData: ContactFormModel = this.contactForm.value;
// contactData.defaultContact = this.dataSource?.data?.length === 0;
console.log("Contact details : ", contactData);
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;
this.dataSource.data = this.contacts.filter(contact => contact.INACTIVEFLAG === 'Y');
} else {
this.dataSource.data = this.contacts.filter(contact => contact.INACTIVEFLAG === 'N');
}
}
deleteContact(contactId: string): void {
// const dialogRef = this.dialog.open(ConfirmDialogComponent, {
// width: '350px',
// data: {
// title: 'Confirm Delete',
// message: 'Are you sure you want to delete this contact?',
// confirmText: 'Delete',
// cancelText: 'Cancel'
// }
// });
// dialogRef.afterClosed().subscribe(result => {
// if (result) {
// this.contactService.deleteContact(contactId).subscribe({
// next: () => {
// this.notificationService.showSuccess('Contact deleted successfully');
// this.loadContacts();
// },
// error: (error) => {
// let errorMessage = this.errorHandler.handleApiError(error, 'Failed to delete contact');
// this.notificationService.showError(errorMessage);
// console.error('Error deleting contact:', error);
// }
// });
// }
// });
}
createLogin(): void {
}
cancelEdit(): void {
this.showForm = false;
this.isEditing = false;
this.currentContactId = null;
this.contactForm.reset();
}
}

View File

@ -0,0 +1,166 @@
<div class="basic-details-container">
<mat-card class="details-card mat-elevation-z4">
<mat-card-content>
<div class="loading-shade" *ngIf="isLoading">
<mat-spinner diameter="50"></mat-spinner>
</div>
<form [formGroup]="holderDetailsForm" class="details-form"
(ngSubmit)="saveHolderDetails()">
<!-- Client Information -->
<div class="form-row">
<mat-form-field appearance="outline" class="name">
<mat-label>Holder Name</mat-label>
<input matInput formControlName="holderName" required>
<mat-error *ngIf="f['holderName'].errors?.['required']">
Name is required
</mat-error>
<mat-error *ngIf="f['holderName'].errors?.['maxlength']">
Maximum 100 characters allowed
</mat-error>
</mat-form-field>
<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']">
Lookup code is required
</mat-error>
<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="HolderNumber">
<mat-label>Tax ID No</mat-label>
<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']">
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>
</div>
<div class="form-row">
<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 -->
<div class="form-row">
<mat-form-field appearance="outline" class="address1">
<mat-label>Address Line 1</mat-label>
<input matInput formControlName="address1" required>
<mat-error *ngIf="f['address1'].errors?.['required']">
Address is required
</mat-error>
<mat-error *ngIf="f['address1'].errors?.['maxlength']">
Maximum 100 characters allowed
</mat-error>
</mat-form-field>
</div>
<div class="form-row">
<mat-form-field appearance="outline" class="address2">
<mat-label>Address Line 2 (Optional)</mat-label>
<input matInput formControlName="address2">
<mat-error *ngIf="f['address2'].errors?.['maxlength']">
Maximum 100 characters allowed
</mat-error>
</mat-form-field>
</div>
<!-- Location Information -->
<div class="form-row">
<mat-form-field appearance="outline" class="city">
<mat-label>City</mat-label>
<input matInput formControlName="city" required>
<mat-error *ngIf="f['city'].errors?.['required']">
City is required
</mat-error>
<mat-error *ngIf="f['city'].errors?.['maxlength']">
Maximum 50 characters allowed
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="country">
<mat-label>Country</mat-label>
<mat-select formControlName="country" required
(selectionChange)="onCountryChange($event.value)">
<mat-option *ngFor="let country of countries" [value]="country.value">
{{ country.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="f['country'].errors?.['required']">
Country is required
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="state">
<mat-label>State/Province</mat-label>
<mat-select formControlName="state" required>
<mat-option *ngFor="let state of states" [value]="state.value">
{{ state.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="f['state'].errors?.['required']">
State is required
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="zip">
<mat-label>ZIP/Postal Code</mat-label>
<input matInput formControlName="zip" required>
<mat-error *ngIf="f['zip'].errors?.['required']">
ZIP/Postal code is required
</mat-error>
<mat-error
*ngIf="f['country']?.value === 'US' && f['zip']?.touched && f['zip']?.errors?.['invalidUSZip']">
Please enter a valid 5-digit US ZIP code
</mat-error>
<mat-error
*ngIf="f['country']?.value === 'CA' && f['zip']?.touched && f['zip']?.errors?.['invalidCanadaPostal']">
Please enter a valid postal code (e.g., A1B2C3)
</mat-error>
</mat-form-field>
</div>
<div class="form-actions">
<button mat-raised-button color="primary" type="submit"
[disabled]="holderDetailsForm.invalid || !holderDetailsForm.dirty">
Save
</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>

View File

@ -0,0 +1,125 @@
.basic-details-container {
display: flex;
flex-direction: column;
gap: 24px;
width: 100%;
.details-card {
overflow: hidden;
transition: all 0.3s ease;
position: relative;
background: none;
box-shadow: none;
.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;
}
.details-form {
display: flex;
flex-direction: column;
gap: 16px;
}
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
align-items: start;
.question-row {
margin-bottom: 1rem;
// padding: 1rem;
// background: #ffff;
border-radius: 4px;
// box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
mat-radio-group {
display: flex;
// flex-direction: column;
align-items: center;
gap: 8px;
mat-radio-button {
margin-right: 1rem;
}
}
}
.name {
grid-column: span 2;
}
.lookup-code {
grid-column: span 1;
}
.HolderNumber{
grid-column: span 1;
// background-color: azure;
margin: 0;
}
.address1,
.address2 {
grid-column: span 3;
}
.city,
.state,
.country,
.zip {
grid-column: span 1;
}
.carnet-issuing-region,
.revenue-location {
grid-column: span 1;
}
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 16px;
}
mat-form-field {
width: 100%;
}
}
}
@media (max-width: 960px) {
.basic-details-container {
.details-card {
.form-row {
grid-template-columns: 1fr;
.name,
.lookup-code,
.address1,
.address2,
.city,
.state,
.zip,
.country,
.carnet-issuing-region,
.revenue-location {
grid-column: span 1;
}
}
}
}
}

View File

@ -0,0 +1,239 @@
import { Component, EventEmitter, 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 { HolderDetail, HolderDetailFormType } from '../../core/models/holder/holder-detail';
import { HolderDetailService } from '../../core/services/holder/holder-detail.service';
import { CommonService } from '../../core/services/common/common.service';
import { Subject, takeUntil } from 'rxjs';
import { Country } from '../../core/models/country';
// interface Country {
// name: string;
// id: number;
// alpha2: string;
// alpha3: string;
// }
@Component({
selector: 'app-holder-detail',
imports: [AngularMaterialModule, ReactiveFormsModule, CommonModule],
templateUrl: './holder-detail.component.html',
styleUrl: './holder-detail.component.scss'
})
export class HolderDetailComponent implements OnInit, OnDestroy {
@Input() isEditMode = false;
@Input() holderid: number = 0;
@Output() headerIdCreated = new EventEmitter<number>();
@Output() clientName = new EventEmitter<string>();
holderDetailsForm: FormGroup;
countries: Country[] = [];
regions: Region[] = [];
states: State[] = [];
isLoading = false;
countriesHasStates = ['US', 'CA', 'MX'];
private destroy$ = new Subject<void>();
constructor(
private fb: FormBuilder,
private holderDetailService: HolderDetailService,
private notificationService: NotificationService,
private commonService: CommonService,
private errorHandler: ApiErrorHandlerService) {
this.holderDetailsForm = this.createForm();
}
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: ['', [Validators.required]],
govAgency: ['', [Validators.required]],
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')]]
});
}
ngOnInit(): void {
this.loadLookupData();
// if (this.isEditMode) {
// this.patchFormData(this.holder);
// }
if (this.holderid > 0) {
console.log('ngOnInit - holderid:', this.holderid);
this.isLoading = true;
this.holderDetailService.getHolderDetailByHolderId(this.holderid).subscribe({
next: (basicDetail: HolderDetail) => {
// alert(basicDetail.HOLDERNAME)
// if (basicDetail?.HOLDERID ? basicDetail.HOLDERID > 0 : false) {
this.patchFormData(basicDetail);
this.clientName.emit(basicDetail.HOLDERNAME);
this.isLoading = false;
// }
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load holder details');
this.notificationService.showError(errorMessage);
this.isLoading = false;
console.error('Error loading holder details:', error);
}
});
}
}
patchFormData(data: any): void {
console.log("patch data : -- ",data);
const selectedCountry = this.countries.find(
c => c.name === data.COUNTRY
);
this.holderDetailsForm.patchValue({
holderName: data.HOLDERNAME,
DBAName: data.ADDLNAME,
HolderNumber: data.HOLDERNO,
holderType: data.HOLDERTYPE,
USCIBMember: data.USCIBMEMBERFLAG,
govAgency: data.GOVAGENCYFLAG,
address1: data.ADDRESS1,
address2: data.ADDRESS2,
city: data.CITY,
state: data.STATE,
// country: selectedCountry?.value || null,
country: data.COUNTRY,
zip: data.ZIP
})
// this.loadStates(selectedCountry?.name || '');
if (data.COUNTRY) {
this.loadStates(data.COUNTRY);
}
// ✅ Ensure form validity is updated
// this.holderDetailsForm.updateValueAndValidity();
// ✅ Optional: mark pristine to track edits properly
// this.holderDetailsForm.markAsPristine();
this.holderDetailsForm.markAsPristine();
this.holderDetailsForm.markAsUntouched();
}
get f() {
return this.holderDetailsForm.controls;
}
onCountryChange(country: string): void {
this.holderDetailsForm.get('state')?.reset();
if (country) {
this.loadStates(country);
}
this.holderDetailsForm.get('zip')?.updateValueAndValidity();
}
saveHolderDetails() {
if (this.holderDetailsForm.invalid) {
this.holderDetailsForm.markAllAsTouched();
return;
}
const holderDetailData: HolderDetailFormType = this.holderDetailsForm.value;
const saveObservable = this.isEditMode && this.holderid > 0
? this.holderDetailService.updateHolderDetails(this.holderid, holderDetailData)
: this.holderDetailService.createHolderDetail(holderDetailData);
saveObservable.subscribe({
next: (basicData: any) => {
this.notificationService.showSuccess(`Holder details ${this.isEditMode ? 'updated' : 'added'} successfully`);
if (!this.isEditMode) {
this.headerIdCreated.emit(basicData.HOLDERID);
}
if (this.isEditMode) {
this.clientName.emit(holderDetailData.holderName);
}
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, `Failed to ${this.isEditMode ? 'update' : 'add'} Holder details`);
this.notificationService.showError(errorMessage);
console.error('Error saving basic details:', error);
}
});
}
updateStateControl(controlName: string, country: string): void {
const stateControl = this.holderDetailsForm.get(controlName);
if (this.countriesHasStates.includes(country)) {
stateControl?.enable();
} else {
stateControl?.disable();
stateControl?.setValue('FN');
}
}
loadLookupData(): void {
this.commonService.getCountries(0)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (countries) => {
this.countries = countries;
this.isLoading = false;
},
error: (error) => {
console.error('Failed to load countries', error);
this.isLoading = false;
}
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
loadStates(country: string): void {
this.isLoading = true;
country = this.countriesHasStates.includes(country) ? country : 'FN';
this.commonService.getStates(country, this.holderid)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (states) => {
this.states = states;
this.updateStateControl('state', country);
this.isLoading = false;
},
error: (error) => {
console.error('Failed to load states', error);
this.isLoading = false;
}
});
}
}

View File

@ -46,6 +46,12 @@
<mat-spinner diameter="50"></mat-spinner> <mat-spinner diameter="50"></mat-spinner>
</div> </div>
<div class="actions-bar" style="margin-bottom: 10px;">
<mat-slide-toggle (change)="toggleShowInactiveHolders()" [disabled]="holdersSearched.length === 0">
Show Inactive Holders
</mat-slide-toggle>
</div>
<table mat-table [dataSource]="dataSource" class="results-table" matSort> <table mat-table [dataSource]="dataSource" class="results-table" matSort>
<!-- Name Column --> <!-- Name Column -->
@ -94,14 +100,26 @@
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef class="actions-header">Actions</th> <th mat-header-cell *matHeaderCellDef class="actions-header">Actions</th>
<td mat-cell *matCellDef="let client" class="actions-cell"> <td mat-cell *matCellDef="let client" class="actions-cell">
<button mat-icon-button color="primary" (click)="onEdit(client.HolderID)" matTooltip="Edit" [disabled]="HolderSaved"> <button mat-icon-button color="primary" (click)="onEdit(client.HOLDERID)" matTooltip="Edit"
[disabled]="HolderSaved">
<mat-icon>edit</mat-icon> <mat-icon>edit</mat-icon>
</button> </button>
<button mat-icon-button color="warn" *ngIf="client.INACTIVEFLAG === 'N'" matTooltip="Inactivate"
(click)="InactivateHolder(client.HOLDERID)">
<mat-icon>delete</mat-icon>
</button>
<button mat-icon-button color="warn" *ngIf="client.INACTIVEFLAG === 'Y'" matTooltip="Reactivate"
(click)="ReactivateHolder(client.HOLDERID)">
<mat-icon>delete_outline</mat-icon>
</button>
<mat-radio-button style="position: absolute; margin-left: 10px" matTooltip="Select Holder" <mat-radio-button style="position: absolute; margin-left: 10px" matTooltip="Select Holder"
class="selectHolder" [value]="client.HOLDERID" class="selectHolder" [value]="client.HOLDERID" [checked]="selectedHolderId === client.HOLDERID"
[checked]="selectedHolderId === client.HOLDERID" (change)="onRadioSelect(client)" (change)="onRadioSelect(client)" aria-label="Select row" [hidden]="client.INACTIVEFLAG === 'Y'">
aria-label="Select row" [disabled]="HolderSaved"></mat-radio-button> </mat-radio-button>
</td> </td>
</ng-container> </ng-container>
@ -122,15 +140,16 @@
showFirstLastButtons> showFirstLastButtons>
</mat-paginator> --> </mat-paginator> -->
<mat-paginator [length]="dataSource.data.length" [pageSizeOptions]="[2]" [hidePageSize]="true" <mat-paginator [length]="dataSource.data.length" [pageSizeOptions]="[userPreferences.pageSize || 2]"
showFirstLastButtons></mat-paginator> [hidePageSize]="true" showFirstLastButtons></mat-paginator>
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button mat-button type="button" (click)="cancelEdit()">Cancel</button> <button mat-button type="button" (click)="cancelEdit()">Cancel</button>
<button mat-raised-button color="primary" type="submit" [disabled]="selectedHolderId && HolderSaved" (click)="completeSelection()"> <button mat-raised-button color="primary" type="submit" [disabled]="selectedHolderId && HolderSaved"
(click)="completeSelection()">
{{ 'Save' }} {{ 'Save' }}
</button> </button>
</div> </div>
</div> </div>

View File

@ -9,9 +9,9 @@ import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort'; import { MatSort } from '@angular/material/sort';
import { HolderService } from '../../core/services/holder/holder.service'; import { HolderService } from '../../core/services/holder/holder.service';
import { HolderDetail } from '../../core/models/holder/holder-detail';
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service'; import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
import { NotificationService } from '../../core/services/common/notification.service'; import { NotificationService } from '../../core/services/common/notification.service';
import { HolderDetail } from '../../core/models/holder/holder-detail';
@Component({ @Component({
selector: 'app-search', selector: 'app-search',
@ -21,72 +21,49 @@ import { NotificationService } from '../../core/services/common/notification.ser
}) })
export class SearchComponent implements OnInit, AfterViewInit { export class SearchComponent implements OnInit, AfterViewInit {
@Output() selectHolderCompleted = new EventEmitter<boolean>(); @ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
@Output() selectHolderCompleted = new EventEmitter<number>();
@Output() searchedHolderEmit = new EventEmitter<string>();
@Input() searchedHolder: string = ''
@Input() headerid: number = 0; @Input() headerid: number = 0;
@Input() HOLDERID: number = 0
isEditing: boolean = false isEditing: boolean = false
selectedHolder: any = null; selectedHolder: any = null;
selectedHolderId: number = 0; selectedHolderId: number = 0;
HolderSaved: boolean = false;
showInactiveHolders: boolean = false;
holdersSearched: any = []
isLoading: boolean = false;
userPreferences: UserPreferences; userPreferences: UserPreferences;
searchForm: FormGroup; searchForm: FormGroup;
HolderSaved: boolean = false;
private holderService = inject(HolderService); private holderService = inject(HolderService);
private errorHandler = inject(ApiErrorHandlerService); private errorHandler = inject(ApiErrorHandlerService);
private notificationService = inject(NotificationService); private notificationService = inject(NotificationService);
dataSource = new MatTableDataSource<any>([]);
ngAfterViewInit() {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
private fb = inject(FormBuilder);
displayedColumns: string[] = ['HolderName', 'DBAName', 'Address', 'USCIBMember', 'HolderType', 'actions'];
constructor( constructor(
private userPrefenceService: UserPreferencesService, private userPrefenceService: UserPreferencesService,
// private cdr: ChangeDetectorRef,
private navigationService: NavigationService, private navigationService: NavigationService,
) { ) {
this.userPreferences = userPrefenceService.getPreferences(); this.userPreferences = this.userPrefenceService.getPreferences();
this.searchForm = this.createSearchForm(); this.searchForm = this.createSearchForm();
} }
// Call this method when the holder selection is completed
completeSelection() {
console.log("Headerid : ", this.headerid);
console.log("Headerid : ", this.selectedHolderId);
this.saveApplication();
}
addNewHolder(): void {
this.navigationService.navigate(["holder"], { state: { isEditMode: false } })
}
cancelEdit() {
this.clearSelection();
}
findHolderById(id: number) {
return this.dataSource.data.reduce((found: any, current: any) => {
return found || (current.HolderID === id ? current : null);
}, null);
}
onEdit(id: string) {
// this.selectedHolder = this.findHolderById(Number(id));
// console.log("Holder id: ", id, this.selectedHolder);
// this.navigationService.navigate(['holder', id], { state: { holder: this.selectedHolder } });
}
isLoading = false;
displayedColumns: string[] = [
'HolderName',
'DBAName',
'Address',
'USCIBMember',
'HolderType',
'actions',
];
dataSource = new MatTableDataSource<any>([]);
private fb = inject(FormBuilder);
createSearchForm(): FormGroup { createSearchForm(): FormGroup {
return this.fb.group({ return this.fb.group({
holderName: [''] holderName: ['']
@ -94,7 +71,43 @@ export class SearchComponent implements OnInit, AfterViewInit {
} }
ngOnInit(): void { ngOnInit(): void {
if (this.searchedHolder) {
this.patchData()
this.searchHolders();
}
if (this.HOLDERID > 0) {
this.selectedHolderId = this.HOLDERID;
}
}
patchData() {
this.searchForm.patchValue(
{ holderName: this.searchedHolder }
)
}
searchHolders(): void {
this.isLoading = true;
console.log("search form : ", this.searchForm.value);
const filterData: { holderName: string } = this.searchForm.value;
this.holderService.searchHolders(filterData).subscribe({
next: (holders: HolderDetail[]) => {
this.holdersSearched = holders
this.dataSource.data = this.holdersSearched;
this.renderHolders()
this.isLoading = false;
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to search preparers');
this.notificationService.showError(errorMessage);
this.isLoading = false;
console.error('Error loading preparers:', error);
}
});
} }
isSearchCriteriaProvided(): boolean { isSearchCriteriaProvided(): boolean {
@ -109,26 +122,11 @@ export class SearchComponent implements OnInit, AfterViewInit {
this.searchHolders(); this.searchHolders();
} }
searchHolders(): void { completeSelection() {
console.log("Headerid : ", this.headerid);
this.isLoading = true; console.log("Headerid : ", this.selectedHolderId);
console.log("search form : ", this.searchForm.value);
const filterData: { holderName: string } = this.searchForm.value;
this.holderService.searchHolders(filterData).subscribe({
next: (holders: HolderDetail[]) => {
this.dataSource.data = holders;
this.isLoading = false;
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to search preparers');
this.notificationService.showError(errorMessage);
this.isLoading = false;
console.error('Error loading preparers:', error);
}
});
this.saveApplication();
} }
saveApplication(): void { saveApplication(): void {
@ -137,9 +135,9 @@ export class SearchComponent implements OnInit, AfterViewInit {
saveObservable.subscribe({ saveObservable.subscribe({
next: (basicData: any) => { next: (basicData: any) => {
this.notificationService.showSuccess(`Holder updated successfully`); this.notificationService.showSuccess(`Holder updated successfully`);
this.selectHolderCompleted.emit(true); this.selectHolderCompleted.emit(this.selectedHolderId);
this.searchForm.get('holderName')?.disable(); // this.searchForm.get('holderName')?.disable();
this.HolderSaved = true // this.HolderSaved = true
}, },
error: (error: any) => { error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, `Failed to update holder details`); let errorMessage = this.errorHandler.handleApiError(error, `Failed to update holder details`);
@ -147,32 +145,38 @@ export class SearchComponent implements OnInit, AfterViewInit {
console.error('Error saving holder details:', error); console.error('Error saving holder details:', error);
} }
}); });
// this.selectHolderCompleted.emit(this.selectedHolderId);
} }
onClearSearch(): void { toggleShowInactiveHolders(): void {
this.searchForm.reset(); this.showInactiveHolders = !this.showInactiveHolders;
this.dataSource.data = [] this.renderHolders();
} }
renderHolders() {
@ViewChild(MatPaginator) paginator!: MatPaginator; if (this.showInactiveHolders) {
@ViewChild(MatSort) sort!: MatSort; // this.dataSource.data = this.contacts;
this.dataSource.data = this.holdersSearched.filter((holder: any) => holder?.INACTIVEFLAG === 'Y');
ngAfterViewInit() { } else {
this.dataSource.paginator = this.paginator; // let holders = this.dataSource.data;
this.dataSource.sort = this.sort; this.dataSource.data = this.holdersSearched.filter((holder: any) => holder?.INACTIVEFLAG === 'N');
}
} }
onCheckboxChange(client: any) { addNewHolder(): void {
console.log('Checkbox changed:', client); this.navigationService.navigate(["add-holder"], { state: { isEditMode: false } })
} }
onEdit(id: string) {
// this.selectedHolder = this.findHolderById(Number(id));
// console.log("Holder id: ", id, this.selectedHolder);
// this.navigationService.navigate(['holder', id], { state: { holder: this.selectedHolder } });
this.navigationService.navigate(['edit-holder', id]);
}
cancelEdit() {
onRadioSelect(holder: any): void { this.clearSelection();
this.selectedHolder = holder;
this.selectedHolderId = holder.HOLDERID;
console.log('Selected row:', holder);
} }
clearSelection(): void { clearSelection(): void {
@ -180,4 +184,51 @@ export class SearchComponent implements OnInit, AfterViewInit {
this.selectedHolderId = 0; this.selectedHolderId = 0;
} }
InactivateHolder(HOLDERID: number): void {
console.log("hello called InactivateHolder ............");
this.holderService.InactivateHolder(HOLDERID).subscribe(
{
next: (basicData: any) => {
this.notificationService.showSuccess(`Holder Inactivated successfully`);
this.searchHolders();
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, `Failed to Inactivate Holder`);
this.notificationService.showError(errorMessage);
console.error('Error Inactivating Holder:', error);
}
}
);
}
ReactivateHolder(HOLDERID: number): void {
this.holderService.ReactivateHolder(HOLDERID).subscribe(
{
next: (basicData: any) => {
this.notificationService.showSuccess(`Holder Reactivated successfully`);
this.searchHolders();
},
error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, `Failed to Reactivate Holder`);
this.notificationService.showError(errorMessage);
console.error('Error Reactivating Holder:', error);
}
}
);
}
onClearSearch(): void {
this.searchForm.reset();
this.dataSource.data = []
this.holdersSearched = [];
this.searchedHolderEmit.emit('');
}
onRadioSelect(holder: any): void {
this.selectedHolder = holder;
this.selectedHolderId = holder.HOLDERID;
console.log('Selected row:', holder);
}
} }

View File

@ -15,6 +15,7 @@ import { CommonService } from '../core/services/common/common.service';
import { NotificationService } from '../core/services/common/notification.service'; import { NotificationService } from '../core/services/common/notification.service';
import { NavigationService } from '../core/services/common/navigation.service'; import { NavigationService } from '../core/services/common/navigation.service';
import { MatRadioChange } from '@angular/material/radio'; import { MatRadioChange } from '@angular/material/radio';
import { CarnetStateStore } from '../core/services/carnet/carnet-application.service';
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
@ -29,6 +30,7 @@ export class HomeComponent {
showTable = false; showTable = false;
userPreferences: UserPreferences; userPreferences: UserPreferences;
carnetStatuses: CarnetStatus[] = []; carnetStatuses: CarnetStatus[] = [];
store = inject(CarnetStateStore);
dataSource = new MatTableDataSource<any>(); dataSource = new MatTableDataSource<any>();
@ -114,6 +116,7 @@ export class HomeComponent {
newCarnet(event: MatRadioChange): void { newCarnet(event: MatRadioChange): void {
if (event.value) { if (event.value) {
this.store.resetState();
this.navigateTo('add-carnet'); this.navigateTo('add-carnet');
} }
} }

View File

@ -68,6 +68,9 @@ export class LoginComponent {
console.error('Login error:', error); console.error('Login error:', error);
} }
}); });
// this.userService.setUser(username);
// this.setUserData();
} }
} }
@ -91,5 +94,22 @@ export class LoginComponent {
console.error('Error retrieving app data:', error); console.error('Error retrieving app data:', error);
} }
}); });
// const data = {
// "userDetails": {
// "SPID": 187,
// "ENCURLKEY": "F55CBF557FEE5BC6065CF0DA74E48E98",
// "PRIMARYCOLOR": "#607c7c",
// "SECONDARYCOLOR": "#9cb4b8",
// "TERTIARYCOLOR": null,
// "NEUTRALCOLOR": null,
// "LOGONAME": "USCIBLogo.jpeg",
// "THEMENAME": "Theme187"
// }
// }
// this.navigationService.setCurrentAppId(data.userDetails.ENCURLKEY);
// this.themeService.setTheme(data.userDetails.THEMENAME);
// this.navigationService.navigate(['home']);
} }
} }