new Landing page, stripped app
This commit is contained in:
@@ -0,0 +1,394 @@
|
||||
<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 class="relative bg-white rounded-lg shadow">
|
||||
<div class="flex items-start justify-between p-4 border-b rounded-t">
|
||||
@if(criteria.criteriaType==='businessListings'){
|
||||
<h3 class="text-xl font-semibold text-gray-900">
|
||||
Business Listing Search
|
||||
</h3>
|
||||
} @else if (criteria.criteriaType==='commercialPropertyListings'){
|
||||
<h3 class="text-xl font-semibold text-gray-900">
|
||||
Property Listing Search
|
||||
</h3>
|
||||
} @else {
|
||||
<h3 class="text-xl font-semibold text-gray-900">
|
||||
Professional Listing Search
|
||||
</h3>
|
||||
}
|
||||
<button
|
||||
(click)="close()"
|
||||
type="button"
|
||||
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center"
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 14 14"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
|
||||
/>
|
||||
</svg>
|
||||
<span class="sr-only">Close Modal</span>
|
||||
</button>
|
||||
</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>
|
||||
<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>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 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>
|
||||
<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">
|
||||
<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>
|
||||
<!-- 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">
|
||||
@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="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"
|
||||
[(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"
|
||||
[(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"
|
||||
[(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"
|
||||
[(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"
|
||||
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">
|
||||
@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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b"
|
||||
>
|
||||
<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>
|
||||
<button
|
||||
type="button"
|
||||
(click)="close()"
|
||||
class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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: 46px;
|
||||
border-radius: 0.5rem;
|
||||
.ng-value-container .ng-input {
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import { AsyncPipe, CommonModule, NgIf } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { NgSelectModule } from '@ng-select/ng-select';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { debounceTime, Observable, of, Subject, Subscription } from 'rxjs';
|
||||
import { BusinessListingCriteria, CountyResult, GeoResult, KeyValue, KeyValueStyle } from '../../../../../bizmatch-server/src/models/main.model';
|
||||
|
||||
import { ModalService } from '../../services/modal.service';
|
||||
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||
import { CriteriaChangeService } from '../../services/criteria-change.service';
|
||||
import { ListingsService } from '../../services/listings.service';
|
||||
import { SelectOptionsService } from '../../services/select-options.service';
|
||||
import { UserService } from '../../services/user.service';
|
||||
import { resetBusinessListingCriteria } from '../../utils/utils';
|
||||
import { ValidatedPriceComponent } from '../validated-price/validated-price.component';
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
selector: 'app-search-modal',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, RouterModule, FontAwesomeModule, AsyncPipe, NgIf, NgSelectModule, ValidatedPriceComponent],
|
||||
templateUrl: './search-modal.component.html',
|
||||
styleUrl: './search-modal.component.scss',
|
||||
})
|
||||
export class SearchModalComponent {
|
||||
// cities$: Observable<GeoResult[]>;
|
||||
counties$: Observable<CountyResult[]>;
|
||||
// cityLoading = false;
|
||||
countyLoading = false;
|
||||
// cityInput$ = new Subject<string>();
|
||||
countyInput$ = new Subject<string>();
|
||||
private criteriaChangeSubscription: Subscription;
|
||||
public criteria: BusinessListingCriteria;
|
||||
backupCriteria: BusinessListingCriteria;
|
||||
numberOfResults$: Observable<number>;
|
||||
cancelDisable = false;
|
||||
constructor(public selectOptions: SelectOptionsService, public modalService: ModalService, private criteriaChangeService: CriteriaChangeService, private listingService: ListingsService, private userService: UserService) {}
|
||||
ngOnInit() {
|
||||
this.setupCriteriaChangeListener();
|
||||
this.modalService.message$.pipe(untilDestroyed(this)).subscribe(msg => {
|
||||
this.criteria = msg as BusinessListingCriteria;
|
||||
this.backupCriteria = JSON.parse(JSON.stringify(msg));
|
||||
this.setTotalNumberOfResults();
|
||||
});
|
||||
this.modalService.modalVisible$.pipe(untilDestroyed(this)).subscribe(val => {
|
||||
if (val) {
|
||||
this.criteria.page = 1;
|
||||
this.criteria.start = 0;
|
||||
}
|
||||
});
|
||||
// this.loadCities();
|
||||
// this.loadCounties();
|
||||
}
|
||||
|
||||
ngOnChanges() {}
|
||||
categoryClicked(checked: boolean, value: string) {
|
||||
if (checked) {
|
||||
this.criteria.types.push(value);
|
||||
} else {
|
||||
const index = this.criteria.types.findIndex(t => t === value);
|
||||
if (index > -1) {
|
||||
this.criteria.types.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// private loadCounties() {
|
||||
// this.counties$ = concat(
|
||||
// of([]), // default items
|
||||
// this.countyInput$.pipe(
|
||||
// distinctUntilChanged(),
|
||||
// tap(() => (this.countyLoading = true)),
|
||||
// switchMap(term =>
|
||||
// this.geoService.findCountiesStartingWith(term).pipe(
|
||||
// catchError(() => of([])), // empty list on error
|
||||
// map(counties => counties.map(county => county.name)), // transform the list of objects to a list of city names
|
||||
// tap(() => (this.countyLoading = false)),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
setCity(city) {
|
||||
if (city) {
|
||||
this.criteria.city = city;
|
||||
this.criteria.state = city.state;
|
||||
} else {
|
||||
this.criteria.city = null;
|
||||
this.criteria.radius = null;
|
||||
this.criteria.searchType = 'exact';
|
||||
}
|
||||
}
|
||||
setState(state: string) {
|
||||
if (state) {
|
||||
this.criteria.state = state;
|
||||
} else {
|
||||
this.criteria.state = null;
|
||||
this.setCity(null);
|
||||
}
|
||||
}
|
||||
private setupCriteriaChangeListener() {
|
||||
this.criteriaChangeSubscription = this.criteriaChangeService.criteriaChange$.pipe(debounceTime(400)).subscribe(() => {
|
||||
this.setTotalNumberOfResults();
|
||||
this.cancelDisable = true;
|
||||
});
|
||||
}
|
||||
trackByFn(item: GeoResult) {
|
||||
return item.id;
|
||||
}
|
||||
search() {
|
||||
console.log('Search criteria:', this.criteria);
|
||||
}
|
||||
// getCounties() {
|
||||
// this.geoService.findCountiesStartingWith('');
|
||||
// }
|
||||
closeModal() {
|
||||
console.log('Closing modal');
|
||||
}
|
||||
isTypeOfBusinessClicked(v: KeyValueStyle) {
|
||||
return this.criteria.types.find(t => t === v.value);
|
||||
}
|
||||
isTypeOfProfessionalClicked(v: KeyValue) {
|
||||
return this.criteria.types.find(t => t === v.value);
|
||||
}
|
||||
setTotalNumberOfResults() {
|
||||
if (this.criteria) {
|
||||
console.log(`Getting total number of results for ${this.criteria.criteriaType}`);
|
||||
if (this.criteria.criteriaType === 'businessListings' || this.criteria.criteriaType === 'commercialPropertyListings') {
|
||||
this.numberOfResults$ = this.listingService.getNumberOfListings(this.criteria);
|
||||
} else if (this.criteria.criteriaType === 'brokerListings') {
|
||||
this.numberOfResults$ = this.userService.getNumberOfBroker();
|
||||
} else {
|
||||
this.numberOfResults$ = of();
|
||||
}
|
||||
}
|
||||
}
|
||||
clearFilter() {
|
||||
resetBusinessListingCriteria(this.criteria);
|
||||
}
|
||||
close() {
|
||||
this.modalService.reject(this.backupCriteria);
|
||||
}
|
||||
onCheckboxChange(checkbox: string, value: boolean) {
|
||||
// Deaktivieren Sie alle Checkboxes
|
||||
(<BusinessListingCriteria>this.criteria).realEstateChecked = false;
|
||||
(<BusinessListingCriteria>this.criteria).leasedLocation = false;
|
||||
(<BusinessListingCriteria>this.criteria).franchiseResale = false;
|
||||
|
||||
// Aktivieren Sie nur die aktuell ausgewählte Checkbox
|
||||
this.criteria[checkbox] = value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user