import { CommonModule } from '@angular/common'; import { ChangeDetectorRef, Component, ElementRef, ViewChild } from '@angular/core'; 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 { initFlowbite } from 'flowbite'; import { KeycloakService } from 'keycloak-angular'; import { catchError, concat, debounceTime, distinctUntilChanged, lastValueFrom, Observable, of, Subject, Subscription, 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 { CriteriaChangeService } from '../../services/criteria-change.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 { assignProperties, compareObjects, createEmptyBusinessListingCriteria, createEmptyCommercialPropertyListingCriteria, createEmptyUserListingCriteria, createEnhancedProxy, getCriteriaStateObject, map2User, } from '../../utils/utils'; @UntilDestroy() @Component({ selector: 'app-home', standalone: true, imports: [CommonModule, FormsModule, RouterModule, NgSelectModule, TooltipComponent], templateUrl: './home.component.html', styleUrl: './home.component.scss', }) export class HomeComponent { placeholders: string[] = ['Property close to Houston less than 10M', 'Franchise business in Austin price less than 500K']; activeTabAction: 'business' | 'commercialProperty' | 'broker' = 'business'; type: string; maxPrice: string; minPrice: string; criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria; states = []; isMenuOpen = false; user: KeycloakUser; prompt: string; cities$: Observable; cityLoading = false; cityInput$ = new Subject(); cityOrState = undefined; private criteriaChangeSubscription: Subscription; numberOfResults$: Observable; aiSearch = false; aiSearchText = ''; 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) index: number = 0; charIndex: number = 0; typingInterval: any; showInput: boolean = true; // Steuerung der Anzeige des Eingabefelds tooltipTargetBeta = 'tooltipTargetBeta'; public constructor( private router: Router, private modalService: ModalService, private searchService: SearchService, private activatedRoute: ActivatedRoute, public selectOptions: SelectOptionsService, public keycloakService: KeycloakService, private criteriaChangeService: CriteriaChangeService, private geoService: GeoService, public cdRef: ChangeDetectorRef, private listingService: ListingsService, private userService: UserService, private aiService: AiService, ) {} async ngOnInit() { setTimeout(() => { initFlowbite(); }, 0); const token = await this.keycloakService.getToken(); sessionStorage.removeItem('businessListings'); sessionStorage.removeItem('commercialPropertyListings'); sessionStorage.removeItem('brokerListings'); this.criteria = createEnhancedProxy(getCriteriaStateObject('businessListings'), this); this.user = map2User(token); this.loadCities(); this.setupCriteriaChangeListener(); } async 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; } } search() { this.router.navigate([`${this.activeTabAction}Listings`]); } private setupCriteriaChangeListener() { this.criteriaChangeSubscription = this.criteriaChangeService.criteriaChange$.pipe(untilDestroyed(this), debounceTime(400)).subscribe(() => this.setTotalNumberOfResults()); } login() { this.keycloakService.login({ redirectUri: `${window.location.origin}/login${this.router.routerState.snapshot.url}`, }); } register() { this.keycloakService.register({ redirectUri: `${window.location.origin}/account` }); } 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]; } } 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); } } async openModal() { const accepted = await this.modalService.showModal(this.criteria); if (accepted) { this.router.navigate([`${this.activeTabAction}Listings`]); } } private loadCities() { this.cities$ = concat( of([]), // default items 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 tap(() => (this.cityLoading = false)), ), ), ), ); } trackByFn(item: GeoResult) { return item.id; } setCityOrState(cityOrState: CityAndStateResult) { if (cityOrState) { if (cityOrState.type === 'state') { this.criteria.state = cityOrState.content.state_code; } else { this.criteria.city = cityOrState.content as GeoResult; this.criteria.state = cityOrState.content.state; this.criteria.searchType = 'radius'; this.criteria.radius = 20; } } else { this.criteria.state = null; this.criteria.city = null; this.criteria.radius = null; this.criteria.searchType = 'exact'; } } getTypes() { if (this.criteria.criteriaType === 'businessListings') { return this.selectOptions.typesOfBusiness; } else if (this.criteria.criteriaType === 'commercialPropertyListings') { return this.selectOptions.typesOfCommercialProperty; } else { return this.selectOptions.customerSubTypes; } } getPlaceholderLabel() { if (this.criteria.criteriaType === 'businessListings') { return 'Business Type'; } else if (this.criteria.criteriaType === 'commercialPropertyListings') { return 'Property Type'; } else { return 'Professional Type'; } } setTotalNumberOfResults() { if (this.criteria) { console.log(`Getting total number of results for ${this.criteria.criteriaType}`); if (this.criteria.criteriaType === 'businessListings' || this.criteria.criteriaType === 'commercialPropertyListings') { this.numberOfResults$ = this.listingService.getNumberOfListings(this.criteria, this.criteria.criteriaType === 'businessListings' ? 'business' : 'commercialProperty'); } else if (this.criteria.criteriaType === 'brokerListings') { this.numberOfResults$ = this.userService.getNumberOfBroker(this.criteria); } 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); } } async generateAiResponse() { this.loadingAi = true; this.aiSearchFailed = false; try { const result = await this.aiService.generateAiReponse(this.aiSearchText); let criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria | any; if (result.criteriaType === 'businessListings') { this.changeTab('business'); criteria = result as BusinessListingCriteria; } else if (result.criteriaType === 'commercialPropertyListings') { this.changeTab('commercialProperty'); criteria = result as CommercialPropertyListingCriteria; } else { this.changeTab('broker'); criteria = result as UserListingCriteria; } const city = criteria.city as string; if (city && city.length > 0) { let results = await lastValueFrom(this.geoService.findCitiesStartingWith(city, criteria.state)); if (results.length > 0) { criteria.city = results[0]; } else { criteria.city = null; } } if (criteria.radius && criteria.radius.length > 0) { criteria.radius = parseInt(criteria.radius); } this.loadingAi = false; this.criteria = assignProperties(this.criteria, criteria); this.search(); } catch (error) { console.log(error); this.aiSearchFailed = true; this.loadingAi = false; } } }