This commit is contained in:
2024-09-10 21:30:03 +02:00
parent 17213ba4b0
commit 83307684ee
25 changed files with 799 additions and 477 deletions

View File

@@ -0,0 +1,31 @@
<div>
<label for="type" class="block text-sm font-bold text-gray-700 mb-1 relative w-fit {{ labelClasses }}"
>{{ label }} @if(validationMessage){
<div
attr.data-tooltip-target="tooltip-{{ name }}"
class="absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white bg-red-500 border-2 border-white rounded-full -top-2 dark:border-gray-900 hover:cursor-pointer"
(click)="toggleTooltip($event)"
(touchstart)="toggleTooltip($event)"
>
!
</div>
<app-tooltip id="tooltip-{{ name }}" [text]="validationMessage" [isVisible]="isTooltipVisible"></app-tooltip>
}
</label>
<ng-select
class="custom"
[multiple]="false"
[hideSelected]="true"
[trackByFn]="trackByFn"
[minTermLength]="2"
[loading]="placeLoading"
typeToSearchText="Please enter 2 or more characters"
[typeahead]="placeInput$"
ngModel="{{ formatGeoAddress(value) }}"
(ngModelChange)="onInputChange($event)"
>
@for (place of places$ | async; track place.place_id) {
<ng-option [value]="place">{{ formatPlaceAddress(place) }}</ng-option>
}
</ng-select>
</div>

View File

@@ -0,0 +1,9 @@
:host ::ng-deep .ng-select.custom .ng-select-container {
// --tw-bg-opacity: 1;
// background-color: rgb(249 250 251 / var(--tw-bg-opacity));
// height: 42px;
border-radius: 0.5rem;
.ng-value-container .ng-input {
top: 10px;
}
}

View File

@@ -0,0 +1,157 @@
import { CommonModule } from '@angular/common';
import { Component, forwardRef, Input } from '@angular/core';
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
import { NgSelectModule } from '@ng-select/ng-select';
import { catchError, concat, debounceTime, distinctUntilChanged, Observable, of, Subject, switchMap, tap } from 'rxjs';
import { GeoResult } from '../../../../../bizmatch-server/src/models/main.model';
import { Place } from '../../../../../bizmatch-server/src/models/server.model';
import { GeoService } from '../../services/geo.service';
import { SelectOptionsService } from '../../services/select-options.service';
import { BaseInputComponent } from '../base-input/base-input.component';
import { TooltipComponent } from '../tooltip/tooltip.component';
import { ValidationMessagesService } from '../validation-messages.service';
@Component({
selector: 'app-validated-location',
standalone: true,
imports: [CommonModule, FormsModule, NgSelectModule, TooltipComponent],
templateUrl: './validated-location.component.html',
styleUrl: './validated-location.component.scss',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ValidatedLocationComponent),
multi: true,
},
],
})
export class ValidatedLocationComponent extends BaseInputComponent {
@Input() items;
@Input() labelClasses: string;
places$: Observable<Place[]>;
placeInput$ = new Subject<string>();
placeLoading = false;
constructor(validationMessagesService: ValidationMessagesService, private geoService: GeoService, public selectOptions: SelectOptionsService) {
super(validationMessagesService);
}
override ngOnInit() {
super.ngOnInit();
this.loadCities();
}
onInputChange(event: Place): void {
this.value = event; //{ ...event, longitude: parseFloat(event.longitude), latitude: parseFloat(event.latitude) };
this.value = {
id: event?.place_id,
name: event?.address.city,
county: event?.address.county,
street: event?.address.road,
housenumber: event?.address.house_number,
state: event?.address['ISO3166-2-lvl4'].substr(3),
latitude: event ? parseFloat(event?.lat) : undefined,
longitude: event ? parseFloat(event?.lon) : undefined,
};
this.onChange(this.value);
}
private loadCities() {
this.places$ = concat(
of([]), // default items
this.placeInput$.pipe(
debounceTime(300),
distinctUntilChanged(),
tap(() => (this.placeLoading = true)),
switchMap(term =>
this.geoService.findLocationStartingWith(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.placeLoading = false)),
),
),
),
);
}
trackByFn(item: GeoResult) {
return item.id;
}
compareFn = (item, selected) => {
return item.id === selected.id;
};
formatGeoAddress(geoResult: GeoResult | null | undefined): string {
// Überprüfen, ob geoResult null oder undefined ist
if (!geoResult) {
return '';
}
let addressParts: string[] = [];
// Füge Hausnummer hinzu, wenn vorhanden
if (geoResult.housenumber) {
addressParts.push(geoResult.housenumber);
}
// Füge Straße hinzu, wenn vorhanden
if (geoResult.street) {
addressParts.push(geoResult.street);
}
// Kombiniere Hausnummer und Straße
let address = addressParts.join(' ');
// Füge Namen hinzu, wenn vorhanden
if (geoResult.name) {
address = address ? `${address}, ${geoResult.name}` : geoResult.name;
}
// Füge County hinzu, wenn vorhanden
if (geoResult.county) {
address = address ? `${address}, ${geoResult.county}` : geoResult.county;
}
// Füge Bundesland hinzu, wenn vorhanden
if (geoResult.state) {
address = address ? `${address} - ${geoResult.state}` : geoResult.state;
}
return address;
}
formatPlaceAddress(place: Place | null | undefined): string {
// Überprüfen, ob place null oder undefined ist
if (!place) {
return '';
}
const { house_number, road, city, county, state } = place.address;
let addressParts: string[] = [];
// Füge Hausnummer hinzu, wenn vorhanden
if (house_number) {
addressParts.push(house_number);
}
// Füge Straße hinzu, wenn vorhanden
if (road) {
addressParts.push(road);
}
// Kombiniere Hausnummer und Straße
let address = addressParts.join(' ');
// Füge Stadt hinzu, wenn vorhanden
if (city) {
address = address ? `${address}, ${city}` : city;
}
// Füge County hinzu, wenn vorhanden
if (county) {
address = address ? `${address}, ${county}` : county;
}
// Füge Bundesland hinzu, wenn vorhanden
if (state) {
address = address ? `${address} - ${state}` : state;
}
return address;
}
}

View File

@@ -63,7 +63,7 @@
</div>
<div class="flex flex-col sm:flex-row sm:items-center">
<span class="font-semibold w-40 p-2">Company Location</span>
<span class="p-2 flex-grow">{{ user.companyLocation?.name }} - {{ user.companyLocation?.state }}</span>
<span class="p-2 flex-grow">{{ user.location?.name }} - {{ user.location?.state }}</span>
</div>
<div class="flex flex-col sm:flex-row sm:items-center bg-gray-100">
<span class="font-semibold w-40 p-2">Professional Type</span>

View File

@@ -119,7 +119,8 @@
<app-validated-input label="Your Phone Number" name="phoneNumber" [(ngModel)]="user.phoneNumber" mask="(000) 000-0000"></app-validated-input>
<app-validated-input label="Company Website" name="companyWebsite" [(ngModel)]="user.companyWebsite"></app-validated-input>
<!-- <app-validated-input label="Company Location" name="companyLocation" [(ngModel)]="user.companyLocation"></app-validated-input> -->
<app-validated-city label="Company Location" name="companyLocation" [(ngModel)]="user.companyLocation"></app-validated-city>
<!-- <app-validated-city label="Company Location" name="location" [(ngModel)]="user.location"></app-validated-city> -->
<app-validated-location label="Company Location" name="location" [(ngModel)]="user.location"></app-validated-location>
</div>
<!-- <div>

View File

@@ -21,6 +21,7 @@ import { TooltipComponent } from '../../../components/tooltip/tooltip.component'
import { ValidatedCityComponent } from '../../../components/validated-city/validated-city.component';
import { ValidatedCountyComponent } from '../../../components/validated-county/validated-county.component';
import { ValidatedInputComponent } from '../../../components/validated-input/validated-input.component';
import { ValidatedLocationComponent } from '../../../components/validated-location/validated-location.component';
import { ValidatedQuillComponent } from '../../../components/validated-quill/validated-quill.component';
import { ValidatedSelectComponent } from '../../../components/validated-select/validated-select.component';
import { ValidationMessagesService } from '../../../components/validation-messages.service';
@@ -52,6 +53,7 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
ValidatedCityComponent,
TooltipComponent,
ValidatedCountyComponent,
ValidatedLocationComponent,
],
providers: [TitleCasePipe, DatePipe],
templateUrl: './account.component.html',

View File

@@ -54,7 +54,8 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- <app-validated-ng-select label="State" name="state" [(ngModel)]="listing.location.state" [items]="selectOptions?.states"></app-validated-ng-select>
<app-validated-input label="City" name="city" [(ngModel)]="listing.location.city"></app-validated-input> -->
<app-validated-city label="Location" name="location" [(ngModel)]="listing.location"></app-validated-city>
<!-- <app-validated-city label="Location" name="location" [(ngModel)]="listing.location"></app-validated-city> -->
<app-validated-location label="Location" name="location" [(ngModel)]="listing.location"></app-validated-location>
<app-validated-price label="Price" name="price" [(ngModel)]="listing.price"></app-validated-price>
</div>

View File

@@ -19,6 +19,7 @@ import { environment } from '../../../../environments/environment';
import { MessageService } from '../../../components/message/message.service';
import { ValidatedCityComponent } from '../../../components/validated-city/validated-city.component';
import { ValidatedInputComponent } from '../../../components/validated-input/validated-input.component';
import { ValidatedLocationComponent } from '../../../components/validated-location/validated-location.component';
import { ValidatedNgSelectComponent } from '../../../components/validated-ng-select/validated-ng-select.component';
import { ValidatedPriceComponent } from '../../../components/validated-price/validated-price.component';
import { ValidatedQuillComponent } from '../../../components/validated-quill/validated-quill.component';
@@ -47,6 +48,7 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
ValidatedPriceComponent,
ValidatedTextareaComponent,
ValidatedCityComponent,
ValidatedLocationComponent,
],
providers: [],
templateUrl: './edit-business-listing.component.html',

View File

@@ -38,7 +38,8 @@
</div> -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<app-validated-ng-select label="Property Category" name="type" [(ngModel)]="listing.type" [items]="typesOfCommercialProperty"></app-validated-ng-select>
<app-validated-city label="Location" name="location" [(ngModel)]="listing.location"></app-validated-city>
<!-- <app-validated-city label="Location" name="location" [(ngModel)]="listing.location"></app-validated-city> -->
<app-validated-location label="Location" name="location" [(ngModel)]="listing.location"></app-validated-location>
</div>
<!-- <div class="flex mb-4 space-x-4">

View File

@@ -24,6 +24,7 @@ import { ImageCropAndUploadComponent, UploadReponse } from '../../../components/
import { MessageService } from '../../../components/message/message.service';
import { ValidatedCityComponent } from '../../../components/validated-city/validated-city.component';
import { ValidatedInputComponent } from '../../../components/validated-input/validated-input.component';
import { ValidatedLocationComponent } from '../../../components/validated-location/validated-location.component';
import { ValidatedNgSelectComponent } from '../../../components/validated-ng-select/validated-ng-select.component';
import { ValidatedPriceComponent } from '../../../components/validated-price/validated-price.component';
import { ValidatedQuillComponent } from '../../../components/validated-quill/validated-quill.component';
@@ -52,6 +53,7 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
ValidatedQuillComponent,
ValidatedNgSelectComponent,
ValidatedPriceComponent,
ValidatedLocationComponent,
ValidatedCityComponent,
ImageCropAndUploadComponent,
],

View File

@@ -1,7 +1,8 @@
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { CityAndStateResult, CountyResult, GeoResult } from '../../../../bizmatch-server/src/models/main.model';
import { Place } from '../../../../bizmatch-server/src/models/server.model';
import { environment } from '../../environments/environment';
@Injectable({
@@ -9,6 +10,7 @@ import { environment } from '../../environments/environment';
})
export class GeoService {
private apiBaseUrl = environment.apiBaseUrl;
private baseUrl: string = 'https://nominatim.openstreetmap.org/search';
constructor(private http: HttpClient) {}
findCitiesStartingWith(prefix: string, state?: string): Observable<GeoResult[]> {
@@ -21,4 +23,8 @@ export class GeoService {
findCountiesStartingWith(prefix: string, states?: string[]): Observable<CountyResult[]> {
return this.http.post<CountyResult[]>(`${this.apiBaseUrl}/bizmatch/geo/counties`, { prefix, states });
}
findLocationStartingWith(prefix: string): Observable<Place[]> {
let headers = new HttpHeaders().set('X-Hide-Loading', 'true').set('Accept-Language', 'en-US');
return this.http.get(`${this.baseUrl}?q=${prefix},US&format=json&addressdetails=1&limit=5`, { headers }) as Observable<Place[]>;
}
}

View File

@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
import { BehaviorSubject, lastValueFrom, Observable } from 'rxjs';
import urlcat from 'urlcat';
import { User } from '../../../../bizmatch-server/src/models/db.model';
import { ResponseUsersArray, StatesResult, UserListingCriteria } from '../../../../bizmatch-server/src/models/main.model';
import { ResponseUsersArray, UserListingCriteria } from '../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../environments/environment';
@Injectable({
@@ -46,7 +46,7 @@ export class UserService {
getNumberOfBroker(criteria?: UserListingCriteria): Observable<number> {
return this.http.post<number>(`${this.apiBaseUrl}/bizmatch/user/findTotal`, criteria);
}
async getAllStates(): Promise<any> {
return await lastValueFrom(this.http.get<StatesResult[]>(`${this.apiBaseUrl}/bizmatch/user/states/all`));
}
// async getAllStates(): Promise<any> {
// return await lastValueFrom(this.http.get<StatesResult[]>(`${this.apiBaseUrl}/bizmatch/user/states/all`));
// }
}

View File

@@ -139,7 +139,7 @@ export function resetUserListingCriteria(criteria: UserListingCriteria) {
export function createMailInfo(user: User): MailInfo {
return {
sender: { name: `${user.firstname} ${user.lastname}`, email: user.email, phoneNumber: user.phoneNumber, state: user.companyLocation?.state, comments: null },
sender: { name: `${user.firstname} ${user.lastname}`, email: user.email, phoneNumber: user.phoneNumber, state: user.location?.state, comments: null },
email: null,
url: environment.mailinfoUrl,
listing: null,