From 0c12e3fcbdae969d3e4bd3d10e801dadbdf4c966 Mon Sep 17 00:00:00 2001 From: Kallesh B S Date: Wed, 9 Jul 2025 13:40:56 +0530 Subject: [PATCH] search holder intergration while creating application done --- src/app/carnet/add/add-carnet.component.html | 5 +- src/app/carnet/add/add-carnet.component.ts | 7 +- src/app/core/models/holder/holder-detail.ts | 24 +++ src/app/core/models/holder/holder-filder.ts | 24 +++ .../core/services/holder/holder.service.ts | 113 +++++++++++ src/app/holder/search/search.component.html | 136 +++++++++++++ src/app/holder/search/search.component.scss | 135 +++++++++++++ src/app/holder/search/search.component.ts | 183 ++++++++++++++++++ 8 files changed, 622 insertions(+), 5 deletions(-) create mode 100644 src/app/core/models/holder/holder-detail.ts create mode 100644 src/app/core/models/holder/holder-filder.ts create mode 100644 src/app/core/services/holder/holder.service.ts create mode 100644 src/app/holder/search/search.component.html create mode 100644 src/app/holder/search/search.component.scss create mode 100644 src/app/holder/search/search.component.ts diff --git a/src/app/carnet/add/add-carnet.component.html b/src/app/carnet/add/add-carnet.component.html index 4422733..ab7f433 100644 --- a/src/app/carnet/add/add-carnet.component.html +++ b/src/app/carnet/add/add-carnet.component.html @@ -15,8 +15,9 @@ Holder Selection - - + + diff --git a/src/app/carnet/add/add-carnet.component.ts b/src/app/carnet/add/add-carnet.component.ts index 671d65b..d8d44eb 100644 --- a/src/app/carnet/add/add-carnet.component.ts +++ b/src/app/carnet/add/add-carnet.component.ts @@ -11,12 +11,13 @@ import { InsuranceComponent } from '../insurance/insurance.component'; import { PaymentComponent } from '../payment/payment.component'; import { ShippingComponent } from '../shipping/shipping.component'; import { DeliveryComponent } from '../delivery/delivery.component'; +import { SearchComponent } from '../../holder/search/search.component'; @Component({ selector: 'app-add-carnet', imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule, - ApplicationComponent, HolderComponent, GoodsComponent, TravelPlanComponent, InsuranceComponent, - ShippingComponent, DeliveryComponent, PaymentComponent], + ApplicationComponent, GoodsComponent, TravelPlanComponent, InsuranceComponent, + ShippingComponent, DeliveryComponent, PaymentComponent, SearchComponent], templateUrl: './add-carnet.component.html', styleUrl: './add-carnet.component.scss' }) @@ -44,7 +45,7 @@ export class AddCarnetComponent { } onApplicationDetailCreated(headerid: number): void { - this.headerid = this.headerid; + this.headerid = headerid; this.stepsCompleted.applicationDetail = true; this.isLinear = false; // Disable linear mode after application detail is created } diff --git a/src/app/core/models/holder/holder-detail.ts b/src/app/core/models/holder/holder-detail.ts new file mode 100644 index 0000000..c0f9499 --- /dev/null +++ b/src/app/core/models/holder/holder-detail.ts @@ -0,0 +1,24 @@ +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; +} diff --git a/src/app/core/models/holder/holder-filder.ts b/src/app/core/models/holder/holder-filder.ts new file mode 100644 index 0000000..0671940 --- /dev/null +++ b/src/app/core/models/holder/holder-filder.ts @@ -0,0 +1,24 @@ +export interface HolderFilter { + 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; + city?: string; + state?: string; + zip?: string; + country?: string; + dateCreated?: string; + createdBy?: string; + inactiveFlag?: string; + inactiveDate?: string | null; + lastUpdatedBy?: string | null; + lastUpdatedDate?: string | null; +} diff --git a/src/app/core/services/holder/holder.service.ts b/src/app/core/services/holder/holder.service.ts new file mode 100644 index 0000000..b1f132d --- /dev/null +++ b/src/app/core/services/holder/holder.service.ts @@ -0,0 +1,113 @@ +import { inject, Injectable } from '@angular/core'; +import { environment } from '../../../../environments/environment'; +import { HttpClient } from '@angular/common/http'; +import { UserService } from '../common/user.service'; +// import { BasicDetail } from '../../models/preparer/basic-detail'; +import { map, Observable } from 'rxjs'; +import { HolderDetail } from '../../models/holder/holder-detail'; +// import { PreparerFilter } from '../../models/preparer/preparer-filter'; + +@Injectable({ + providedIn: 'root' +}) +export class HolderService { + private apiUrl = environment.apiUrl; + private apiDb = environment.apiDb; + + private http = inject(HttpClient); + private userService = inject(UserService); + + searchHolders(filter: { holderName: string }): Observable { + return this.http.get(`${this.apiUrl}/${this.apiDb}/SearchHolder/${this.userService.getUserSpid()}/${filter.holderName}`).pipe( + map(response => this.mapToHolders(response)) + ) + } + + updateApplicationHolder(headerId: number, holderId: number) { + const applicationDetail = { + P_HEADERID: headerId, + P_HOLDERID: holderId + } + + return this.http.patch(`${this.apiUrl}/${this.apiDb}/update-holder`, applicationDetail); + } + + + // getPreparers(filter: PreparerFilter): Observable { + // return this.http.get(`${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( + // map(response => this.mapToClients(response))); + // } + + private mapToHolders(data: any[]): HolderDetail[] { + return data.map((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; + CITY: string; + STATE: string; + ZIP: string; + COUNTRY: string; + DATECREATED: string; + CREATEDBY: string; + INACTIVEFLAG: string; + INACTIVEDATE: string | null; + LASTUPDATEDBY: string | null; + LASTUPDATEDDATE: string | null; + }) => ({ + 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 + })) + } + + // private mapToClients(data: any[]): BasicDetail[] { + // return data.map(basicDetails => ({ + // clientid: basicDetails.CLIENTID, + // spid: basicDetails.SPID, + // name: basicDetails.PREPARERNAME, + // lookupCode: basicDetails.LOOKUPCODE, + // address1: basicDetails.ADDRESS1, + // address2: basicDetails.ADDRESS2, + // city: basicDetails.CITY, + // state: basicDetails.STATE, + // country: basicDetails.COUNTRY, + // carnetIssuingRegion: basicDetails.ISSUINGREGION, + // revenueLocation: basicDetails.REVENUELOCATION, + // zip: basicDetails.ZIP, + // // createdBy: basicDetails.CREATEDBY || null, + // // dateCreated: basicDetails.DATECREATED || null, + // // lastUpdatedBy: basicDetails.LASTUPDATEDBY || null, + // // lastUpdatedDate: basicDetails.LASTUPDATEDDATE || null, + // // isInactive: basicDetails.INACTIVEFLAG === 'Y' || false, + // // inactivatedDate: basicDetails.INACTIVEDATE || null + // })); + // } +} diff --git a/src/app/holder/search/search.component.html b/src/app/holder/search/search.component.html new file mode 100644 index 0000000..8e2be27 --- /dev/null +++ b/src/app/holder/search/search.component.html @@ -0,0 +1,136 @@ +
+ + +
+
+
+
+ + Holder Name + + search + +
+
+ +
+ + + + + + + +
+
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Holder Name{{ client.HOLDERNAME }}DBA Name{{ client.ADDLNAME || '--'}}Address{{ client.ADDRESS1 }}USCIB Member{{ client.USCIBMEMBERFLAG === 'Y' ? 'Yes' : 'No' }}Holder Type{{ client.HOLDERTYPE }}Actions + + + +
+ info + No records found matching your criteria +
+ + + + + +
+ +
+ + +
+
diff --git a/src/app/holder/search/search.component.scss b/src/app/holder/search/search.component.scss new file mode 100644 index 0000000..90cb630 --- /dev/null +++ b/src/app/holder/search/search.component.scss @@ -0,0 +1,135 @@ +.page-header { + margin: 0.5rem 0px; + color: var(--mat-sys-primary); + font-weight: 500; +} + +.form-actions { + display: flex; + justify-content: flex-end; + gap: 16px; + margin-top: 16px; +} + +.manage-preparers-container { + display: flex; + flex-direction: column; + gap: 1rem; + width: 100%; + + .search-fields { + 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; + + .name, + .address { + grid-column: span 2; + } + + .city, + .state, + .lookup-code { + grid-column: span 1; + } + } + } + + .search-actions { + display: flex; + justify-content: flex-end; + gap: 0.5rem; + + button { + display: flex; + align-items: center; + } + } + + .results-section { + 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; + } + } +} + +@media (max-width: 960px) { + .manage-preparers-container { + .search-form { + .search-fields { + .form-row { + grid-template-columns: 1fr; + + .name, + .address, + .city, + .state, + .lookup-code { + grid-column: span 1; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/app/holder/search/search.component.ts b/src/app/holder/search/search.component.ts new file mode 100644 index 0000000..ecf22df --- /dev/null +++ b/src/app/holder/search/search.component.ts @@ -0,0 +1,183 @@ +import { AfterViewInit, Component, EventEmitter, inject, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { AngularMaterialModule } from '../../shared/module/angular-material.module'; +import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { CommonModule } from '@angular/common'; +import { UserPreferences } from '../../core/models/user-preference'; +import { UserPreferencesService } from '../../core/services/user-preference.service'; +import { NavigationService } from '../../core/services/common/navigation.service'; +import { MatTableDataSource } from '@angular/material/table'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +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 { NotificationService } from '../../core/services/common/notification.service'; + +@Component({ + selector: 'app-search', + imports: [AngularMaterialModule, ReactiveFormsModule, CommonModule], + templateUrl: './search.component.html', + styleUrl: './search.component.scss' +}) +export class SearchComponent implements OnInit, AfterViewInit { + + @Output() selectHolderCompleted = new EventEmitter(); + @Input() headerid: number = 0; + isEditing: boolean = false + selectedHolder: any = null; + selectedHolderId: number = 0; + userPreferences: UserPreferences; + searchForm: FormGroup; + HolderSaved: boolean = false; + + private holderService = inject(HolderService); + private errorHandler = inject(ApiErrorHandlerService); + private notificationService = inject(NotificationService); + + constructor( + private userPrefenceService: UserPreferencesService, + // private cdr: ChangeDetectorRef, + private navigationService: NavigationService, + ) { + this.userPreferences = userPrefenceService.getPreferences(); + 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([]); + + private fb = inject(FormBuilder); + + createSearchForm(): FormGroup { + return this.fb.group({ + holderName: [''] + }); + } + + ngOnInit(): void { + + } + + isSearchCriteriaProvided(): boolean { + const values = this.searchForm.value; + return !!values.holderName + } + + onSearch(): void { + if (this.searchForm.invalid || !this.isSearchCriteriaProvided()) { + return; + } + this.searchHolders(); + } + + 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.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); + } + }); + + } + + saveApplication(): void { + const saveObservable = this.holderService.updateApplicationHolder(this.headerid, this.selectedHolderId ? Number(this.selectedHolderId) : 0); + + saveObservable.subscribe({ + next: (basicData: any) => { + this.notificationService.showSuccess(`Holder updated successfully`); + this.selectHolderCompleted.emit(true); + this.searchForm.get('holderName')?.disable(); + this.HolderSaved = true + }, + error: (error: any) => { + let errorMessage = this.errorHandler.handleApiError(error, `Failed to update holder details`); + this.notificationService.showError(errorMessage); + console.error('Error saving holder details:', error); + } + }); + } + + onClearSearch(): void { + this.searchForm.reset(); + this.dataSource.data = [] + } + + + @ViewChild(MatPaginator) paginator!: MatPaginator; + @ViewChild(MatSort) sort!: MatSort; + + ngAfterViewInit() { + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + } + + onCheckboxChange(client: any) { + console.log('Checkbox changed:', client); + } + + + + onRadioSelect(holder: any): void { + this.selectedHolder = holder; + this.selectedHolderId = holder.HOLDERID; + console.log('Selected row:', holder); + } + + clearSelection(): void { + this.selectedHolder = null; + this.selectedHolderId = 0; + } + +}