homepage overhault, aiService 1. try
This commit is contained in:
parent
38e943c18e
commit
a6ae643458
|
|
@ -38,6 +38,7 @@
|
|||
"dotenv": "^16.4.5",
|
||||
"drizzle-orm": "^0.32.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"groq-sdk": "^0.5.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"jwks-rsa": "^3.1.0",
|
||||
"ky": "^1.4.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import { AiService } from './ai.service.js';
|
||||
|
||||
@Controller('ai')
|
||||
export class AiController {
|
||||
constructor(private readonly aiService: AiService) {}
|
||||
|
||||
@Post()
|
||||
async getBusinessCriteria(@Body('query') query: string) {
|
||||
return this.aiService.getBusinessCriteria(query);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { AiController } from './ai.controller.js';
|
||||
import { AiService } from './ai.service.js';
|
||||
|
||||
@Module({
|
||||
controllers: [AiController],
|
||||
providers: [AiService],
|
||||
})
|
||||
export class AiModule {}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import Groq from 'groq-sdk';
|
||||
import OpenAI from 'openai';
|
||||
import { BusinessListingCriteria } from '../models/main.model';
|
||||
|
||||
const businessListingCriteriaStructure = {
|
||||
criteriaType: 'business | commercialProperty | broker',
|
||||
types: "'Automotive'|'Industrial Services'|'Food and Restaurant'|'Real Estate'|'Retail'|'Oilfield SVE and MFG.'|'Service'|'Advertising'|'Agriculture'|'Franchise'|'Professional'|'Manufacturing'",
|
||||
city: 'string',
|
||||
state: 'string',
|
||||
county: 'string',
|
||||
minPrice: 'number',
|
||||
maxPrice: 'number',
|
||||
minRevenue: 'number',
|
||||
maxRevenue: 'number',
|
||||
minCashFlow: 'number',
|
||||
maxCashFlow: 'number',
|
||||
minNumberEmployees: 'number',
|
||||
maxNumberEmployees: 'number',
|
||||
establishedSince: 'number',
|
||||
establishedUntil: 'number',
|
||||
realEstateChecked: 'boolean',
|
||||
leasedLocation: 'boolean',
|
||||
franchiseResale: 'boolean',
|
||||
title: 'string',
|
||||
brokerName: 'string',
|
||||
searchType: "'exact' | 'radius'",
|
||||
radius: "'0' | '5' | '20' | '50' | '100' | '200' | '300' | '400' | '500'",
|
||||
};
|
||||
@Injectable()
|
||||
export class AiService {
|
||||
private readonly openai: OpenAI;
|
||||
private readonly groq: Groq;
|
||||
constructor() {
|
||||
this.openai = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY, // Verwenden Sie Umgebungsvariablen für den API-Schlüssel
|
||||
});
|
||||
this.groq = new Groq({ apiKey: process.env.GROQ_API_KEY });
|
||||
}
|
||||
|
||||
async getBusinessCriteria(query: string): Promise<BusinessListingCriteria> {
|
||||
// const prompt = `
|
||||
// Dieses Objekt ist wie folgt definiert: ${JSON.stringify(businessListingCriteriaStructure)}.
|
||||
// Die Antwort darf nur das von dir befüllte JSON als unformatierten Text enthalten so das es von mir mit JSON.parse() einlesbar ist!!!!
|
||||
// Falls es Ortsangaben gibt, dann befülle City, County und State wenn möglich Die Suchanfrage des Users lautet: "${query}"`;
|
||||
const prompt = `The Search Query of the User is: "${query}"`;
|
||||
let response = null;
|
||||
try {
|
||||
// response = await this.openai.chat.completions.create({
|
||||
// model: 'gpt-4o-mini',
|
||||
// //model: 'gpt-3.5-turbo',
|
||||
// max_tokens: 300,
|
||||
// messages: [
|
||||
// {
|
||||
// role: 'system',
|
||||
// content: `Please create unformatted JSON Object from a user input.
|
||||
// The type is: ${JSON.stringify(businessListingCriteriaStructure)}.,
|
||||
// If location details available please fill city, county and state as State Code`,
|
||||
// },
|
||||
// ],
|
||||
// temperature: 0.5,
|
||||
// response_format: { type: 'json_object' },
|
||||
// });
|
||||
|
||||
response = await this.groq.chat.completions.create({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: `Please create unformatted JSON Object from a user input.
|
||||
The type must be: ${JSON.stringify(businessListingCriteriaStructure)}.
|
||||
If location details available please fill city, county and state as State Code`,
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: prompt,
|
||||
},
|
||||
],
|
||||
model: 'llama-3.1-70b-versatile',
|
||||
//model: 'llama-3.1-8b-instant',
|
||||
temperature: 0.2,
|
||||
max_tokens: 300,
|
||||
response_format: { type: 'json_object' },
|
||||
});
|
||||
|
||||
const generatedCriteria = JSON.parse(response.choices[0]?.message?.content);
|
||||
return generatedCriteria;
|
||||
|
||||
// return response.choices[0]?.message?.content;
|
||||
} catch (error) {
|
||||
console.error(`Error calling GPT-4 API: ${response.choices[0]}`, error);
|
||||
throw new Error('Failed to generate business criteria');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import * as dotenv from 'dotenv';
|
|||
import fs from 'fs-extra';
|
||||
import { WinstonModule, utilities as nestWinstonModuleUtilities } from 'nest-winston';
|
||||
import * as winston from 'winston';
|
||||
import { AiModule } from './ai/ai.module.js';
|
||||
import { AppController } from './app.controller.js';
|
||||
import { AppService } from './app.service.js';
|
||||
import { AuthModule } from './auth/auth.module.js';
|
||||
|
|
@ -73,6 +74,7 @@ loadEnvFiles();
|
|||
SelectOptionsModule,
|
||||
ImageModule,
|
||||
PassportModule,
|
||||
AiModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService, FileService],
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ export interface ListCriteria {
|
|||
city: string;
|
||||
prompt: string;
|
||||
searchType: 'exact' | 'radius';
|
||||
// radius: '5' | '20' | '50' | '100' | '200' | '300' | '400' | '500';
|
||||
radius: number;
|
||||
criteriaType: 'business' | 'commercialProperty' | 'broker';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export class SelectOptionsController {
|
|||
locations: this.selectOptionsService.locations,
|
||||
typesOfCommercialProperty: this.selectOptionsService.typesOfCommercialProperty,
|
||||
customerSubTypes: this.selectOptionsService.customerSubTypes,
|
||||
distances: this.selectOptionsService.distances,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,17 @@ export class SelectOptionsService {
|
|||
{ name: '$1M', value: '1000000' },
|
||||
{ name: '$5M', value: '5000000' },
|
||||
];
|
||||
|
||||
public distances: Array<KeyValue> = [
|
||||
{ name: '5 miles', value: '5' },
|
||||
{ name: '20 miles', value: '20' },
|
||||
{ name: '50 miles', value: '50' },
|
||||
{ name: '100 miles', value: '100' },
|
||||
{ name: '200 miles', value: '200' },
|
||||
{ name: '300 miles', value: '300' },
|
||||
{ name: '400 miles', value: '400' },
|
||||
{ name: '500 miles', value: '500' },
|
||||
];
|
||||
public listingCategories: Array<KeyValue> = [
|
||||
{ name: 'Business', value: 'business' },
|
||||
{ name: 'Commercial Property', value: 'commercialProperty' },
|
||||
|
|
|
|||
|
|
@ -7,28 +7,13 @@
|
|||
|
||||
<app-footer></app-footer>
|
||||
</div>
|
||||
<!-- <p-confirmDialog #cd>
|
||||
<ng-template pTemplate="headless" let-message>
|
||||
<div class="flex flex-column align-items-center p-5 surface-overlay border-round">
|
||||
<span class="font-bold text-2xl block mb-2 mt-4">
|
||||
{{ message.header }}
|
||||
</span>
|
||||
<p class="mb-0">{{ message.message }}</p>
|
||||
<div class="flex align-items-center gap-2 mt-4">
|
||||
<button pButton label="OK" (click)="cd.accept()" size="small"></button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</p-confirmDialog> -->
|
||||
<!-- </div> -->
|
||||
|
||||
@if (loadingService.isLoading$ | async) {
|
||||
<!-- @if (loadingService.isLoading$ | async) {
|
||||
<div class="spinner-overlay">
|
||||
<div class="spinner-container">
|
||||
<!-- <p-progressSpinner></p-progressSpinner> -->
|
||||
<div class="spinner-text" *ngIf="loadingService.loadingText$ | async as loadingText">{{ loadingText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
} -->
|
||||
<app-message-container></app-message-container>
|
||||
<app-search-modal></app-search-modal>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { DetailsBusinessListingComponent } from './pages/details/details-busines
|
|||
import { DetailsCommercialPropertyListingComponent } from './pages/details/details-commercial-property-listing/details-commercial-property-listing.component';
|
||||
import { DetailsUserComponent } from './pages/details/details-user/details-user.component';
|
||||
import { HomeComponent } from './pages/home/home.component';
|
||||
import { Home1Component } from './pages/home1/home1.component';
|
||||
import { BrokerListingsComponent } from './pages/listings/broker-listings/broker-listings.component';
|
||||
import { BusinessListingsComponent } from './pages/listings/business-listings/business-listings.component';
|
||||
import { CommercialPropertyListingsComponent } from './pages/listings/commercial-property-listings/commercial-property-listings.component';
|
||||
|
|
@ -39,6 +40,10 @@ export const routes: Routes = [
|
|||
path: 'home',
|
||||
component: HomeComponent,
|
||||
},
|
||||
{
|
||||
path: 'home1',
|
||||
component: Home1Component,
|
||||
},
|
||||
// #########
|
||||
// Listings Details
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
::ng-deep .ng-select.custom .ng-select-container {
|
||||
:host ::ng-deep .ng-select.custom .ng-select-container {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
|
||||
height: 46px;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
<h1 class="text-3xl md:text-4xl lg:text-5xl font-bold text-blue-900 mb-4 text-center">Find businesses for sale.</h1>
|
||||
<p class="text-base md:text-lg lg:text-xl text-blue-600 mb-8 text-center">Unlocking Exclusive Opportunities - Empowering Entrepreneurial Dreams</p>
|
||||
<div class="bg-white bg-opacity-80 p-2 rounded-lg shadow-lg w-full">
|
||||
<div class="text-sm lg:text-base text-center text-gray-500 border-b border-gray-200 dark:text-gray-400 dark:border-gray-700 flex justify-center">
|
||||
<div class="text-sm lg:text-base mb-1 text-center text-gray-500 border-gray-200 dark:text-gray-400 dark:border-gray-700 flex justify-center">
|
||||
<ul class="flex flex-wrap -mb-px">
|
||||
<li class="me-2">
|
||||
<a
|
||||
|
|
@ -76,15 +76,78 @@
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex items-center border border-gray-300 rounded-full p-2">
|
||||
<input type="text" [(ngModel)]="prompt" placeholder="AI Search" class="flex-grow px-4 py-2 outline-none rounded-full text-sm md:text-base" />
|
||||
<button class="bg-blue-600 text-white p-2 rounded-full" (click)="search()">
|
||||
<svg class="h-5 w-5 md:h-6 md:w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-4.35-4.35M10.5 18.5A7.5 7.5 0 1018 10.5 7.5 7.5 0 0010.5 18.5z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
@if(criteria){
|
||||
<div class="w-full max-w-3xl mx-auto bg-white rounded-lg flex flex-col md:flex-row border border-gray-300">
|
||||
<div class="md:flex-none md:w-40 flex-1 border-r border-gray-300 overflow-hidden">
|
||||
<div class="relative">
|
||||
<select
|
||||
class="appearance-none bg-transparent w-full py-3 px-4 pr-8 focus:outline-none"
|
||||
[ngModel]="criteria.types"
|
||||
(ngModelChange)="onTypesChange($event)"
|
||||
[ngClass]="{ 'placeholder-selected': criteria.types.length === 0 }"
|
||||
>
|
||||
<option [value]="[]">Business Type</option>
|
||||
@for(tob of selectOptions.typesOfBusiness; track tob){
|
||||
<option [value]="tob.value">{{ tob.name }}</option>
|
||||
}
|
||||
</select>
|
||||
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
|
||||
<i class="fas fa-chevron-down text-xs"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md:flex-auto md:w-36 lex-grow border-b md:border-b-0 md:border-r border-gray-300">
|
||||
<ng-select
|
||||
class="custom"
|
||||
[multiple]="false"
|
||||
[hideSelected]="true"
|
||||
[trackByFn]="trackByFn"
|
||||
[minTermLength]="2"
|
||||
[loading]="cityLoading"
|
||||
typeToSearchText="Please enter 2 or more characters"
|
||||
[typeahead]="cityInput$"
|
||||
[ngModel]="cityOrState"
|
||||
(ngModelChange)="setCity($event)"
|
||||
placeholder="Enter City or State ..."
|
||||
>
|
||||
@for (city of cities$ | async; track city.id) {
|
||||
<ng-option [value]="city">{{ city.city }} - {{ selectOptions.getStateInitials(city.state) }}</ng-option>
|
||||
}
|
||||
</ng-select>
|
||||
</div>
|
||||
<div class="md:flex-none md:w-36 flex-1 border-b md:border-b-0 md:border-r border-gray-300">
|
||||
<div class="relative">
|
||||
<select
|
||||
class="appearance-none bg-transparent w-full py-3 px-4 pr-8 focus:outline-none"
|
||||
(ngModelChange)="onRadiusChange($event)"
|
||||
[ngModel]="criteria.radius"
|
||||
[ngClass]="{ 'placeholder-selected': !criteria.radius }"
|
||||
>
|
||||
<option [value]="null">City Radius</option>
|
||||
@for(dist of selectOptions.distances; track dist){
|
||||
<option [value]="dist.value">{{ dist.name }}</option>
|
||||
}
|
||||
</select>
|
||||
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
|
||||
<i class="fas fa-chevron-down text-xs"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-blue-600 hover:bg-blue-500 transition-colors duration-200">
|
||||
<button class="w-full h-full text-white font-semibold py-3 px-6 focus:outline-none" (click)="search()">Suchen</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="mt-4 flex items-center justify-center text-gray-700">
|
||||
<span class="mr-2">AI-Search</span>
|
||||
<span class="bg-teal-100 text-teal-800 text-xs font-semibold px-2 py-1 rounded">BETA</span>
|
||||
<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 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 class="mt-4 text-gray-600 text-sm md:text-base text-center hover:cursor-pointer" (click)="openModal()">Or search using filters ▼</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,3 @@
|
|||
// :host {
|
||||
// height: 100%;
|
||||
// }
|
||||
|
||||
// .container {
|
||||
// background-image: url(../../../assets/images/index-bg.webp);
|
||||
// background-size: cover;
|
||||
// background-position: center;
|
||||
// height: 100vh;
|
||||
// }
|
||||
// .combo_lp {
|
||||
// width: 200px;
|
||||
// }
|
||||
// .p-button-white {
|
||||
// color: aliceblue;
|
||||
// }
|
||||
// .mt-11 {
|
||||
// margin-top: 5.9rem !important;
|
||||
// }
|
||||
// .mt-22 {
|
||||
// margin-top: 9.7rem !important;
|
||||
// }
|
||||
.bg-cover-custom {
|
||||
background-image: url('/assets/images/index-bg.webp');
|
||||
background-size: cover;
|
||||
|
|
@ -28,3 +6,65 @@
|
|||
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.3);
|
||||
min-height: calc(100vh - 4rem);
|
||||
}
|
||||
select:not([size]) {
|
||||
background-image: unset;
|
||||
}
|
||||
[type='text'],
|
||||
[type='email'],
|
||||
[type='url'],
|
||||
[type='password'],
|
||||
[type='number'],
|
||||
[type='date'],
|
||||
[type='datetime-local'],
|
||||
[type='month'],
|
||||
[type='search'],
|
||||
[type='tel'],
|
||||
[type='time'],
|
||||
[type='week'],
|
||||
[multiple],
|
||||
textarea,
|
||||
select {
|
||||
border: unset;
|
||||
}
|
||||
.toggle-checkbox:checked {
|
||||
right: 0;
|
||||
border-color: #4fd1c5;
|
||||
}
|
||||
.toggle-checkbox:checked + .toggle-label {
|
||||
background-color: #4fd1c5;
|
||||
}
|
||||
:host ::ng-deep .ng-select.ng-select-single .ng-select-container {
|
||||
height: 48px;
|
||||
border: unset;
|
||||
.ng-value-container .ng-input {
|
||||
top: 10px;
|
||||
}
|
||||
span.ng-arrow-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.flex-1-1-2 {
|
||||
flex: 1 1 2%;
|
||||
}
|
||||
// .light {
|
||||
// color: #999;
|
||||
// }
|
||||
// component.css
|
||||
select {
|
||||
color: #000; /* Standard-Textfarbe für das Dropdown */
|
||||
// background-color: #fff; /* Hintergrundfarbe für das Dropdown */
|
||||
}
|
||||
|
||||
select option {
|
||||
color: #000; /* Textfarbe für Dropdown-Optionen */
|
||||
}
|
||||
|
||||
select.placeholder-selected {
|
||||
color: #999; /* Farbe für den Platzhalter */
|
||||
}
|
||||
|
||||
/* Stellt sicher, dass die Optionen im Dropdown immer schwarz sind */
|
||||
select:focus option,
|
||||
select:hover option {
|
||||
color: #000 !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||
import { NgSelectModule } from '@ng-select/ng-select';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import onChange from 'on-change';
|
||||
import { BusinessListingCriteria, CommercialPropertyListingCriteria, KeycloakUser, UserListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
|
||||
import { catchError, concat, distinctUntilChanged, Observable, of, Subject, switchMap, tap } from 'rxjs';
|
||||
import { BusinessListingCriteria, CommercialPropertyListingCriteria, GeoResult, KeycloakUser, UserListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
|
||||
import { ModalService } from '../../components/search-modal/modal.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';
|
||||
|
|
@ -14,7 +17,7 @@ import { getCriteriaStateObject, map2User } from '../../utils/utils';
|
|||
@Component({
|
||||
selector: 'app-home',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, RouterModule],
|
||||
imports: [CommonModule, FormsModule, RouterModule, NgSelectModule],
|
||||
templateUrl: './home.component.html',
|
||||
styleUrl: './home.component.scss',
|
||||
})
|
||||
|
|
@ -28,6 +31,10 @@ export class HomeComponent {
|
|||
isMenuOpen = false;
|
||||
user: KeycloakUser;
|
||||
prompt: string;
|
||||
cities$: Observable<GeoResult[]>;
|
||||
cityLoading = false;
|
||||
cityInput$ = new Subject<string>();
|
||||
cityOrState = undefined;
|
||||
public constructor(
|
||||
private router: Router,
|
||||
private modalService: ModalService,
|
||||
|
|
@ -37,6 +44,8 @@ export class HomeComponent {
|
|||
public keycloakService: KeycloakService,
|
||||
private listingsService: ListingsService,
|
||||
private criteriaChangeService: CriteriaChangeService,
|
||||
private geoService: GeoService,
|
||||
public cdRef: ChangeDetectorRef,
|
||||
) {}
|
||||
async ngOnInit() {
|
||||
const token = await this.keycloakService.getToken();
|
||||
|
|
@ -45,6 +54,7 @@ export class HomeComponent {
|
|||
sessionStorage.removeItem('broker_criteria');
|
||||
this.criteria = this.createEnhancedProxy(getCriteriaStateObject('business'));
|
||||
this.user = map2User(token);
|
||||
this.loadCities();
|
||||
}
|
||||
async changeTab(tabname: 'business' | 'commercialProperty' | 'broker') {
|
||||
this.activeTabAction = tabname;
|
||||
|
|
@ -90,6 +100,22 @@ export class HomeComponent {
|
|||
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 = value;
|
||||
}
|
||||
}
|
||||
async openModal() {
|
||||
const accepted = await this.modalService.showModal(this.criteria);
|
||||
if (accepted) {
|
||||
|
|
@ -97,4 +123,33 @@ export class HomeComponent {
|
|||
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(
|
||||
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;
|
||||
}
|
||||
setCity(city) {
|
||||
if (city) {
|
||||
this.criteria.city = city.city;
|
||||
this.criteria.state = city.state_code;
|
||||
} else {
|
||||
this.criteria.city = null;
|
||||
this.criteria.radius = null;
|
||||
this.criteria.searchType = 'exact';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export class SelectOptionsService {
|
|||
this.states = allSelectOptions.locations;
|
||||
this.gender = allSelectOptions.gender;
|
||||
this.typesOfCommercialProperty = allSelectOptions.typesOfCommercialProperty;
|
||||
this.distances = allSelectOptions.distances;
|
||||
}
|
||||
public typesOfBusiness: Array<KeyValueStyle>;
|
||||
|
||||
|
|
@ -36,6 +37,7 @@ export class SelectOptionsService {
|
|||
|
||||
public states: Array<any>;
|
||||
public customerSubTypes: Array<KeyValue>;
|
||||
public distances: Array<KeyValue>;
|
||||
getState(value: string): string {
|
||||
return this.states.find(l => l.value === value)?.name;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue