bizmatch-project/bizmatch/src/app/components/search-modal/search-modal-commercial.com...

317 lines
8.9 KiB
TypeScript

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 { CommercialPropertyListingCriteria, CountyResult, GeoResult } from '../../../../../bizmatch-server/src/models/main.model';
import { FilterStateService } from '../../services/filter-state.service';
import { GeoService } from '../../services/geo.service';
import { ListingsService } from '../../services/listings.service';
import { SearchService } from '../../services/search.service';
import { SelectOptionsService } from '../../services/select-options.service';
import { ValidatedCityComponent } from '../validated-city/validated-city.component';
import { ValidatedPriceComponent } from '../validated-price/validated-price.component';
import { ModalService } from './modal.service';
@UntilDestroy()
@Component({
selector: 'app-search-modal-commercial',
standalone: true,
imports: [CommonModule, FormsModule, NgSelectModule, ValidatedCityComponent, ValidatedPriceComponent],
templateUrl: './search-modal-commercial.component.html',
styleUrls: ['./search-modal.component.scss'],
})
export class SearchModalCommercialComponent implements OnInit, OnDestroy {
@Input() isModal: boolean = true;
private destroy$ = new Subject<void>();
private searchDebounce$ = new Subject<void>();
// State
criteria: CommercialPropertyListingCriteria;
backupCriteria: any;
// Geo search
counties$: Observable<CountyResult[]>;
countyLoading = false;
countyInput$ = new Subject<string>();
// Results count
numberOfResults$: Observable<number>;
cancelDisable = false;
constructor(
public selectOptions: SelectOptionsService,
public modalService: ModalService,
private geoService: GeoService,
private filterStateService: FilterStateService,
private listingService: ListingsService,
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 === 'commercialPropertyListings') {
this.initializeWithCriteria(criteria);
}
});
this.modalService.modalVisible$.pipe(untilDestroyed(this)).subscribe(val => {
if (val.visible && val.type === 'commercialPropertyListings') {
// 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: CommercialPropertyListingCriteria): void {
this.criteria = criteria;
this.backupCriteria = JSON.parse(JSON.stringify(criteria));
this.setTotalNumberOfResults();
}
private subscribeToStateChanges(): void {
if (!this.isModal) {
this.filterStateService
.getState$('commercialPropertyListings')
.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 'price':
updates.minPrice = null;
updates.maxPrice = null;
break;
case 'types':
updates.types = [];
break;
case 'title':
updates.title = null;
break;
case 'brokerName':
updates.brokerName = null;
break;
}
this.updateCriteria(updates);
}
// Category 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 });
}
// 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('commercialPropertyListings');
}
}
// Modal-specific methods
closeAndSearch(): void {
if (this.isModal) {
// Save changes to state
this.filterStateService.setCriteria('commercialPropertyListings', this.criteria);
this.modalService.accept();
this.searchService.search('commercialPropertyListings');
}
}
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('commercialPropertyListings', 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('commercialPropertyListings');
}
}
private setTotalNumberOfResults(): void {
this.numberOfResults$ = this.listingService.getNumberOfListings('commercialProperty', this.criteria);
}
private getDefaultCriteria(): CommercialPropertyListingCriteria {
// Access the private method through a workaround or create it here
return {
criteriaType: 'commercialPropertyListings',
types: [],
state: null,
city: null,
radius: null,
searchType: 'exact' as const,
minPrice: null,
maxPrice: null,
title: null,
brokerName: null,
prompt: null,
page: 1,
start: 0,
length: 12,
};
}
hasActiveFilters(): boolean {
if (!this.criteria) return false;
return !!(
this.criteria.state ||
this.criteria.city ||
this.criteria.minPrice ||
this.criteria.maxPrice ||
this.criteria.types?.length ||
this.criteria.title ||
this.criteria.brokerName
);
}
trackByFn(item: GeoResult): any {
return item.id;
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}