All filters & debounce

This commit is contained in:
Andreas Knuth 2025-07-22 10:45:29 -05:00
parent 01b5679e54
commit a6f1571b8b
2 changed files with 70 additions and 41 deletions

View File

@ -398,27 +398,35 @@
<!-- Display active filters as tags --> <!-- Display active filters as tags -->
<div class="flex flex-wrap gap-2" *ngIf="hasActiveFilters()"> <div class="flex flex-wrap gap-2" *ngIf="hasActiveFilters()">
<span *ngIf="criteria.state" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center"> <span *ngIf="criteria.state" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700">&times;</button> State: {{ criteria.state }} <button (click)="removeFilter('state')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span> </span>
<span *ngIf="criteria.city" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center"> <span *ngIf="criteria.city" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
City: {{ criteria.city }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700">&times;</button> City: {{ criteria.city }} <button (click)="removeFilter('city')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span> </span>
<span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center"> <span *ngIf="criteria.minPrice || criteria.maxPrice" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Price: {{ criteria.minPrice || 'Any' }} - {{ criteria.maxPrice || 'Any' }} <button (click)="removeFilter('price')" class="ml-1 text-red-500 hover:text-red-700">&times;</button> Price: {{ criteria.minPrice || 'Any' }} - {{ criteria.maxPrice || 'Any' }} <button (click)="removeFilter('price')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span> </span>
<span *ngIf="criteria.minRevenue || criteria.maxRevenue" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center"> <span *ngIf="criteria.minRevenue || criteria.maxRevenue" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Revenue: {{ criteria.minRevenue || 'Any' }} - {{ criteria.maxRevenue || 'Any' }} <button (click)="removeFilter('revenue')" class="ml-1 text-red-500 hover:text-red-700">&times;</button> Revenue: {{ criteria.minRevenue || 'Any' }} - {{ criteria.maxRevenue || 'Any' }} <button (click)="removeFilter('revenue')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span> </span>
<span *ngIf="criteria.minCashFlow || criteria.maxCashFlow" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center"> <span *ngIf="criteria.minCashFlow || criteria.maxCashFlow" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Cashflow: {{ criteria.minCashFlow || 'Any' }} - {{ criteria.maxCashFlow || 'Any' }} <button (click)="removeFilter('cashflow')" class="ml-1 text-red-500 hover:text-red-700">&times;</button> Cashflow: {{ criteria.minCashFlow || 'Any' }} - {{ criteria.maxCashFlow || 'Any' }} <button (click)="removeFilter('cashflow')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span> </span>
<span *ngIf="criteria.types.length" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center"> <span *ngIf="criteria.types.length" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Categories: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700">&times;</button> Categories: {{ criteria.types.join(', ') }} <button (click)="removeFilter('types')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span> </span>
<span *ngIf="selectedPropertyType" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center"> <span *ngIf="selectedPropertyType" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Property Type: {{ selectedPropertyTypeName }} <button (click)="removeFilter('propertyType')" class="ml-1 text-red-500 hover:text-red-700">×</button> Property Type: {{ getSelectedPropertyTypeName() }} <button (click)="removeFilter('propertyType')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.minNumberEmployees || criteria.maxNumberEmployees" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Employees: {{ criteria.minNumberEmployees || 'Any' }} - {{ criteria.maxNumberEmployees || 'Any' }} <button (click)="removeFilter('employees')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.establishedSince || criteria.establishedUntil" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Established: {{ criteria.establishedSince || 'Any' }} - {{ criteria.establishedUntil || 'Any' }} <button (click)="removeFilter('established')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span>
<span *ngIf="criteria.brokerName" class="bg-gray-200 text-gray-800 text-xs font-medium px-2.5 py-0.5 rounded flex items-center">
Broker: {{ criteria.brokerName }} <button (click)="removeFilter('brokerName')" class="ml-1 text-red-500 hover:text-red-700">×</button>
</span> </span>
<!-- Add more filters as needed (e.g., radius, establishedSince, etc.) -->
</div> </div>
@if(criteria.criteriaType==='businessListings') { @if(criteria.criteriaType==='businessListings') {
<div class="space-y-4"> <div class="space-y-4">
@ -460,25 +468,25 @@
<div> <div>
<label for="price" class="block mb-2 text-sm font-medium text-gray-900">Price</label> <label for="price" class="block mb-2 text-sm font-medium text-gray-900">Price</label>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<app-validated-price name="price-from" (ngModelChange)="onCriteriaChange()" [(ngModel)]="criteria.minPrice" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price> <app-validated-price name="price-from" (ngModelChange)="debouncedSearch()" [(ngModel)]="criteria.minPrice" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
<span>-</span> <span>-</span>
<app-validated-price name="price-to" (ngModelChange)="onCriteriaChange()" [(ngModel)]="criteria.maxPrice" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price> <app-validated-price name="price-to" (ngModelChange)="debouncedSearch()" [(ngModel)]="criteria.maxPrice" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
</div> </div>
</div> </div>
<div> <div>
<label for="salesRevenue" class="block mb-2 text-sm font-medium text-gray-900">Sales Revenue</label> <label for="salesRevenue" class="block mb-2 text-sm font-medium text-gray-900">Sales Revenue</label>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<app-validated-price name="salesRevenue-from" (ngModelChange)="onCriteriaChange()" [(ngModel)]="criteria.minRevenue" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price> <app-validated-price name="salesRevenue-from" (ngModelChange)="debouncedSearch()" [(ngModel)]="criteria.minRevenue" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
<span>-</span> <span>-</span>
<app-validated-price name="salesRevenue-to" (ngModelChange)="onCriteriaChange()" [(ngModel)]="criteria.maxRevenue" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price> <app-validated-price name="salesRevenue-to" (ngModelChange)="debouncedSearch()" [(ngModel)]="criteria.maxRevenue" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
</div> </div>
</div> </div>
<div> <div>
<label for="cashflow" class="block mb-2 text-sm font-medium text-gray-900">Cashflow</label> <label for="cashflow" class="block mb-2 text-sm font-medium text-gray-900">Cashflow</label>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<app-validated-price name="cashflow-from" (ngModelChange)="onCriteriaChange()" [(ngModel)]="criteria.minCashFlow" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price> <app-validated-price name="cashflow-from" (ngModelChange)="debouncedSearch()" [(ngModel)]="criteria.minCashFlow" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
<span>-</span> <span>-</span>
<app-validated-price name="cashflow-to" (ngModelChange)="onCriteriaChange()" [(ngModel)]="criteria.maxCashFlow" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price> <app-validated-price name="cashflow-to" (ngModelChange)="debouncedSearch()" [(ngModel)]="criteria.maxCashFlow" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
</div> </div>
</div> </div>
<div> <div>
@ -487,7 +495,7 @@
type="text" type="text"
id="title" id="title"
[(ngModel)]="criteria.title" [(ngModel)]="criteria.title"
(ngModelChange)="onCriteriaChange()" (ngModelChange)="debouncedSearch()"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="e.g. Restaurant" placeholder="e.g. Restaurant"
/> />
@ -525,7 +533,7 @@
type="number" type="number"
id="numberEmployees-from" id="numberEmployees-from"
[(ngModel)]="criteria.minNumberEmployees" [(ngModel)]="criteria.minNumberEmployees"
(ngModelChange)="onCriteriaChange()" (ngModelChange)="debouncedSearch()"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
placeholder="From" placeholder="From"
/> />
@ -534,7 +542,7 @@
type="number" type="number"
id="numberEmployees-to" id="numberEmployees-to"
[(ngModel)]="criteria.maxNumberEmployees" [(ngModel)]="criteria.maxNumberEmployees"
(ngModelChange)="onCriteriaChange()" (ngModelChange)="debouncedSearch()"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
placeholder="To" placeholder="To"
/> />
@ -547,7 +555,7 @@
type="number" type="number"
id="establishedSince-From" id="establishedSince-From"
[(ngModel)]="criteria.establishedSince" [(ngModel)]="criteria.establishedSince"
(ngModelChange)="onCriteriaChange()" (ngModelChange)="debouncedSearch()"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
placeholder="YYYY" placeholder="YYYY"
/> />
@ -556,7 +564,7 @@
type="number" type="number"
id="establishedSince-To" id="establishedSince-To"
[(ngModel)]="criteria.establishedUntil" [(ngModel)]="criteria.establishedUntil"
(ngModelChange)="onCriteriaChange()" (ngModelChange)="debouncedSearch()"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
placeholder="YYYY" placeholder="YYYY"
/> />
@ -568,6 +576,7 @@
type="text" type="text"
id="brokername" id="brokername"
[(ngModel)]="criteria.brokerName" [(ngModel)]="criteria.brokerName"
(ngModelChange)="debouncedSearch()"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="e.g. Brokers Invest" placeholder="e.g. Brokers Invest"
/> />

View File

@ -35,7 +35,7 @@ export class SearchModalComponent {
countyInput$ = new Subject<string>(); countyInput$ = new Subject<string>();
private criteriaChangeSubscription: Subscription; private criteriaChangeSubscription: Subscription;
public criteria: BusinessListingCriteria; public criteria: BusinessListingCriteria;
private debounceTimeout: any;
public backupCriteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria = getCriteriaStateObject('businessListings'); public backupCriteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria = getCriteriaStateObject('businessListings');
numberOfResults$: Observable<number>; numberOfResults$: Observable<number>;
cancelDisable = false; cancelDisable = false;
@ -49,7 +49,7 @@ export class SearchModalComponent {
private searchService: SearchService, private searchService: SearchService,
) {} ) {}
// Define property type options // Define property type options
propertyTypeOptions = [ public propertyTypeOptions = [
{ name: 'Real Estate', value: 'realEstateChecked' }, { name: 'Real Estate', value: 'realEstateChecked' },
{ name: 'Leased Location', value: 'leasedLocation' }, { name: 'Leased Location', value: 'leasedLocation' },
{ name: 'Franchise', value: 'franchiseResale' }, { name: 'Franchise', value: 'franchiseResale' },
@ -78,14 +78,19 @@ export class SearchModalComponent {
return !!( return !!(
this.criteria.state || this.criteria.state ||
this.criteria.city || this.criteria.city ||
(<BusinessListingCriteria>this.criteria).minPrice || this.criteria.minPrice ||
(<BusinessListingCriteria>this.criteria).maxPrice || this.criteria.maxPrice ||
(<BusinessListingCriteria>this.criteria).minRevenue || this.criteria.minRevenue ||
(<BusinessListingCriteria>this.criteria).maxRevenue || this.criteria.maxRevenue ||
(<BusinessListingCriteria>this.criteria).minCashFlow || this.criteria.minCashFlow ||
(<BusinessListingCriteria>this.criteria).maxCashFlow || this.criteria.maxCashFlow ||
this.criteria.types.length || this.criteria.types.length ||
this.selectedPropertyType this.selectedPropertyType ||
this.criteria.minNumberEmployees ||
this.criteria.maxNumberEmployees ||
this.criteria.establishedSince ||
this.criteria.establishedUntil ||
this.criteria.brokerName
); );
} }
removeFilter(filterType: string) { removeFilter(filterType: string) {
@ -100,26 +105,37 @@ export class SearchModalComponent {
this.criteria.searchType = 'exact'; this.criteria.searchType = 'exact';
break; break;
case 'price': case 'price':
(<BusinessListingCriteria>this.criteria).minPrice = null; this.criteria.minPrice = null;
(<BusinessListingCriteria>this.criteria).maxPrice = null; this.criteria.maxPrice = null;
break; break;
case 'revenue': case 'revenue':
(<BusinessListingCriteria>this.criteria).minRevenue = null; this.criteria.minRevenue = null;
(<BusinessListingCriteria>this.criteria).maxRevenue = null; this.criteria.maxRevenue = null;
break; break;
case 'cashflow': case 'cashflow':
(<BusinessListingCriteria>this.criteria).minCashFlow = null; this.criteria.minCashFlow = null;
(<BusinessListingCriteria>this.criteria).maxCashFlow = null; this.criteria.maxCashFlow = null;
break; break;
case 'types': case 'types':
this.criteria.types = []; this.criteria.types = [];
break; break;
case 'propertyType': case 'propertyType':
(<BusinessListingCriteria>this.criteria).realEstateChecked = false; this.criteria.realEstateChecked = false;
(<BusinessListingCriteria>this.criteria).leasedLocation = false; this.criteria.leasedLocation = false;
(<BusinessListingCriteria>this.criteria).franchiseResale = false; this.criteria.franchiseResale = false;
this.selectedPropertyType = null; this.selectedPropertyType = null;
break; break;
case 'employees':
this.criteria.minNumberEmployees = null;
this.criteria.maxNumberEmployees = null;
break;
case 'established':
this.criteria.establishedSince = null;
this.criteria.establishedUntil = null;
break;
case 'brokerName':
this.criteria.brokerName = null;
break;
} }
this.searchService.search(this.criteria); this.searchService.search(this.criteria);
} }
@ -140,7 +156,6 @@ export class SearchModalComponent {
this.criteria[value] = true; this.criteria[value] = true;
} }
this.selectedPropertyType = value; this.selectedPropertyType = value;
this.updateSelectedPropertyTypeName();
this.searchService.search(this.criteria); this.searchService.search(this.criteria);
} }
@ -150,10 +165,9 @@ export class SearchModalComponent {
else if ((<BusinessListingCriteria>this.criteria).leasedLocation) this.selectedPropertyType = 'leasedLocation'; else if ((<BusinessListingCriteria>this.criteria).leasedLocation) this.selectedPropertyType = 'leasedLocation';
else if ((<BusinessListingCriteria>this.criteria).franchiseResale) this.selectedPropertyType = 'franchiseResale'; else if ((<BusinessListingCriteria>this.criteria).franchiseResale) this.selectedPropertyType = 'franchiseResale';
else this.selectedPropertyType = null; else this.selectedPropertyType = null;
this.updateSelectedPropertyTypeName();
} }
updateSelectedPropertyTypeName() { getSelectedPropertyTypeName() {
this.selectedPropertyTypeName = this.selectedPropertyType ? this.propertyTypeOptions.find(opt => opt.value === this.selectedPropertyType)?.name : null; return this.selectedPropertyType ? this.propertyTypeOptions.find(opt => opt.value === this.selectedPropertyType)?.name : null;
} }
categoryClicked(checked: boolean, value: string) { categoryClicked(checked: boolean, value: string) {
if (checked) { if (checked) {
@ -268,4 +282,10 @@ export class SearchModalComponent {
this.criteria[checkbox] = value; this.criteria[checkbox] = value;
this.searchService.search(this.criteria); this.searchService.search(this.criteria);
} }
debouncedSearch() {
clearTimeout(this.debounceTimeout);
this.debounceTimeout = setTimeout(() => {
this.searchService.search(this.criteria);
}, 1000);
}
} }