bizmatch-project/bizmatch/src/app/pages/home/home.component.ts

317 lines
12 KiB
TypeScript

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<CityAndStateResult[]>;
cityLoading = false;
cityInput$ = new Subject<string>();
cityOrState = undefined;
private criteriaChangeSubscription: Subscription;
numberOfResults$: Observable<number>;
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;
}
}
}