Umbau Filter linksseitig
|
|
@ -31,6 +31,7 @@ export function createEmptyBusinessListingCriteria(): BusinessListingCriteria {
|
|||
leasedLocation: false,
|
||||
franchiseResale: false,
|
||||
title: '',
|
||||
email: 'bizmatch@proton.me',
|
||||
brokerName: '',
|
||||
searchType: 'exact',
|
||||
radius: null,
|
||||
|
|
@ -61,6 +62,7 @@ export function resetBusinessListingCriteria(criteria: BusinessListingCriteria)
|
|||
criteria.leasedLocation = false;
|
||||
criteria.franchiseResale = false;
|
||||
criteria.title = '';
|
||||
criteria.email = 'bizmatch@proton.me';
|
||||
criteria.brokerName = '';
|
||||
criteria.searchType = 'exact';
|
||||
criteria.radius = null;
|
||||
|
|
|
|||
|
|
@ -103,6 +103,9 @@ export class BusinessListingService {
|
|||
whereConditions.push(and(ilike(schema.users.firstname, `%${firstname}%`), ilike(schema.users.lastname, `%${lastname}%`)));
|
||||
}
|
||||
}
|
||||
if (criteria.email) {
|
||||
whereConditions.push(eq(schema.users.email, criteria.email));
|
||||
}
|
||||
if (user?.role !== 'admin') {
|
||||
whereConditions.push(or(eq(businesses.email, user?.email), ne(businesses.draft, true)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ export interface BusinessListingCriteria extends ListCriteria {
|
|||
franchiseResale: boolean;
|
||||
title: string;
|
||||
brokerName: string;
|
||||
email: string;
|
||||
criteriaType: 'businessListings';
|
||||
}
|
||||
export interface CommercialPropertyListingCriteria extends ListCriteria {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@
|
|||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
},
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve --port=4300 --host 0.0.0.0 & http-server ../bizmatch-server",
|
||||
"start": "ng serve --host 0.0.0.0 & http-server ../bizmatch-server",
|
||||
"prebuild": "node version.js",
|
||||
"build": "node version.js && ng build",
|
||||
"build.dev": "node version.js && ng build --configuration dev --output-hashing=all",
|
||||
|
|
|
|||
|
|
@ -3,10 +3,16 @@
|
|||
@if (actualRoute !=='home' && actualRoute !=='login' && actualRoute!=='emailVerification' && actualRoute!=='email-authorized'){
|
||||
<header></header>
|
||||
}
|
||||
<main class="flex-1">
|
||||
<main class="flex-1 flex">
|
||||
@if (isFilterRoute()) {
|
||||
<div class="hidden md:block w-1/4 bg-white shadow-lg p-6 overflow-y-auto">
|
||||
<app-search-modal [isModal]="false"></app-search-modal>
|
||||
</div>
|
||||
}
|
||||
<div [ngClass]="{ 'w-full': !isFilterRoute(), 'md:w-3/4': isFilterRoute() }">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<app-footer></app-footer>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,3 @@
|
|||
// .progress-spinner {
|
||||
// position: fixed;
|
||||
// z-index: 999;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// bottom: 0;
|
||||
// right: 0;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// align-items: center;
|
||||
// }
|
||||
|
||||
// .progress-spinner:before {
|
||||
// content: '';
|
||||
// display: block;
|
||||
// position: fixed;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// background-color: rgba(0, 0, 0, 0.3);
|
||||
// }
|
||||
.spinner-text {
|
||||
margin-top: 20px; /* Abstand zwischen Spinner und Text anpassen */
|
||||
font-size: 20px; /* Schriftgröße nach Bedarf anpassen */
|
||||
|
|
|
|||
|
|
@ -47,42 +47,18 @@ export class AppComponent {
|
|||
this.actualRoute = currentRoute.snapshot.url[0].path;
|
||||
});
|
||||
}
|
||||
ngOnInit() {
|
||||
// this.keycloakService.keycloakEvents$.subscribe({
|
||||
// next: event => {
|
||||
// if (event.type === KeycloakEventType.OnTokenExpired) {
|
||||
// this.handleTokenExpiration();
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
}
|
||||
// private async handleTokenExpiration(): Promise<void> {
|
||||
// try {
|
||||
// // Versuche, den Token zu erneuern
|
||||
// const refreshed = await this.keycloakService.updateToken();
|
||||
// if (!refreshed) {
|
||||
// // Wenn der Token nicht erneuert werden kann, leite zur Login-Seite weiter
|
||||
// this.keycloakService.login({
|
||||
// redirectUri: window.location.href, // oder eine andere Seite
|
||||
// });
|
||||
// }
|
||||
// } catch (error) {
|
||||
// if (error.error === 'invalid_grant' && error.error_description === 'Token is not active') {
|
||||
// // Hier wird der Fehler "invalid_grant" abgefangen
|
||||
// this.keycloakService.login({
|
||||
// redirectUri: window.location.href,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
ngOnInit() {}
|
||||
@HostListener('window:keydown', ['$event'])
|
||||
handleKeyboardEvent(event: KeyboardEvent) {
|
||||
if (event.shiftKey && event.ctrlKey && event.key === 'V') {
|
||||
this.showVersionDialog();
|
||||
}
|
||||
}
|
||||
|
||||
showVersionDialog() {
|
||||
this.confirmationService.showConfirmation({ message: `App Version: ${this.build.timestamp}`, buttons: 'none' });
|
||||
}
|
||||
isFilterRoute(): boolean {
|
||||
const filterRoutes = ['/businessListings', '/commercialPropertyListings', '/brokerListings'];
|
||||
return filterRoutes.includes(this.actualRoute);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<div class="flex items-center md:order-2 space-x-3 rtl:space-x-reverse">
|
||||
<!-- Filter button -->
|
||||
@if(isFilterUrl()){
|
||||
<button
|
||||
<!-- <button
|
||||
type="button"
|
||||
#triggerButton
|
||||
(click)="openModal()"
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
class="max-sm:hidden px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg hover:bg-gray-100 hover:text-blue-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
||||
>
|
||||
<i class="fas fa-filter mr-2"></i>Filter ({{ getNumberOfFiltersSet() }})
|
||||
</button>
|
||||
</button> -->
|
||||
<!-- Sort button -->
|
||||
<div class="relative">
|
||||
<button
|
||||
|
|
@ -217,14 +217,14 @@
|
|||
</div>
|
||||
<!-- Mobile filter button -->
|
||||
<div class="md:hidden flex justify-center pb-4">
|
||||
<button
|
||||
<!-- <button
|
||||
(click)="openModal()"
|
||||
type="button"
|
||||
id="filterDropdownMobileButton"
|
||||
class="w-full mx-4 px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg hover:bg-gray-100 hover:text-blue-700 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
||||
>
|
||||
<i class="fas fa-filter mr-2"></i>Filter ({{ getNumberOfFiltersSet() }})
|
||||
</button>
|
||||
</button> -->
|
||||
<!-- Sorting -->
|
||||
<button
|
||||
(click)="toggleSortDropdown()"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,12 @@ export class ModalService {
|
|||
this.resolvePromise = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
sendCriteria(message: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria): Promise<ModalResult> {
|
||||
this.messageSubject.next(message);
|
||||
return new Promise<ModalResult>(resolve => {
|
||||
this.resolvePromise = resolve;
|
||||
});
|
||||
}
|
||||
accept(): void {
|
||||
this.modalVisibleSubject.next(false);
|
||||
this.resolvePromise({ accepted: true });
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div *ngIf="modalService.modalVisible$ | async" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full flex items-center justify-center">
|
||||
<div class="relative w-full max-w-4xl max-h-full">
|
||||
<div *ngIf="isModal && (modalService.modalVisible$ | async)" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full flex items-center justify-center z-50">
|
||||
<div class="relative w-full max-h-full">
|
||||
<div class="relative bg-white rounded-lg shadow">
|
||||
<div class="flex items-start justify-between p-4 border-b rounded-t">
|
||||
@if(criteria.criteriaType==='businessListings') {
|
||||
|
|
@ -18,16 +18,17 @@
|
|||
</div>
|
||||
<div class="p-6 space-y-6">
|
||||
<div class="flex space-x-4 mb-4">
|
||||
<button class="text-blue-600 font-medium border-b-2 border-blue-600 pb-2">Classic Search</button>
|
||||
<!-- <button class="text-gray-500">AI Search <span class="bg-gray-200 text-xs font-semibold px-2 py-1 rounded">BETA</span></button> -->
|
||||
<button class="text-blue-600 font-medium border-b-2 border-blue-600 pb-2">Filter ({{ numberOfResults$ | async }})</button>
|
||||
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-blue-500" (click)="clearFilter()"></i>
|
||||
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg shadow-sm opacity-0 tooltip">
|
||||
Clear all Filter
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
</div>
|
||||
@if(criteria){
|
||||
<div class="space-y-6">
|
||||
@if(criteria.criteriaType==='businessListings') {
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="state" class="block mb-2 text-sm font-medium text-gray-900">Location - State</label>
|
||||
|
|
@ -36,41 +37,19 @@
|
|||
<div>
|
||||
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-gray-900 font-medium" [state]="criteria.state"></app-validated-city>
|
||||
</div>
|
||||
<!-- <div>
|
||||
<label for="city" class="block mb-2 text-sm font-medium text-gray-900">Location - City</label>
|
||||
|
||||
<ng-select
|
||||
class="custom"
|
||||
[multiple]="false"
|
||||
[hideSelected]="true"
|
||||
[trackByFn]="trackByFn"
|
||||
[minTermLength]="2"
|
||||
[loading]="cityLoading"
|
||||
typeToSearchText="Please enter 2 or more characters"
|
||||
[typeahead]="cityInput$"
|
||||
[ngModel]="criteria.city"
|
||||
(ngModelChange)="setCity($event)"
|
||||
>
|
||||
@for (city of cities$ | async; track city.id) {
|
||||
<ng-option [value]="city">{{ city.city }} - {{ city.state }}</ng-option>
|
||||
}
|
||||
</ng-select>
|
||||
</div> -->
|
||||
<!-- New section for city search type -->
|
||||
<div *ngIf="criteria.city">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Search Type</label>
|
||||
<div class="flex items-center space-x-4">
|
||||
<label class="inline-flex items-center">
|
||||
<input type="radio" class="form-radio" name="searchType" [(ngModel)]="criteria.searchType" value="exact" />
|
||||
<input type="radio" class="form-radio" name="searchType" [(ngModel)]="criteria.searchType" (ngModelChange)="onCriteriaChange()" value="exact" />
|
||||
<span class="ml-2">Exact City</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center">
|
||||
<input type="radio" class="form-radio" name="searchType" [(ngModel)]="criteria.searchType" value="radius" />
|
||||
<input type="radio" class="form-radio" name="searchType" [(ngModel)]="criteria.searchType" (ngModelChange)="onCriteriaChange()" value="radius" />
|
||||
<span class="ml-2">Radius Search</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- New section for radius selection -->
|
||||
<div *ngIf="criteria.city && criteria.searchType === 'radius'" class="space-y-2">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Select Radius (in miles)</label>
|
||||
<div class="flex flex-wrap">
|
||||
|
|
@ -86,71 +65,34 @@
|
|||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="price" class="block mb-2 text-sm font-medium text-gray-900">Price</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<app-validated-price name="price-from" [(ngModel)]="criteria.minPrice" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
<!-- <input
|
||||
type="number"
|
||||
id="price-from"
|
||||
[(ngModel)]="criteria.minPrice"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="From"
|
||||
/> -->
|
||||
<app-validated-price name="price-from" [(ngModel)]="criteria.minPrice" (ngModelChange)="onCriteriaChange()" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
<span>-</span>
|
||||
<!-- <input
|
||||
type="number"
|
||||
id="price-to"
|
||||
[(ngModel)]="criteria.maxPrice"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="To"
|
||||
/> -->
|
||||
<app-validated-price name="price-to" [(ngModel)]="criteria.maxPrice" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
<app-validated-price name="price-to" [(ngModel)]="criteria.maxPrice" (ngModelChange)="onCriteriaChange()" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="salesRevenue" class="block mb-2 text-sm font-medium text-gray-900">Sales Revenue</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<!-- <input
|
||||
type="number"
|
||||
id="salesRevenue-from"
|
||||
<app-validated-price
|
||||
name="salesRevenue-from"
|
||||
[(ngModel)]="criteria.minRevenue"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
(ngModelChange)="onCriteriaChange()"
|
||||
placeholder="From"
|
||||
/> -->
|
||||
<app-validated-price name="salesRevenue-from" [(ngModel)]="criteria.minRevenue" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"
|
||||
></app-validated-price>
|
||||
<span>-</span>
|
||||
<!-- <input
|
||||
type="number"
|
||||
id="salesRevenue-to"
|
||||
[(ngModel)]="criteria.maxRevenue"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="To"
|
||||
/> -->
|
||||
<app-validated-price name="salesRevenue-to" [(ngModel)]="criteria.maxRevenue" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
<app-validated-price name="salesRevenue-to" [(ngModel)]="criteria.maxRevenue" (ngModelChange)="onCriteriaChange()" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="cashflow" class="block mb-2 text-sm font-medium text-gray-900">Cashflow</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<!-- <input
|
||||
type="number"
|
||||
id="cashflow-from"
|
||||
[(ngModel)]="criteria.minCashFlow"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="From"
|
||||
/> -->
|
||||
<app-validated-price name="cashflow-from" [(ngModel)]="criteria.minCashFlow" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
<app-validated-price name="cashflow-from" [(ngModel)]="criteria.minCashFlow" (ngModelChange)="onCriteriaChange()" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
<span>-</span>
|
||||
<app-validated-price name="cashflow-to" [(ngModel)]="criteria.maxCashFlow" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
<!-- <input
|
||||
type="number"
|
||||
id="cashflow-to"
|
||||
[(ngModel)]="criteria.maxCashFlow"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="To"
|
||||
/> -->
|
||||
<app-validated-price name="cashflow-to" [(ngModel)]="criteria.maxCashFlow" (ngModelChange)="onCriteriaChange()" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -159,12 +101,11 @@
|
|||
type="text"
|
||||
id="title"
|
||||
[(ngModel)]="criteria.title"
|
||||
(ngModelChange)="onCriteriaChange()"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||
placeholder="e.g. Restaurant"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Category</label>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
|
|
@ -225,6 +166,7 @@
|
|||
type="number"
|
||||
id="numberEmployees-from"
|
||||
[(ngModel)]="criteria.minNumberEmployees"
|
||||
(ngModelChange)="onCriteriaChange()"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="From"
|
||||
/>
|
||||
|
|
@ -233,6 +175,7 @@
|
|||
type="number"
|
||||
id="numberEmployees-to"
|
||||
[(ngModel)]="criteria.maxNumberEmployees"
|
||||
(ngModelChange)="onCriteriaChange()"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="To"
|
||||
/>
|
||||
|
|
@ -245,6 +188,7 @@
|
|||
type="number"
|
||||
id="establishedSince-From"
|
||||
[(ngModel)]="criteria.establishedSince"
|
||||
(ngModelChange)="onCriteriaChange()"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="YYYY"
|
||||
/>
|
||||
|
|
@ -253,6 +197,7 @@
|
|||
type="number"
|
||||
id="establishedSince-To"
|
||||
[(ngModel)]="criteria.establishedUntil"
|
||||
(ngModelChange)="onCriteriaChange()"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="YYYY"
|
||||
/>
|
||||
|
|
@ -264,6 +209,7 @@
|
|||
type="text"
|
||||
id="brokername"
|
||||
[(ngModel)]="criteria.brokerName"
|
||||
(ngModelChange)="onCriteriaChange()"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||
placeholder="e.g. Brokers Invest"
|
||||
/>
|
||||
|
|
@ -271,33 +217,15 @@
|
|||
</div>
|
||||
</div>
|
||||
} @if(criteria.criteriaType==='commercialPropertyListings') {
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="state" class="block mb-2 text-sm font-medium text-gray-900">Location - State</label>
|
||||
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
|
||||
</div>
|
||||
<div>
|
||||
<!-- <label for="city" class="block mb-2 text-sm font-medium text-gray-900">Location - City</label>
|
||||
<ng-select
|
||||
class="custom"
|
||||
[multiple]="false"
|
||||
[hideSelected]="true"
|
||||
[trackByFn]="trackByFn"
|
||||
[minTermLength]="2"
|
||||
[loading]="cityLoading"
|
||||
typeToSearchText="Please enter 2 or more characters"
|
||||
[typeahead]="cityInput$"
|
||||
[ngModel]="criteria.city"
|
||||
(ngModelChange)="setCity($event)"
|
||||
>
|
||||
@for (city of cities$ | async; track city.id) {
|
||||
<ng-option [value]="city">{{ city.city }} - {{ selectOptions.getStateInitials(city.state) }}</ng-option>
|
||||
}
|
||||
</ng-select> -->
|
||||
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-gray-900 font-medium" [state]="criteria.state"></app-validated-city>
|
||||
</div>
|
||||
<!-- New section for city search type -->
|
||||
<div *ngIf="criteria.city">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Search Type</label>
|
||||
<div class="flex items-center space-x-4">
|
||||
|
|
@ -311,7 +239,6 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- New section for radius selection -->
|
||||
<div *ngIf="criteria.city && criteria.searchType === 'radius'" class="space-y-2">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Select Radius (in miles)</label>
|
||||
<div class="flex flex-wrap">
|
||||
|
|
@ -330,23 +257,9 @@
|
|||
<div>
|
||||
<label for="price" class="block mb-2 text-sm font-medium text-gray-900">Price</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<!-- <input
|
||||
type="number"
|
||||
id="price-from"
|
||||
[(ngModel)]="criteria.minPrice"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="From"
|
||||
/> -->
|
||||
<app-validated-price name="price-from" [(ngModel)]="criteria.minPrice" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
<span>-</span>
|
||||
<app-validated-price name="price-to" [(ngModel)]="criteria.maxPrice" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
<!-- <input
|
||||
type="number"
|
||||
id="price-to"
|
||||
[(ngModel)]="criteria.maxPrice"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="To"
|
||||
/> -->
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -359,8 +272,6 @@
|
|||
placeholder="e.g. Restaurant"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Category</label>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
|
|
@ -382,7 +293,7 @@
|
|||
</div>
|
||||
</div>
|
||||
} @if(criteria.criteriaType==='brokerListings') {
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="states" class="block mb-2 text-sm font-medium text-gray-900">Locations served - States</label>
|
||||
|
|
@ -402,27 +313,9 @@
|
|||
typeToSearchText="Please enter 2 or more characters"
|
||||
[typeahead]="countyInput$"
|
||||
[(ngModel)]="criteria.counties"
|
||||
>
|
||||
</ng-select>
|
||||
></ng-select>
|
||||
</div>
|
||||
<div>
|
||||
<!-- <label for="city" class="block mb-2 text-sm font-medium text-gray-900">Location - City</label>
|
||||
<ng-select
|
||||
class="custom"
|
||||
[multiple]="false"
|
||||
[hideSelected]="true"
|
||||
[trackByFn]="trackByFn"
|
||||
[minTermLength]="2"
|
||||
[loading]="cityLoading"
|
||||
typeToSearchText="Please enter 2 or more characters"
|
||||
[typeahead]="cityInput$"
|
||||
[ngModel]="criteria.city"
|
||||
(ngModelChange)="setCity($event)"
|
||||
>
|
||||
@for (city of cities$ | async; track city.id) {
|
||||
<ng-option [value]="city">{{ city.name }} - {{ selectOptions.getStateInitials(city.state) }}</ng-option>
|
||||
}
|
||||
</ng-select> -->
|
||||
<app-validated-city
|
||||
label="Company Location - City"
|
||||
name="city"
|
||||
|
|
@ -432,7 +325,6 @@
|
|||
[state]="criteria.state"
|
||||
></app-validated-city>
|
||||
</div>
|
||||
<!-- New section for city search type -->
|
||||
<div *ngIf="criteria.city">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Search Type</label>
|
||||
<div class="flex items-center space-x-4">
|
||||
|
|
@ -446,16 +338,6 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="brokername" class="block mb-2 text-sm font-medium text-gray-900">Name of Professional</label>
|
||||
<input
|
||||
type="text"
|
||||
id="brokername"
|
||||
[(ngModel)]="criteria.brokerName"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||
/>
|
||||
</div>
|
||||
<!-- New section for radius selection -->
|
||||
<div *ngIf="criteria.city && criteria.searchType === 'radius'" class="space-y-2">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Select Radius (in miles)</label>
|
||||
<div class="flex flex-wrap">
|
||||
|
|
@ -471,28 +353,19 @@
|
|||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Category</label>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
@for(tob of selectOptions.customerSubTypes; track tob){
|
||||
<div class="flex items-center">
|
||||
<label for="brokername" class="block mb-2 text-sm font-medium text-gray-900">Name of Professional</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="automotive"
|
||||
[ngModel]="isTypeOfProfessionalClicked(tob)"
|
||||
(ngModelChange)="categoryClicked($event, tob.value)"
|
||||
value="{{ tob.value }}"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500"
|
||||
type="text"
|
||||
id="brokername"
|
||||
[(ngModel)]="criteria.brokerName"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||
/>
|
||||
<label for="automotive" class="ml-2 text-sm font-medium text-gray-900">{{ tob.name }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b">
|
||||
|
|
@ -510,3 +383,331 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!isModal" class="space-y-6">
|
||||
<div class="flex space-x-4 mb-4">
|
||||
<h3 class="text-xl font-semibold text-gray-900">Filter ({{ numberOfResults$ | async }})</h3>
|
||||
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-blue-500" (click)="clearFilter()"></i>
|
||||
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg shadow-sm opacity-0 tooltip">
|
||||
Clear all Filter
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
</div>
|
||||
@if(criteria.criteriaType==='businessListings') {
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="state" class="block mb-2 text-sm font-medium text-gray-900">Location - State</label>
|
||||
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
|
||||
</div>
|
||||
<div>
|
||||
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-gray-900 font-medium" [state]="criteria.state"></app-validated-city>
|
||||
</div>
|
||||
<div *ngIf="criteria.city">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Search Type</label>
|
||||
<div class="flex items-center space-x-4">
|
||||
<label class="inline-flex items-center">
|
||||
<input type="radio" class="form-radio" name="searchType" (ngModelChange)="onCriteriaChange()" [(ngModel)]="criteria.searchType" value="exact" />
|
||||
<span class="ml-2">Exact City</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center">
|
||||
<input type="radio" class="form-radio" name="searchType" (ngModelChange)="onCriteriaChange()" [(ngModel)]="criteria.searchType" value="radius" />
|
||||
<span class="ml-2">Radius Search</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="criteria.city && criteria.searchType === 'radius'" class="space-y-2">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Select Radius (in miles)</label>
|
||||
<div class="flex flex-wrap">
|
||||
@for (radius of [5, 20, 50, 100, 200, 300, 400, 500]; track radius) {
|
||||
<button
|
||||
type="button"
|
||||
class="px-3 py-2 text-xs font-medium text-center border border-gray-200 hover:bg-gray-500 hover:text-white"
|
||||
[ngClass]="criteria.radius === radius ? 'text-white bg-gray-500' : 'text-gray-900 bg-white'"
|
||||
(click)="setRadius(radius)"
|
||||
>
|
||||
{{ radius }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="price" class="block mb-2 text-sm font-medium text-gray-900">Price</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<app-validated-price name="price-from" (ngModelChange)="onCriteriaChange()" [(ngModel)]="criteria.minPrice" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
<span>-</span>
|
||||
<app-validated-price name="price-to" (ngModelChange)="onCriteriaChange()" [(ngModel)]="criteria.maxPrice" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="salesRevenue" class="block mb-2 text-sm font-medium text-gray-900">Sales Revenue</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<app-validated-price name="salesRevenue-from" (ngModelChange)="onCriteriaChange()" [(ngModel)]="criteria.minRevenue" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
<span>-</span>
|
||||
<app-validated-price name="salesRevenue-to" (ngModelChange)="onCriteriaChange()" [(ngModel)]="criteria.maxRevenue" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="cashflow" class="block mb-2 text-sm font-medium text-gray-900">Cashflow</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<app-validated-price name="cashflow-from" (ngModelChange)="onCriteriaChange()" [(ngModel)]="criteria.minCashFlow" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
<span>-</span>
|
||||
<app-validated-price name="cashflow-to" (ngModelChange)="onCriteriaChange()" [(ngModel)]="criteria.maxCashFlow" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="title" class="block mb-2 text-sm font-medium text-gray-900">Title / Description (Free Search)</label>
|
||||
<input
|
||||
type="text"
|
||||
id="title"
|
||||
[(ngModel)]="criteria.title"
|
||||
(ngModelChange)="onCriteriaChange()"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||
placeholder="e.g. Restaurant"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Category</label>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
@for(tob of selectOptions.typesOfBusiness; track tob) {
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="automotive"
|
||||
[ngModel]="isTypeOfBusinessClicked(tob)"
|
||||
(ngModelChange)="categoryClicked($event, tob.value)"
|
||||
value="{{ tob.value }}"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
<label for="automotive" class="ml-2 text-sm font-medium text-gray-900">{{ tob.name }}</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Type of Property</label>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
[(ngModel)]="criteria.realEstateChecked"
|
||||
(ngModelChange)="onCheckboxChange('realEstateChecked', $event)"
|
||||
type="checkbox"
|
||||
name="realEstateChecked"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
<label for="realEstateChecked" class="ml-2 text-sm font-medium text-gray-900">Real Estate</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
[(ngModel)]="criteria.leasedLocation"
|
||||
(ngModelChange)="onCheckboxChange('leasedLocation', $event)"
|
||||
type="checkbox"
|
||||
name="leasedLocation"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
<label for="leasedLocation" class="ml-2 text-sm font-medium text-gray-900">Leased Location</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
[(ngModel)]="criteria.franchiseResale"
|
||||
(ngModelChange)="onCheckboxChange('franchiseResale', $event)"
|
||||
type="checkbox"
|
||||
name="franchiseResale"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
<label for="franchiseResale" class="ml-2 text-sm font-medium text-gray-900">Franchise</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="numberEmployees" class="block mb-2 text-sm font-medium text-gray-900">Number of Employees</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input
|
||||
type="number"
|
||||
id="numberEmployees-from"
|
||||
[(ngModel)]="criteria.minNumberEmployees"
|
||||
(ngModelChange)="onCriteriaChange()"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="From"
|
||||
/>
|
||||
<span>-</span>
|
||||
<input
|
||||
type="number"
|
||||
id="numberEmployees-to"
|
||||
[(ngModel)]="criteria.maxNumberEmployees"
|
||||
(ngModelChange)="onCriteriaChange()"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="To"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="establishedSince" class="block mb-2 text-sm font-medium text-gray-900">Established Since</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input
|
||||
type="number"
|
||||
id="establishedSince-From"
|
||||
[(ngModel)]="criteria.establishedSince"
|
||||
(ngModelChange)="onCriteriaChange()"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="YYYY"
|
||||
/>
|
||||
<span>-</span>
|
||||
<input
|
||||
type="number"
|
||||
id="establishedSince-To"
|
||||
[(ngModel)]="criteria.establishedUntil"
|
||||
(ngModelChange)="onCriteriaChange()"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
|
||||
placeholder="YYYY"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="brokername" class="block mb-2 text-sm font-medium text-gray-900">Broker Name / Company Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="brokername"
|
||||
[(ngModel)]="criteria.brokerName"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||
placeholder="e.g. Brokers Invest"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
} @if(criteria.criteriaType==='commercialPropertyListings') {
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="state" class="block mb-2 text-sm font-medium text-gray-900">Location - State</label>
|
||||
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state"></ng-select>
|
||||
</div>
|
||||
<div>
|
||||
<app-validated-city label="Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-gray-900 font-medium" [state]="criteria.state"></app-validated-city>
|
||||
</div>
|
||||
<div *ngIf="criteria.city">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Search Type</label>
|
||||
<div class="flex items-center space-x-4">
|
||||
<label class="inline-flex items-center">
|
||||
<input type="radio" class="form-radio" name="searchType" [(ngModel)]="criteria.searchType" value="exact" />
|
||||
<span class="ml-2">Exact City</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center">
|
||||
<input type="radio" class="form-radio" name="searchType" [(ngModel)]="criteria.searchType" value="radius" />
|
||||
<span class="ml-2">Radius Search</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="criteria.city && criteria.searchType === 'radius'" class="space-y-2">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Select Radius (in miles)</label>
|
||||
<div class="flex flex-wrap">
|
||||
@for (radius of [5, 20, 50, 100, 200, 300, 400, 500]; track radius) {
|
||||
<button
|
||||
type="button"
|
||||
class="px-3 py-2 text-xs font-medium text-center border border-gray-200 hover:bg-gray-500 hover:text-white"
|
||||
[ngClass]="criteria.radius === radius ? 'text-white bg-gray-500' : 'text-gray-900 bg-white'"
|
||||
(click)="criteria.radius = radius"
|
||||
>
|
||||
{{ radius }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="price" class="block mb-2 text-sm font-medium text-gray-900">Price</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<app-validated-price name="price-from" [(ngModel)]="criteria.minPrice" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
<span>-</span>
|
||||
<app-validated-price name="price-to" [(ngModel)]="criteria.maxPrice" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="title" class="block mb-2 text-sm font-medium text-gray-900">Title / Description (Free Search)</label>
|
||||
<input
|
||||
type="text"
|
||||
id="title"
|
||||
[(ngModel)]="criteria.title"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||
placeholder="e.g. Restaurant"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Category</label>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
@for(tob of selectOptions.typesOfCommercialProperty; track tob) {
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="automotive"
|
||||
[ngModel]="isTypeOfBusinessClicked(tob)"
|
||||
(ngModelChange)="categoryClicked($event, tob.value)"
|
||||
value="{{ tob.value }}"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
<label for="automotive" class="ml-2 text-sm font-medium text-gray-900">{{ tob.name }}</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
} @if(criteria.criteriaType==='brokerListings') {
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="states" class="block mb-2 text-sm font-medium text-gray-900">Locations served - States</label>
|
||||
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [ngModel]="criteria.state" (ngModelChange)="setState($event)" name="state" [multiple]="false"></ng-select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="counties" class="block mb-2 text-sm font-medium text-gray-900">Locations served - Counties</label>
|
||||
<ng-select
|
||||
[items]="counties$ | async"
|
||||
bindLabel="name"
|
||||
class="custom"
|
||||
[multiple]="true"
|
||||
[hideSelected]="true"
|
||||
[trackByFn]="trackByFn"
|
||||
[minTermLength]="2"
|
||||
[loading]="countyLoading"
|
||||
typeToSearchText="Please enter 2 or more characters"
|
||||
[typeahead]="countyInput$"
|
||||
[(ngModel)]="criteria.counties"
|
||||
></ng-select>
|
||||
</div>
|
||||
<div>
|
||||
<app-validated-city label="Company Location - City" name="city" [ngModel]="criteria.city" (ngModelChange)="setCity($event)" labelClasses="text-gray-900 font-medium" [state]="criteria.state"></app-validated-city>
|
||||
</div>
|
||||
<div *ngIf="criteria.city">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Search Type</label>
|
||||
<div class="flex items-center space-x-4">
|
||||
<label class="inline-flex items-center">
|
||||
<input type="radio" class="form-radio" name="searchType" [(ngModel)]="criteria.searchType" value="exact" />
|
||||
<span class="ml-2">Exact City</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center">
|
||||
<input type="radio" class="form-radio" name="searchType" [(ngModel)]="criteria.searchType" value="radius" />
|
||||
<span class="ml-2">Radius Search</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="criteria.city && criteria.searchType === 'radius'" class="space-y-2">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900">Select Radius (in miles)</label>
|
||||
<div class="flex flex-wrap">
|
||||
@for (radius of [5, 20, 50, 100, 200, 300, 400, 500]; track radius) {
|
||||
<button
|
||||
type="button"
|
||||
class="px-3 py-2 text-xs font-medium text-center border border-gray-200 hover:bg-gray-500 hover:text-white"
|
||||
[ngClass]="criteria.radius === radius ? 'text-white bg-gray-500' : 'text-gray-900 bg-white'"
|
||||
(click)="criteria.radius = radius"
|
||||
>
|
||||
{{ radius }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="brokername" class="block mb-2 text-sm font-medium text-gray-900">Name of Professional</label>
|
||||
<input type="text" id="brokername" [(ngModel)]="criteria.brokerName" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<!-- <div class="flex items-center space-x-2">
|
||||
<button type="button" (click)="modalService.accept()" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center">
|
||||
Search ({{ numberOfResults$ | async }})
|
||||
</button>
|
||||
</div> -->
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, Input, Output } from '@angular/core';
|
||||
import { NgSelectModule } from '@ng-select/ng-select';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { catchError, concat, debounceTime, distinctUntilChanged, map, Observable, of, Subject, Subscription, switchMap, tap } from 'rxjs';
|
||||
|
|
@ -7,10 +7,11 @@ import { BusinessListingCriteria, CommercialPropertyListingCriteria, CountyResul
|
|||
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 { SharedModule } from '../../shared/shared/shared.module';
|
||||
import { resetBusinessListingCriteria, resetCommercialPropertyListingCriteria, resetUserListingCriteria } from '../../utils/utils';
|
||||
import { getCriteriaStateObject, resetBusinessListingCriteria, resetCommercialPropertyListingCriteria, resetUserListingCriteria } from '../../utils/utils';
|
||||
import { ValidatedCityComponent } from '../validated-city/validated-city.component';
|
||||
import { ValidatedPriceComponent } from '../validated-price/validated-price.component';
|
||||
import { ModalService } from './modal.service';
|
||||
|
|
@ -23,6 +24,9 @@ import { ModalService } from './modal.service';
|
|||
styleUrl: './search-modal.component.scss',
|
||||
})
|
||||
export class SearchModalComponent {
|
||||
@Output()
|
||||
@Input()
|
||||
isModal: boolean = true;
|
||||
// cities$: Observable<GeoResult[]>;
|
||||
counties$: Observable<CountyResult[]>;
|
||||
// cityLoading = false;
|
||||
|
|
@ -31,7 +35,8 @@ export class SearchModalComponent {
|
|||
countyInput$ = new Subject<string>();
|
||||
private criteriaChangeSubscription: Subscription;
|
||||
public criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria;
|
||||
backupCriteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria;
|
||||
|
||||
public backupCriteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria = getCriteriaStateObject('businessListings');
|
||||
numberOfResults$: Observable<number>;
|
||||
cancelDisable = false;
|
||||
constructor(
|
||||
|
|
@ -41,9 +46,11 @@ export class SearchModalComponent {
|
|||
private criteriaChangeService: CriteriaChangeService,
|
||||
private listingService: ListingsService,
|
||||
private userService: UserService,
|
||||
private searchService: SearchService,
|
||||
) {}
|
||||
ngOnInit() {
|
||||
this.setupCriteriaChangeListener();
|
||||
|
||||
this.modalService.message$.pipe(untilDestroyed(this)).subscribe(msg => {
|
||||
this.criteria = msg;
|
||||
this.backupCriteria = JSON.parse(JSON.stringify(msg));
|
||||
|
|
@ -69,6 +76,7 @@ export class SearchModalComponent {
|
|||
this.criteria.types.splice(index, 1);
|
||||
}
|
||||
}
|
||||
this.searchService.search(this.criteria);
|
||||
}
|
||||
private loadCounties() {
|
||||
this.counties$ = concat(
|
||||
|
|
@ -86,6 +94,9 @@ export class SearchModalComponent {
|
|||
),
|
||||
);
|
||||
}
|
||||
onCriteriaChange() {
|
||||
this.searchService.search(this.criteria);
|
||||
}
|
||||
setCity(city) {
|
||||
if (city) {
|
||||
this.criteria.city = city;
|
||||
|
|
@ -95,6 +106,7 @@ export class SearchModalComponent {
|
|||
this.criteria.radius = null;
|
||||
this.criteria.searchType = 'exact';
|
||||
}
|
||||
this.searchService.search(this.criteria);
|
||||
}
|
||||
setState(state: string) {
|
||||
if (state) {
|
||||
|
|
@ -103,6 +115,11 @@ export class SearchModalComponent {
|
|||
this.criteria.state = null;
|
||||
this.setCity(null);
|
||||
}
|
||||
this.searchService.search(this.criteria);
|
||||
}
|
||||
setRadius(radius: number) {
|
||||
this.criteria.radius = radius;
|
||||
this.searchService.search(this.criteria);
|
||||
}
|
||||
private setupCriteriaChangeListener() {
|
||||
this.criteriaChangeSubscription = this.criteriaChangeService.criteriaChange$.pipe(debounceTime(400)).subscribe(() => {
|
||||
|
|
@ -160,5 +177,6 @@ export class SearchModalComponent {
|
|||
|
||||
// Aktivieren Sie nur die aktuell ausgewählte Checkbox
|
||||
this.criteria[checkbox] = value;
|
||||
this.searchService.search(this.criteria);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,28 @@
|
|||
<div class="container mx-auto p-4">
|
||||
<div class="flex flex-col md:flex-row">
|
||||
<!-- Filter Panel for Desktop -->
|
||||
<div class="hidden md:block w-full md:w-1/4 h-full bg-white shadow-lg p-6 overflow-y-auto z-10">
|
||||
<app-search-modal [isModal]="false"></app-search-modal>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="w-full p-4">
|
||||
<div class="container mx-auto">
|
||||
@if(listings?.length > 0) {
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<!-- Anzahl der Spalten auf 3 reduziert und den Abstand erhöht -->
|
||||
@for (listing of listings; track listing.id) {
|
||||
<div class="bg-white rounded-lg drop-shadow-custom-bg-mobile md:drop-shadow-custom-bg overflow-hidden hover:shadow-xl">
|
||||
<!-- Hover-Effekt hinzugefügt -->
|
||||
<div class="p-6 flex flex-col h-full relative z-[0]">
|
||||
<div class="flex items-center mb-4">
|
||||
<i [class]="selectOptions.getIconAndTextColorType(listing.type)" class="mr-2 text-xl"></i>
|
||||
<!-- Icon vergrößert -->
|
||||
<span [class]="selectOptions.getTextColorType(listing.type)" class="font-bold text-lg">{{ selectOptions.getBusiness(listing.type) }}</span>
|
||||
<!-- Schriftgröße erhöht -->
|
||||
</div>
|
||||
<h2 class="text-xl font-semibold mb-4">
|
||||
<!-- Überschrift vergrößert -->
|
||||
{{ listing.title }}
|
||||
@if(listing.draft) {
|
||||
<span class="bg-red-100 text-red-800 text-sm font-medium me-2 ml-2 px-2.5 py-0.5 rounded dark:bg-red-900 dark:text-red-300">Draft</span>
|
||||
}
|
||||
</h2>
|
||||
<div class="flex justify-between">
|
||||
<!-- State Badge -->
|
||||
<span class="w-fit inline-flex items-center justify-center px-2 py-1 mb-4 text-xs font-bold leading-none bg-gray-200 text-gray-700 rounded-full">
|
||||
{{ selectOptions.getState(listing.location.state) }}
|
||||
</span>
|
||||
|
|
@ -28,7 +30,6 @@
|
|||
<strong>{{ getDaysListed(listing) }} days listed</strong>
|
||||
</p>
|
||||
</div>
|
||||
<!-- Asking Price hervorgehoben -->
|
||||
<p class="text-base font-bold text-gray-800 mb-2">
|
||||
<strong>Asking price:</strong> <span class="text-green-600"> {{ listing.price | currency : 'USD' : 'symbol' : '1.0-0' }}</span>
|
||||
</p>
|
||||
|
|
@ -36,15 +37,12 @@
|
|||
<p class="text-sm text-gray-600 mb-2"><strong>Net profit:</strong> {{ listing.cashFlow | currency : 'USD' : 'symbol' : '1.0-0' }}</p>
|
||||
<p class="text-sm text-gray-600 mb-2"><strong>Location:</strong> {{ listing.location.name ? listing.location.name : listing.location.county }}</p>
|
||||
<p class="text-sm text-gray-600 mb-4"><strong>Established:</strong> {{ listing.established }}</p>
|
||||
|
||||
<img src="{{ env.imageBaseUrl }}/pictures/logo/{{ listing.imageName }}.avif?_ts={{ ts }}" alt="Company logo" class="absolute bottom-[80px] right-[20px] h-[45px] w-auto" />
|
||||
<!-- Position und Größe des Bildes angepasst -->
|
||||
<div class="flex-grow"></div>
|
||||
<button
|
||||
class="bg-green-500 text-white px-5 py-3 rounded-full w-full flex items-center justify-center mt-4 transition-colors duration-200 hover:bg-green-600"
|
||||
[routerLink]="['/details-business-listing', listing.id]"
|
||||
>
|
||||
<!-- Button-Größe und Hover-Effekt verbessert -->
|
||||
View Full Listing
|
||||
<i class="fas fa-arrow-right ml-2"></i>
|
||||
</button>
|
||||
|
|
@ -77,7 +75,7 @@
|
|||
stroke="#818CF8"
|
||||
/>
|
||||
<path
|
||||
d="M83.185 87.2285C82.5387 87.2285 82.0027 86.6926 82.0027 86.0305C82.0027 83.3821 77.9987 83.3821 77.9987 86.0305C77.9987 86.6926 77.4627 87.2285 76.8006 87.2285C76.1543 87.2285 75.6183 86.6926 75.6183 86.0305C75.6183 80.2294 84.3831 80.2451 84.3831 86.0305C84.3831 86.6926 83.8471 87.2285 83.185 87.2285Z"
|
||||
d="M83.185 87.2285C82.5387 87.2285 82.0027 86.6926 82.0027 86.0305C82.0027 86.0305 82.0027 83.3821 77.9987 83.3821C77.9987 83.3821 77.9987 86.0305 77.9987 86.0305C77.9987 86.6926 77.4627 87.2285 76.8006 87.2285C76.1543 87.2285 75.6183 86.6926 75.6183 86.0305C75.6183 80.2294 84.3831 80.2451 84.3831 86.0305C84.3831 86.6926 83.8471 87.2285 83.185 87.2285Z"
|
||||
fill="#4F46E5"
|
||||
/>
|
||||
<path
|
||||
|
|
@ -107,7 +105,11 @@
|
|||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if(pageCount > 1) {
|
||||
<app-paginator [page]="page" [pageCount]="pageCount" (pageChange)="onPageChange($event)"></app-paginator>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Filter Button for Mobile -->
|
||||
<button (click)="openFilterModal()" class="md:hidden fixed bottom-4 right-4 bg-blue-500 text-white p-3 rounded-full shadow-lg z-20"><i class="fas fa-filter"></i> Filter</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { BusinessListingCriteria, LISTINGS_PER_PAGE, ListingType, emailToDirName
|
|||
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 { ImageService } from '../../../services/image.service';
|
||||
import { ListingsService } from '../../../services/listings.service';
|
||||
|
|
@ -19,7 +20,7 @@ import { assignProperties, getCriteriaProxy, resetBusinessListingCriteria } from
|
|||
@Component({
|
||||
selector: 'app-business-listings',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, RouterModule, PaginatorComponent],
|
||||
imports: [CommonModule, FormsModule, RouterModule, PaginatorComponent, SearchModalComponent],
|
||||
templateUrl: './business-listings.component.html',
|
||||
styleUrls: ['./business-listings.component.scss', '../../pages.scss'],
|
||||
})
|
||||
|
|
@ -55,6 +56,7 @@ export class BusinessListingsComponent {
|
|||
private criteriaChangeService: CriteriaChangeService,
|
||||
) {
|
||||
this.criteria = getCriteriaProxy('businessListings', this) as BusinessListingCriteria;
|
||||
this.modalService.sendCriteria(this.criteria);
|
||||
this.init();
|
||||
this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => {
|
||||
if (criteria && criteria.criteriaType === 'businessListings') {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
export interface ValidationMessage {
|
||||
field: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ValidationService {
|
||||
private messages: ValidationMessage[] = [];
|
||||
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Fügt Validierungsnachrichten hinzu oder aktualisiert bestehende
|
||||
* @param messages Array von Validierungsnachrichten
|
||||
*/
|
||||
setMessages(messages: ValidationMessage[]): void {
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht alle Validierungsmeldungen
|
||||
*/
|
||||
clearMessages(): void {
|
||||
this.messages = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob für ein bestimmtes Feld eine Validierungsmeldung existiert
|
||||
* @param field Name des Feldes
|
||||
* @returns true, wenn eine Meldung existiert
|
||||
*/
|
||||
hasMessage(field: string): boolean {
|
||||
return this.messages.some(message => message.field === field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Validierungsmeldung für ein bestimmtes Feld zurück
|
||||
* @param field Name des Feldes
|
||||
* @returns ValidationMessage oder null, wenn keine Meldung existiert
|
||||
*/
|
||||
getMessage(field: string): ValidationMessage | null {
|
||||
return this.messages.find(message => message.field === field) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hilfsmethode zur Verarbeitung von API-Fehlermeldungen
|
||||
* @param error API-Fehler mit message-Array
|
||||
*/
|
||||
handleApiError(error: any): void {
|
||||
if (error && error.message && Array.isArray(error.message)) {
|
||||
this.setMessages(error.message);
|
||||
} else {
|
||||
this.clearMessages();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ export function createEmptyBusinessListingCriteria(): BusinessListingCriteria {
|
|||
leasedLocation: false,
|
||||
franchiseResale: false,
|
||||
title: '',
|
||||
email: '',
|
||||
brokerName: '',
|
||||
searchType: 'exact',
|
||||
radius: null,
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 662 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 667 B |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |