import { CommonModule } from '@angular/common'; import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgSelectModule } from '@ng-select/ng-select'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { catchError, concat, debounceTime, distinctUntilChanged, map, Observable, of, Subject, switchMap, takeUntil, tap } from 'rxjs'; import { CountyResult, GeoResult, UserListingCriteria } from '../../../../../bizmatch-server/src/models/main.model'; import { FilterStateService } from '../../services/filter-state.service'; import { GeoService } from '../../services/geo.service'; import { SearchService } from '../../services/search.service'; import { SelectOptionsService } from '../../services/select-options.service'; import { UserService } from '../../services/user.service'; import { ValidatedCityComponent } from '../validated-city/validated-city.component'; import { ModalService } from './modal.service'; @UntilDestroy() @Component({ selector: 'app-search-modal-broker', standalone: true, imports: [CommonModule, FormsModule, NgSelectModule, ValidatedCityComponent], templateUrl: './search-modal-broker.component.html', styleUrls: ['./search-modal.component.scss'], }) export class SearchModalBrokerComponent implements OnInit, OnDestroy { @Input() isModal: boolean = true; private destroy$ = new Subject(); private searchDebounce$ = new Subject(); // State criteria: UserListingCriteria; backupCriteria: any; // Geo search counties$: Observable; countyLoading = false; countyInput$ = new Subject(); // Results count numberOfResults$: Observable; cancelDisable = false; constructor( public selectOptions: SelectOptionsService, public modalService: ModalService, private geoService: GeoService, private filterStateService: FilterStateService, private userService: UserService, private searchService: SearchService, ) {} ngOnInit(): void { // Load counties this.loadCounties(); if (this.isModal) { // Modal mode: Wait for messages from ModalService this.modalService.message$.pipe(untilDestroyed(this)).subscribe(criteria => { if (criteria?.criteriaType === 'brokerListings') { this.initializeWithCriteria(criteria); } }); this.modalService.modalVisible$.pipe(untilDestroyed(this)).subscribe(val => { if (val.visible && val.type === 'brokerListings') { // Reset pagination when modal opens if (this.criteria) { this.criteria.page = 1; this.criteria.start = 0; } } }); } else { // Embedded mode: Subscribe to state changes this.subscribeToStateChanges(); } // Setup debounced search this.searchDebounce$.pipe(debounceTime(400), takeUntil(this.destroy$)).subscribe(() => { this.triggerSearch(); }); } private initializeWithCriteria(criteria: UserListingCriteria): void { this.criteria = criteria; this.backupCriteria = JSON.parse(JSON.stringify(criteria)); this.setTotalNumberOfResults(); } private subscribeToStateChanges(): void { if (!this.isModal) { this.filterStateService .getState$('brokerListings') .pipe(takeUntil(this.destroy$)) .subscribe(state => { this.criteria = { ...state.criteria }; this.setTotalNumberOfResults(); }); } } private loadCounties(): void { this.counties$ = concat( of([]), // default items this.countyInput$.pipe( distinctUntilChanged(), tap(() => (this.countyLoading = true)), switchMap(term => this.geoService.findCountiesStartingWith(term).pipe( catchError(() => of([])), map(counties => counties.map(county => county.name)), tap(() => (this.countyLoading = false)), ), ), ), ); } // Filter removal methods removeFilter(filterType: string): void { const updates: any = {}; switch (filterType) { case 'state': updates.state = null; updates.city = null; updates.radius = null; updates.searchType = 'exact'; break; case 'city': updates.city = null; updates.radius = null; updates.searchType = 'exact'; break; case 'types': updates.types = []; break; case 'brokerName': updates.brokerName = null; break; case 'companyName': updates.companyName = null; break; case 'counties': updates.counties = []; break; } this.updateCriteria(updates); } // Professional type handling onCategoryChange(selectedCategories: string[]): void { this.updateCriteria({ types: selectedCategories }); } categoryClicked(checked: boolean, value: string): void { const types = [...(this.criteria.types || [])]; if (checked) { if (!types.includes(value)) { types.push(value); } } else { const index = types.indexOf(value); if (index > -1) { types.splice(index, 1); } } this.updateCriteria({ types }); } // Counties handling onCountiesChange(selectedCounties: string[]): void { this.updateCriteria({ counties: selectedCounties }); } // Location handling setState(state: string): void { const updates: any = { state }; if (!state) { updates.city = null; updates.radius = null; updates.searchType = 'exact'; } this.updateCriteria(updates); } setCity(city: any): void { const updates: any = {}; if (city) { updates.city = city; updates.state = city.state; // Automatically set radius to 50 miles and enable radius search updates.searchType = 'radius'; updates.radius = 50; } else { updates.city = null; updates.radius = null; updates.searchType = 'exact'; } this.updateCriteria(updates); } setRadius(radius: number): void { this.updateCriteria({ radius }); } onCriteriaChange(): void { this.triggerSearch(); } // Debounced search for text inputs debouncedSearch(): void { this.searchDebounce$.next(); } // Clear all filters clearFilter(): void { if (this.isModal) { // In modal: Reset locally const defaultCriteria = this.getDefaultCriteria(); this.criteria = defaultCriteria; this.setTotalNumberOfResults(); } else { // Embedded: Use state service this.filterStateService.clearFilters('brokerListings'); } } // Modal-specific methods closeAndSearch(): void { if (this.isModal) { // Save changes to state this.filterStateService.setCriteria('brokerListings', this.criteria); this.modalService.accept(); this.searchService.search('brokerListings'); } } close(): void { if (this.isModal) { // Discard changes this.modalService.reject(this.backupCriteria); } } // Helper methods public updateCriteria(updates: any): void { if (this.isModal) { // In modal: Update locally only this.criteria = { ...this.criteria, ...updates }; this.setTotalNumberOfResults(); } else { // Embedded: Update through state service this.filterStateService.updateCriteria('brokerListings', updates); } // Trigger search after update this.debouncedSearch(); } private triggerSearch(): void { if (this.isModal) { // In modal: Only update count this.setTotalNumberOfResults(); this.cancelDisable = true; } else { // Embedded: Full search this.searchService.search('brokerListings'); } } private setTotalNumberOfResults(): void { this.numberOfResults$ = this.userService.getNumberOfBroker(this.criteria); } private getDefaultCriteria(): UserListingCriteria { return { criteriaType: 'brokerListings', types: [], state: null, city: null, radius: null, searchType: 'exact' as const, brokerName: null, companyName: null, counties: [], prompt: null, page: 1, start: 0, length: 12, }; } hasActiveFilters(): boolean { if (!this.criteria) return false; return !!( this.criteria.state || this.criteria.city || this.criteria.types?.length || this.criteria.brokerName || this.criteria.companyName || this.criteria.counties?.length ); } trackByFn(item: GeoResult): any { return item.id; } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } }