refactoring Filter Handling
This commit is contained in:
@@ -155,7 +155,7 @@
|
||||
</div>
|
||||
}
|
||||
<div class="bg-blue-600 hover:bg-blue-500 transition-colors duration-200 max-sm:rounded-md">
|
||||
@if(getNumberOfFiltersSet()>0 && numberOfResults$){
|
||||
@if( numberOfResults$){
|
||||
<button class="w-full h-full text-white font-semibold py-2 px-4 md:py-3 md:px-6 focus:outline-none rounded-md md:rounded-none min-h-[48px]" (click)="search()">
|
||||
Search ({{ numberOfResults$ | async }})
|
||||
</button>
|
||||
@@ -165,16 +165,6 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<!-- <div class="mt-4 flex items-center justify-center text-gray-700">
|
||||
<span class="mr-2">AI-Search</span>
|
||||
<span [attr.data-tooltip-target]="tooltipTargetBeta" class="bg-sky-300 text-teal-800 text-xs font-semibold px-2 py-1 rounded">BETA</span>
|
||||
<app-tooltip [id]="tooltipTargetBeta" text="AI will convert your input into filter criteria. Please check them in the filter menu after search"></app-tooltip>
|
||||
<span class="ml-2">- Try now</span>
|
||||
<div class="ml-4 relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
||||
<input (click)="toggleAiSearch()" type="checkbox" name="toggle" id="toggle" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 border-gray-300 appearance-none cursor-pointer" />
|
||||
<label for="toggle" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,30 +3,22 @@ import { ChangeDetectorRef, Component, ElementRef, ViewChild } from '@angular/co
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||
import { NgSelectModule } from '@ng-select/ng-select';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { initFlowbite } from 'flowbite';
|
||||
import { catchError, concat, debounceTime, distinctUntilChanged, Observable, of, Subject, Subscription, switchMap, tap } from 'rxjs';
|
||||
import { catchError, concat, distinctUntilChanged, Observable, of, Subject, switchMap, tap } from 'rxjs';
|
||||
import { BusinessListingCriteria, CityAndStateResult, CommercialPropertyListingCriteria, GeoResult, KeycloakUser, UserListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
|
||||
import { ModalService } from '../../components/search-modal/modal.service';
|
||||
import { TooltipComponent } from '../../components/tooltip/tooltip.component';
|
||||
import { AiService } from '../../services/ai.service';
|
||||
import { AuthService } from '../../services/auth.service';
|
||||
import { CriteriaChangeService } from '../../services/criteria-change.service';
|
||||
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 { UserService } from '../../services/user.service';
|
||||
import {
|
||||
compareObjects,
|
||||
createEmptyBusinessListingCriteria,
|
||||
createEmptyCommercialPropertyListingCriteria,
|
||||
createEmptyUserListingCriteria,
|
||||
createEnhancedProxy,
|
||||
getCriteriaStateObject,
|
||||
map2User,
|
||||
removeSortByStorage,
|
||||
} from '../../utils/utils';
|
||||
import { map2User } from '../../utils/utils';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
@@ -50,7 +42,6 @@ export class HomeComponent {
|
||||
cityLoading = false;
|
||||
cityInput$ = new Subject<string>();
|
||||
cityOrState = undefined;
|
||||
private criteriaChangeSubscription: Subscription;
|
||||
numberOfResults$: Observable<number>;
|
||||
numberOfBroker$: Observable<number>;
|
||||
numberOfCommercial$: Observable<number>;
|
||||
@@ -59,127 +50,156 @@ export class HomeComponent {
|
||||
aiSearchFailed = false;
|
||||
loadingAi = false;
|
||||
@ViewChild('aiSearchInput', { static: false }) searchInput!: ElementRef;
|
||||
typingSpeed: number = 100; // Geschwindigkeit des Tippens (ms)
|
||||
pauseTime: number = 2000; // Pausezeit, bevor der Text verschwindet (ms)
|
||||
typingSpeed: number = 100;
|
||||
pauseTime: number = 2000;
|
||||
index: number = 0;
|
||||
charIndex: number = 0;
|
||||
typingInterval: any;
|
||||
showInput: boolean = true; // Steuerung der Anzeige des Eingabefelds
|
||||
showInput: boolean = true;
|
||||
tooltipTargetBeta = 'tooltipTargetBeta';
|
||||
public constructor(
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private modalService: ModalService,
|
||||
private searchService: SearchService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
public selectOptions: SelectOptionsService,
|
||||
|
||||
private criteriaChangeService: CriteriaChangeService,
|
||||
private geoService: GeoService,
|
||||
public cdRef: ChangeDetectorRef,
|
||||
private listingService: ListingsService,
|
||||
private userService: UserService,
|
||||
private aiService: AiService,
|
||||
private authService: AuthService,
|
||||
private filterStateService: FilterStateService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
setTimeout(() => {
|
||||
initFlowbite();
|
||||
}, 0);
|
||||
this.numberOfBroker$ = this.userService.getNumberOfBroker(createEmptyUserListingCriteria());
|
||||
|
||||
// Clear all filters and sort options on initial load
|
||||
this.filterStateService.resetCriteria('businessListings');
|
||||
this.filterStateService.resetCriteria('commercialPropertyListings');
|
||||
this.filterStateService.resetCriteria('brokerListings');
|
||||
this.filterStateService.updateSortBy('businessListings', null);
|
||||
this.filterStateService.updateSortBy('commercialPropertyListings', null);
|
||||
this.filterStateService.updateSortBy('brokerListings', null);
|
||||
|
||||
// Initialize criteria for the default tab
|
||||
this.criteria = this.filterStateService.getCriteria('businessListings');
|
||||
|
||||
this.numberOfBroker$ = this.userService.getNumberOfBroker(this.filterStateService.getCriteria('brokerListings') as UserListingCriteria);
|
||||
this.numberOfCommercial$ = this.listingService.getNumberOfListings('commercialProperty');
|
||||
const token = await this.authService.getToken();
|
||||
sessionStorage.removeItem('businessListings');
|
||||
sessionStorage.removeItem('commercialPropertyListings');
|
||||
sessionStorage.removeItem('brokerListings');
|
||||
this.criteria = createEnhancedProxy(getCriteriaStateObject('businessListings'), this);
|
||||
removeSortByStorage();
|
||||
this.user = map2User(token);
|
||||
this.loadCities();
|
||||
this.setupCriteriaChangeListener();
|
||||
this.setTotalNumberOfResults();
|
||||
}
|
||||
async changeTab(tabname: 'business' | 'commercialProperty' | 'broker') {
|
||||
|
||||
changeTab(tabname: 'business' | 'commercialProperty' | 'broker') {
|
||||
this.activeTabAction = tabname;
|
||||
this.cityOrState = null;
|
||||
if ('business' === tabname) {
|
||||
this.criteria = createEnhancedProxy(getCriteriaStateObject('businessListings'), this);
|
||||
} else if ('commercialProperty' === tabname) {
|
||||
this.criteria = createEnhancedProxy(getCriteriaStateObject('commercialPropertyListings'), this);
|
||||
} else if ('broker' === tabname) {
|
||||
this.criteria = createEnhancedProxy(getCriteriaStateObject('brokerListings'), this);
|
||||
} else {
|
||||
this.criteria = undefined;
|
||||
}
|
||||
const tabToListingType = {
|
||||
business: 'businessListings',
|
||||
commercialProperty: 'commercialPropertyListings',
|
||||
broker: 'brokerListings',
|
||||
};
|
||||
this.criteria = this.filterStateService.getCriteria(tabToListingType[tabname] as 'businessListings' | 'commercialPropertyListings' | 'brokerListings');
|
||||
this.setTotalNumberOfResults();
|
||||
}
|
||||
|
||||
search() {
|
||||
this.router.navigate([`${this.activeTabAction}Listings`]);
|
||||
}
|
||||
private setupCriteriaChangeListener() {
|
||||
this.criteriaChangeSubscription = this.criteriaChangeService.criteriaChange$.pipe(untilDestroyed(this), debounceTime(400)).subscribe(() => this.setTotalNumberOfResults());
|
||||
}
|
||||
|
||||
toggleMenu() {
|
||||
this.isMenuOpen = !this.isMenuOpen;
|
||||
}
|
||||
|
||||
onTypesChange(value) {
|
||||
if (value === '') {
|
||||
// Wenn keine Option ausgewählt ist, setzen Sie types zurück auf ein leeres Array
|
||||
this.criteria.types = [];
|
||||
} else {
|
||||
this.criteria.types = [value];
|
||||
}
|
||||
const tabToListingType = {
|
||||
business: 'businessListings',
|
||||
commercialProperty: 'commercialPropertyListings',
|
||||
broker: 'brokerListings',
|
||||
};
|
||||
const listingType = tabToListingType[this.activeTabAction] as 'businessListings' | 'commercialPropertyListings' | 'brokerListings';
|
||||
this.filterStateService.updateCriteria(listingType, { types: value === '' ? [] : [value] });
|
||||
this.criteria = this.filterStateService.getCriteria(listingType);
|
||||
this.setTotalNumberOfResults();
|
||||
}
|
||||
|
||||
onRadiusChange(value) {
|
||||
if (value === 'null') {
|
||||
// Wenn keine Option ausgewählt ist, setzen Sie types zurück auf ein leeres Array
|
||||
this.criteria.radius = null;
|
||||
} else {
|
||||
this.criteria.radius = parseInt(value);
|
||||
}
|
||||
const tabToListingType = {
|
||||
business: 'businessListings',
|
||||
commercialProperty: 'commercialPropertyListings',
|
||||
broker: 'brokerListings',
|
||||
};
|
||||
const listingType = tabToListingType[this.activeTabAction] as 'businessListings' | 'commercialPropertyListings' | 'brokerListings';
|
||||
this.filterStateService.updateCriteria(listingType, { radius: value === 'null' ? null : parseInt(value) });
|
||||
this.criteria = this.filterStateService.getCriteria(listingType);
|
||||
this.setTotalNumberOfResults();
|
||||
}
|
||||
|
||||
async openModal() {
|
||||
const tabToListingType = {
|
||||
business: 'businessListings',
|
||||
commercialProperty: 'commercialPropertyListings',
|
||||
broker: 'brokerListings',
|
||||
};
|
||||
const listingType = tabToListingType[this.activeTabAction] as 'businessListings' | 'commercialPropertyListings' | 'brokerListings';
|
||||
const accepted = await this.modalService.showModal(this.criteria);
|
||||
if (accepted) {
|
||||
this.router.navigate([`${this.activeTabAction}Listings`]);
|
||||
}
|
||||
}
|
||||
|
||||
private loadCities() {
|
||||
this.cities$ = concat(
|
||||
of([]), // default items
|
||||
of([]),
|
||||
this.cityInput$.pipe(
|
||||
distinctUntilChanged(),
|
||||
tap(() => (this.cityLoading = true)),
|
||||
switchMap(term =>
|
||||
//this.geoService.findCitiesStartingWith(term).pipe(
|
||||
this.geoService.findCitiesAndStatesStartingWith(term).pipe(
|
||||
catchError(() => of([])), // empty list on error
|
||||
// map(cities => cities.map(city => city.city)), // transform the list of objects to a list of city names
|
||||
catchError(() => of([])),
|
||||
tap(() => (this.cityLoading = false)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
trackByFn(item: GeoResult) {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
setCityOrState(cityOrState: CityAndStateResult) {
|
||||
const tabToListingType = {
|
||||
business: 'businessListings',
|
||||
commercialProperty: 'commercialPropertyListings',
|
||||
broker: 'brokerListings',
|
||||
};
|
||||
const listingType = tabToListingType[this.activeTabAction] as 'businessListings' | 'commercialPropertyListings' | 'brokerListings';
|
||||
|
||||
if (cityOrState) {
|
||||
if (cityOrState.type === 'state') {
|
||||
this.criteria.state = cityOrState.content.state_code;
|
||||
this.filterStateService.updateCriteria(listingType, { state: cityOrState.content.state_code, city: null, radius: null, searchType: 'exact' });
|
||||
} else {
|
||||
this.criteria.city = cityOrState.content as GeoResult;
|
||||
this.criteria.state = cityOrState.content.state;
|
||||
this.criteria.searchType = 'radius';
|
||||
this.criteria.radius = 20;
|
||||
this.filterStateService.updateCriteria(listingType, {
|
||||
city: cityOrState.content as GeoResult,
|
||||
state: cityOrState.content.state,
|
||||
searchType: 'radius',
|
||||
radius: 20,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.criteria.state = null;
|
||||
this.criteria.city = null;
|
||||
this.criteria.radius = null;
|
||||
this.criteria.searchType = 'exact';
|
||||
this.filterStateService.updateCriteria(listingType, { state: null, city: null, radius: null, searchType: 'exact' });
|
||||
}
|
||||
this.criteria = this.filterStateService.getCriteria(listingType);
|
||||
this.setTotalNumberOfResults();
|
||||
}
|
||||
|
||||
getTypes() {
|
||||
if (this.criteria.criteriaType === 'businessListings') {
|
||||
return this.selectOptions.typesOfBusiness;
|
||||
@@ -189,6 +209,7 @@ export class HomeComponent {
|
||||
return this.selectOptions.customerSubTypes;
|
||||
}
|
||||
}
|
||||
|
||||
getPlaceholderLabel() {
|
||||
if (this.criteria.criteriaType === 'businessListings') {
|
||||
return 'Business Type';
|
||||
@@ -198,80 +219,28 @@ export class HomeComponent {
|
||||
return 'Professional Type';
|
||||
}
|
||||
}
|
||||
|
||||
setTotalNumberOfResults() {
|
||||
if (this.criteria) {
|
||||
console.log(`Getting total number of results for ${this.criteria.criteriaType}`);
|
||||
const tabToListingType = {
|
||||
business: 'businessListings',
|
||||
commercialProperty: 'commercialPropertyListings',
|
||||
broker: 'brokerListings',
|
||||
};
|
||||
const listingType = tabToListingType[this.activeTabAction] as 'businessListings' | 'commercialPropertyListings' | 'brokerListings';
|
||||
|
||||
if (this.criteria.criteriaType === 'businessListings' || this.criteria.criteriaType === 'commercialPropertyListings') {
|
||||
this.numberOfResults$ = this.listingService.getNumberOfListings(this.criteria.criteriaType === 'businessListings' ? 'business' : 'commercialProperty');
|
||||
} else if (this.criteria.criteriaType === 'brokerListings') {
|
||||
this.numberOfResults$ = this.userService.getNumberOfBroker(this.criteria);
|
||||
this.numberOfResults$ = this.userService.getNumberOfBroker(this.filterStateService.getCriteria('brokerListings') as UserListingCriteria);
|
||||
} else {
|
||||
this.numberOfResults$ = of();
|
||||
}
|
||||
}
|
||||
}
|
||||
getNumberOfFiltersSet() {
|
||||
if (this.criteria?.criteriaType === 'brokerListings') {
|
||||
return compareObjects(createEmptyUserListingCriteria(), this.criteria, ['start', 'length', 'page', 'searchType', 'radius']);
|
||||
} else if (this.criteria?.criteriaType === 'businessListings') {
|
||||
return compareObjects(createEmptyBusinessListingCriteria(), this.criteria, ['start', 'length', 'page', 'searchType', 'radius']);
|
||||
} else if (this.criteria?.criteriaType === 'commercialPropertyListings') {
|
||||
return compareObjects(createEmptyCommercialPropertyListingCriteria(), this.criteria, ['start', 'length', 'page', 'searchType', 'radius']);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
toggleAiSearch() {
|
||||
this.aiSearch = !this.aiSearch;
|
||||
this.aiSearchFailed = false;
|
||||
if (!this.aiSearch) {
|
||||
this.aiSearchText = '';
|
||||
this.stopTypingEffect();
|
||||
} else {
|
||||
setTimeout(() => this.startTypingEffect(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
clearTimeout(this.typingInterval); // Stelle sicher, dass das Intervall gestoppt wird, wenn die Komponente zerstört wird
|
||||
}
|
||||
|
||||
startTypingEffect(): void {
|
||||
if (!this.aiSearchText) {
|
||||
this.typePlaceholder();
|
||||
}
|
||||
}
|
||||
|
||||
stopTypingEffect(): void {
|
||||
clearTimeout(this.typingInterval);
|
||||
}
|
||||
typePlaceholder(): void {
|
||||
if (!this.searchInput || !this.searchInput.nativeElement) {
|
||||
return; // Falls das Eingabefeld nicht verfügbar ist (z.B. durch ngIf)
|
||||
}
|
||||
|
||||
if (this.aiSearchText) {
|
||||
return; // Stoppe, wenn der Benutzer Text eingegeben hat
|
||||
}
|
||||
|
||||
const inputField = this.searchInput.nativeElement as HTMLInputElement;
|
||||
if (document.activeElement === inputField) {
|
||||
this.stopTypingEffect();
|
||||
return;
|
||||
}
|
||||
|
||||
inputField.placeholder = this.placeholders[this.index].substring(0, this.charIndex);
|
||||
|
||||
if (this.charIndex < this.placeholders[this.index].length) {
|
||||
this.charIndex++;
|
||||
this.typingInterval = setTimeout(() => this.typePlaceholder(), this.typingSpeed);
|
||||
} else {
|
||||
// Nach dem vollständigen Tippen eine Pause einlegen
|
||||
this.typingInterval = setTimeout(() => {
|
||||
inputField.placeholder = ''; // Schlagartiges Löschen des Platzhalters
|
||||
this.charIndex = 0;
|
||||
this.index = (this.index + 1) % this.placeholders.length;
|
||||
this.typingInterval = setTimeout(() => this.typePlaceholder(), this.typingSpeed);
|
||||
}, this.pauseTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { CommonModule, NgOptimizedImage } from '@angular/common';
|
||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { BusinessListing, SortByOptions, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
import { LISTINGS_PER_PAGE, ListingType, UserListingCriteria, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
@@ -62,11 +62,11 @@ export class BrokerListingsComponent {
|
||||
this.criteria = getCriteriaProxy('brokerListings', this) as UserListingCriteria;
|
||||
this.init();
|
||||
this.loadSortBy();
|
||||
this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(({ criteria }) => {
|
||||
if (criteria.criteriaType === 'brokerListings') {
|
||||
this.search();
|
||||
}
|
||||
});
|
||||
// this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(({ criteria }) => {
|
||||
// if (criteria.criteriaType === 'brokerListings') {
|
||||
// this.search();
|
||||
// }
|
||||
// });
|
||||
}
|
||||
private loadSortBy() {
|
||||
const storedSortBy = sessionStorage.getItem('professionalsSortBy');
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { BusinessListing, SortByOptions } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
import { BusinessListingCriteria, LISTINGS_PER_PAGE, ListingType, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
@@ -10,12 +12,12 @@ import { environment } from '../../../../environments/environment';
|
||||
import { PaginatorComponent } from '../../../components/paginator/paginator.component';
|
||||
import { ModalService } from '../../../components/search-modal/modal.service';
|
||||
import { SearchModalComponent } from '../../../components/search-modal/search-modal.component';
|
||||
import { CriteriaChangeService } from '../../../services/criteria-change.service';
|
||||
import { FilterStateService } from '../../../services/filter-state.service';
|
||||
import { ImageService } from '../../../services/image.service';
|
||||
import { ListingsService } from '../../../services/listings.service';
|
||||
import { SearchService } from '../../../services/search.service';
|
||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||
import { assignProperties, getCriteriaProxy, resetBusinessListingCriteria } from '../../../utils/utils';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
selector: 'app-business-listings',
|
||||
@@ -24,102 +26,137 @@ import { assignProperties, getCriteriaProxy, resetBusinessListingCriteria } from
|
||||
templateUrl: './business-listings.component.html',
|
||||
styleUrls: ['./business-listings.component.scss', '../../pages.scss'],
|
||||
})
|
||||
export class BusinessListingsComponent {
|
||||
export class BusinessListingsComponent implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
// Component properties
|
||||
environment = environment;
|
||||
listings: Array<BusinessListing>;
|
||||
filteredListings: Array<BusinessListing>;
|
||||
criteria: BusinessListingCriteria;
|
||||
realEstateChecked: boolean;
|
||||
maxPrice: string;
|
||||
minPrice: string;
|
||||
type: string;
|
||||
state: string;
|
||||
totalRecords: number = 0;
|
||||
ts = new Date().getTime();
|
||||
first: number = 0;
|
||||
rows: number = 12;
|
||||
env = environment;
|
||||
public category: 'business' | 'commercialProperty' | 'professionals_brokers' | undefined;
|
||||
listings: Array<BusinessListing> = [];
|
||||
filteredListings: Array<ListingType> = [];
|
||||
criteria: BusinessListingCriteria;
|
||||
sortBy: SortByOptions | null = null;
|
||||
|
||||
// Pagination
|
||||
totalRecords = 0;
|
||||
page = 1;
|
||||
pageCount = 1;
|
||||
first = 0;
|
||||
rows = LISTINGS_PER_PAGE;
|
||||
|
||||
// UI state
|
||||
ts = new Date().getTime();
|
||||
emailToDirName = emailToDirName;
|
||||
sortBy: SortByOptions = null; // Neu: Separate Property
|
||||
|
||||
constructor(
|
||||
public selectOptions: SelectOptionsService,
|
||||
private listingsService: ListingsService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private router: Router,
|
||||
private cdRef: ChangeDetectorRef,
|
||||
private imageService: ImageService,
|
||||
private route: ActivatedRoute,
|
||||
private searchService: SearchService,
|
||||
private modalService: ModalService,
|
||||
private criteriaChangeService: CriteriaChangeService,
|
||||
) {
|
||||
this.criteria = getCriteriaProxy('businessListings', this) as BusinessListingCriteria;
|
||||
this.modalService.sendCriteria(this.criteria);
|
||||
this.init();
|
||||
this.loadSortBy();
|
||||
this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(({ criteria }) => {
|
||||
if (criteria.criteriaType === 'businessListings') {
|
||||
private filterStateService: FilterStateService,
|
||||
private route: ActivatedRoute,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Subscribe to state changes
|
||||
this.filterStateService
|
||||
.getState$('businessListings')
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(state => {
|
||||
this.criteria = state.criteria;
|
||||
this.sortBy = state.sortBy;
|
||||
// Automatically search when state changes
|
||||
this.search();
|
||||
});
|
||||
|
||||
// Subscribe to search triggers (if triggered from other components)
|
||||
this.searchService.searchTrigger$.pipe(takeUntil(this.destroy$)).subscribe(type => {
|
||||
if (type === 'businessListings') {
|
||||
this.search();
|
||||
}
|
||||
});
|
||||
}
|
||||
private loadSortBy() {
|
||||
const storedSortBy = sessionStorage.getItem('businessSortBy');
|
||||
this.sortBy = storedSortBy && storedSortBy !== 'null' ? (storedSortBy as SortByOptions) : null;
|
||||
}
|
||||
async ngOnInit() {
|
||||
this.search();
|
||||
}
|
||||
async init() {
|
||||
this.reset();
|
||||
|
||||
async search(): Promise<void> {
|
||||
try {
|
||||
// Get current criteria from service
|
||||
this.criteria = this.filterStateService.getCriteria('businessListings') as BusinessListingCriteria;
|
||||
|
||||
// Add sortBy if available
|
||||
const searchCriteria = {
|
||||
...this.criteria,
|
||||
sortBy: this.sortBy,
|
||||
};
|
||||
|
||||
// Perform search
|
||||
const listingsResponse = await this.listingsService.getListings('business');
|
||||
this.listings = listingsResponse.results;
|
||||
this.totalRecords = listingsResponse.totalCount;
|
||||
this.pageCount = Math.ceil(this.totalRecords / LISTINGS_PER_PAGE);
|
||||
this.page = this.criteria.page || 1;
|
||||
|
||||
// Update view
|
||||
this.cdRef.markForCheck();
|
||||
this.cdRef.detectChanges();
|
||||
} catch (error) {
|
||||
console.error('Search error:', error);
|
||||
// Handle error appropriately
|
||||
this.listings = [];
|
||||
this.totalRecords = 0;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
async search() {
|
||||
const listingReponse = await this.listingsService.getListings('business');
|
||||
this.listings = listingReponse.results;
|
||||
this.totalRecords = listingReponse.totalCount;
|
||||
this.pageCount = this.totalRecords % LISTINGS_PER_PAGE === 0 ? this.totalRecords / LISTINGS_PER_PAGE : Math.floor(this.totalRecords / LISTINGS_PER_PAGE) + 1;
|
||||
this.page = this.criteria.page ? this.criteria.page : 1;
|
||||
this.cdRef.markForCheck();
|
||||
this.cdRef.detectChanges();
|
||||
onPageChange(page: number): void {
|
||||
// Update only pagination properties
|
||||
this.filterStateService.updateCriteria('businessListings', {
|
||||
page: page,
|
||||
start: (page - 1) * LISTINGS_PER_PAGE,
|
||||
length: LISTINGS_PER_PAGE,
|
||||
});
|
||||
// Search will be triggered automatically through state subscription
|
||||
}
|
||||
onPageChange(page: any) {
|
||||
this.criteria.start = (page - 1) * LISTINGS_PER_PAGE;
|
||||
this.criteria.length = LISTINGS_PER_PAGE;
|
||||
this.criteria.page = page;
|
||||
this.search();
|
||||
|
||||
clearAllFilters(): void {
|
||||
// Reset criteria but keep sortBy
|
||||
this.filterStateService.clearFilters('businessListings');
|
||||
// Search will be triggered automatically through state subscription
|
||||
}
|
||||
imageErrorHandler(listing: ListingType) {}
|
||||
reset() {
|
||||
this.criteria.title = null;
|
||||
|
||||
async openFilterModal(): Promise<void> {
|
||||
// Open modal with current criteria
|
||||
const currentCriteria = this.filterStateService.getCriteria('businessListings');
|
||||
const modalResult = await this.modalService.showModal(currentCriteria);
|
||||
|
||||
if (modalResult.accepted) {
|
||||
// Modal accepted changes - state is updated by modal
|
||||
// Search will be triggered automatically through state subscription
|
||||
} else {
|
||||
// Modal was cancelled - no action needed
|
||||
}
|
||||
}
|
||||
|
||||
getListingPrice(listing: BusinessListing): string {
|
||||
if (!listing.price) return 'Price on Request';
|
||||
return `$${listing.price.toLocaleString()}`;
|
||||
}
|
||||
|
||||
getListingLocation(listing: BusinessListing): string {
|
||||
if (!listing.location) return 'Location not specified';
|
||||
return `${listing.location.name}, ${listing.location.state}`;
|
||||
}
|
||||
|
||||
navigateToDetails(listingId: string): void {
|
||||
this.router.navigate(['/details-business', listingId]);
|
||||
}
|
||||
getDaysListed(listing: BusinessListing) {
|
||||
return dayjs().diff(listing.created, 'day');
|
||||
}
|
||||
// New methods for filter actions
|
||||
clearAllFilters() {
|
||||
// Reset criteria to default values
|
||||
resetBusinessListingCriteria(this.criteria);
|
||||
|
||||
// Reset pagination
|
||||
this.criteria.page = 1;
|
||||
this.criteria.start = 0;
|
||||
|
||||
this.criteriaChangeService.notifyCriteriaChange();
|
||||
|
||||
// Search with cleared filters
|
||||
this.searchService.search('businessListings');
|
||||
}
|
||||
|
||||
async openFilterModal() {
|
||||
// Open the search modal with current criteria
|
||||
const modalResult = await this.modalService.showModal(this.criteria);
|
||||
if (modalResult.accepted) {
|
||||
this.criteria = assignProperties(this.criteria, modalResult.criteria); // Update criteria with modal result
|
||||
this.searchService.search('businessListings'); // Trigger search with updated criteria
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
import dayjs from 'dayjs';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { CommercialPropertyListing, SortByOptions } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
import { CommercialPropertyListingCriteria, LISTINGS_PER_PAGE, ResponseCommercialPropertyListingArray } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { PaginatorComponent } from '../../../components/paginator/paginator.component';
|
||||
import { ModalService } from '../../../components/search-modal/modal.service';
|
||||
import { SearchModalCommercialComponent } from '../../../components/search-modal/search-modal-commercial.component';
|
||||
import { CriteriaChangeService } from '../../../services/criteria-change.service';
|
||||
import { FilterStateService } from '../../../services/filter-state.service';
|
||||
import { ImageService } from '../../../services/image.service';
|
||||
import { ListingsService } from '../../../services/listings.service';
|
||||
import { SearchService } from '../../../services/search.service';
|
||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||
import { assignProperties, getCriteriaProxy, resetCommercialPropertyListingCriteria } from '../../../utils/utils';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
@@ -25,103 +25,141 @@ import { assignProperties, getCriteriaProxy, resetCommercialPropertyListingCrite
|
||||
templateUrl: './commercial-property-listings.component.html',
|
||||
styleUrls: ['./commercial-property-listings.component.scss', '../../pages.scss'],
|
||||
})
|
||||
export class CommercialPropertyListingsComponent {
|
||||
export class CommercialPropertyListingsComponent implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
// Component properties
|
||||
environment = environment;
|
||||
listings: Array<CommercialPropertyListing>;
|
||||
filteredListings: Array<CommercialPropertyListing>;
|
||||
criteria: CommercialPropertyListingCriteria;
|
||||
realEstateChecked: boolean;
|
||||
first: number = 0;
|
||||
rows: number = 12;
|
||||
maxPrice: string;
|
||||
minPrice: string;
|
||||
type: string;
|
||||
statesSet = new Set();
|
||||
state: string;
|
||||
totalRecords: number = 0;
|
||||
env = environment;
|
||||
listings: Array<CommercialPropertyListing> = [];
|
||||
filteredListings: Array<CommercialPropertyListing> = [];
|
||||
criteria: CommercialPropertyListingCriteria;
|
||||
sortBy: SortByOptions | null = null;
|
||||
|
||||
// Pagination
|
||||
totalRecords = 0;
|
||||
page = 1;
|
||||
pageCount = 1;
|
||||
first = 0;
|
||||
rows = LISTINGS_PER_PAGE;
|
||||
|
||||
// UI state
|
||||
ts = new Date().getTime();
|
||||
sortBy: SortByOptions = null; // Neu: Separate Property
|
||||
|
||||
constructor(
|
||||
public selectOptions: SelectOptionsService,
|
||||
private listingsService: ListingsService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private router: Router,
|
||||
private cdRef: ChangeDetectorRef,
|
||||
private imageService: ImageService,
|
||||
private route: ActivatedRoute,
|
||||
private searchService: SearchService,
|
||||
private modalService: ModalService,
|
||||
private criteriaChangeService: CriteriaChangeService,
|
||||
) {
|
||||
this.criteria = getCriteriaProxy('commercialPropertyListings', this) as CommercialPropertyListingCriteria;
|
||||
this.modalService.sendCriteria(this.criteria);
|
||||
this.loadSortBy();
|
||||
this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(({ criteria }) => {
|
||||
if (criteria.criteriaType === 'commercialPropertyListings') {
|
||||
private filterStateService: FilterStateService,
|
||||
private route: ActivatedRoute,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Subscribe to state changes
|
||||
this.filterStateService
|
||||
.getState$('commercialPropertyListings')
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(state => {
|
||||
this.criteria = state.criteria;
|
||||
this.sortBy = state.sortBy;
|
||||
// Automatically search when state changes
|
||||
this.search();
|
||||
});
|
||||
|
||||
// Subscribe to search triggers (if triggered from other components)
|
||||
this.searchService.searchTrigger$.pipe(takeUntil(this.destroy$)).subscribe(type => {
|
||||
if (type === 'commercialPropertyListings') {
|
||||
this.search();
|
||||
}
|
||||
});
|
||||
}
|
||||
private loadSortBy() {
|
||||
const storedSortBy = sessionStorage.getItem('commercialSortBy');
|
||||
this.sortBy = storedSortBy && storedSortBy !== 'null' ? (storedSortBy as SortByOptions) : null;
|
||||
}
|
||||
async ngOnInit() {
|
||||
this.search();
|
||||
|
||||
async search(): Promise<void> {
|
||||
try {
|
||||
// Perform search
|
||||
const listingResponse = await this.listingsService.getListings('commercialProperty');
|
||||
this.listings = (listingResponse as ResponseCommercialPropertyListingArray).results;
|
||||
this.totalRecords = (listingResponse as ResponseCommercialPropertyListingArray).totalCount;
|
||||
this.pageCount = Math.ceil(this.totalRecords / LISTINGS_PER_PAGE);
|
||||
this.page = this.criteria.page || 1;
|
||||
|
||||
// Update view
|
||||
this.cdRef.markForCheck();
|
||||
this.cdRef.detectChanges();
|
||||
} catch (error) {
|
||||
console.error('Search error:', error);
|
||||
// Handle error appropriately
|
||||
this.listings = [];
|
||||
this.totalRecords = 0;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
async search() {
|
||||
const listingReponse = await this.listingsService.getListings('commercialProperty');
|
||||
this.listings = (<ResponseCommercialPropertyListingArray>listingReponse).results;
|
||||
this.totalRecords = (<ResponseCommercialPropertyListingArray>listingReponse).totalCount;
|
||||
this.pageCount = this.totalRecords % LISTINGS_PER_PAGE === 0 ? this.totalRecords / LISTINGS_PER_PAGE : Math.floor(this.totalRecords / LISTINGS_PER_PAGE) + 1;
|
||||
this.page = this.criteria.page ? this.criteria.page : 1;
|
||||
this.cdRef.markForCheck();
|
||||
this.cdRef.detectChanges();
|
||||
onPageChange(page: number): void {
|
||||
// Update only pagination properties
|
||||
this.filterStateService.updateCriteria('commercialPropertyListings', {
|
||||
page: page,
|
||||
start: (page - 1) * LISTINGS_PER_PAGE,
|
||||
length: LISTINGS_PER_PAGE,
|
||||
});
|
||||
// Search will be triggered automatically through state subscription
|
||||
}
|
||||
|
||||
onPageChange(page: any) {
|
||||
this.criteria.start = (page - 1) * LISTINGS_PER_PAGE;
|
||||
this.criteria.length = LISTINGS_PER_PAGE;
|
||||
this.criteria.page = page;
|
||||
this.search();
|
||||
clearAllFilters(): void {
|
||||
// Reset criteria but keep sortBy
|
||||
this.filterStateService.clearFilters('commercialPropertyListings');
|
||||
// Search will be triggered automatically through state subscription
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.criteria.title = null;
|
||||
async openFilterModal(): Promise<void> {
|
||||
// Open modal with current criteria
|
||||
const currentCriteria = this.filterStateService.getCriteria('commercialPropertyListings');
|
||||
const modalResult = await this.modalService.showModal(currentCriteria);
|
||||
|
||||
if (modalResult.accepted) {
|
||||
// Modal accepted changes - state is updated by modal
|
||||
// Search will be triggered automatically through state subscription
|
||||
} else {
|
||||
// Modal was cancelled - no action needed
|
||||
}
|
||||
}
|
||||
|
||||
getTS() {
|
||||
// Helper methods for template
|
||||
getTS(): number {
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
getDaysListed(listing: CommercialPropertyListing) {
|
||||
getDaysListed(listing: CommercialPropertyListing): number {
|
||||
return dayjs().diff(listing.created, 'day');
|
||||
}
|
||||
|
||||
// New methods for filter actions
|
||||
clearAllFilters() {
|
||||
// Reset criteria to default values
|
||||
resetCommercialPropertyListingCriteria(this.criteria);
|
||||
|
||||
// Reset pagination
|
||||
this.criteria.page = 1;
|
||||
this.criteria.start = 0;
|
||||
|
||||
this.criteriaChangeService.notifyCriteriaChange();
|
||||
|
||||
// Search with cleared filters
|
||||
this.searchService.search('commercialPropertyListings');
|
||||
getListingImage(listing: CommercialPropertyListing): string {
|
||||
if (listing.imageOrder?.length > 0) {
|
||||
return `${this.env.imageBaseUrl}/pictures/property/${listing.imagePath}/${listing.serialId}/${listing.imageOrder[0]}`;
|
||||
}
|
||||
return 'assets/images/placeholder_properties.jpg';
|
||||
}
|
||||
|
||||
async openFilterModal() {
|
||||
const modalResult = await this.modalService.showModal(this.criteria);
|
||||
if (modalResult.accepted) {
|
||||
this.criteria = assignProperties(this.criteria, modalResult.criteria); // Update criteria with modal result
|
||||
this.searchService.search('commercialPropertyListings'); // Trigger search with updated criteria
|
||||
}
|
||||
getListingPrice(listing: CommercialPropertyListing): string {
|
||||
if (!listing.price) return 'Price on Request';
|
||||
return `$${listing.price.toLocaleString()}`;
|
||||
}
|
||||
|
||||
getListingLocation(listing: CommercialPropertyListing): string {
|
||||
if (!listing.location) return 'Location not specified';
|
||||
return listing.location.name || listing.location.county || 'Location not specified';
|
||||
}
|
||||
|
||||
navigateToDetails(listingId: string): void {
|
||||
this.router.navigate(['/details-commercial-property-listing', listingId]);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user