Überarbeitung
This commit is contained in:
parent
6d1c50d5df
commit
1e1d5cea57
|
|
@ -60,3 +60,8 @@ pictures_base
|
|||
|
||||
src/*.js
|
||||
bun.lockb
|
||||
|
||||
#drizzle migrations
|
||||
src/drizzle/migrations
|
||||
|
||||
importlog.txt
|
||||
|
|
@ -137,7 +137,7 @@ for (let index = 0; index < usersData.length; index++) {
|
|||
user.companyWebsite = userData.companyWebsite;
|
||||
const [city, state] = userData.companyLocation.split('-').map(e => e.trim());
|
||||
user.companyLocation = {};
|
||||
user.companyLocation.city = city;
|
||||
user.companyLocation.name = city;
|
||||
user.companyLocation.state = state;
|
||||
const cityGeo = geos.states.find(s => s.state_code === state).cities.find(c => c.name === city);
|
||||
user.companyLocation.latitude = cityGeo.latitude;
|
||||
|
|
@ -188,7 +188,7 @@ for (let index = 0; index < commercialJsonData.length; index++) {
|
|||
commercial.location = {};
|
||||
commercial.location.latitude = cityGeo.latitude;
|
||||
commercial.location.longitude = cityGeo.longitude;
|
||||
commercial.location.city = commercialJsonData[index].city;
|
||||
commercial.location.name = commercialJsonData[index].city;
|
||||
commercial.location.state = commercialJsonData[index].state;
|
||||
// console.log(JSON.stringify(commercial.location));
|
||||
} catch (e) {
|
||||
|
|
@ -229,7 +229,7 @@ for (let index = 0; index < businessJsonData.length; index++) {
|
|||
business.location = {};
|
||||
business.location.latitude = cityGeo.latitude;
|
||||
business.location.longitude = cityGeo.longitude;
|
||||
business.location.city = businessJsonData[index].city;
|
||||
business.location.name = businessJsonData[index].city;
|
||||
business.location.state = businessJsonData[index].state;
|
||||
} catch (e) {
|
||||
console.log(`----------------> ERROR ${businessJsonData[index].state} - ${businessJsonData[index].city}`);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { readFileSync } from 'fs';
|
||||
import path, { join } from 'path';
|
||||
import { CountyResult, GeoResult } from 'src/models/main.model.js';
|
||||
import { CityAndStateResult, CountyResult, GeoResult } from 'src/models/main.model.js';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { City, CountyData, Geo, State } from '../models/server.model.js';
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ export class GeoService {
|
|||
if (city.name.toLowerCase().startsWith(prefix.toLowerCase())) {
|
||||
result.push({
|
||||
id: city.id,
|
||||
city: city.name,
|
||||
name: city.name,
|
||||
state: state.state_code,
|
||||
//state_code: state.state_code,
|
||||
latitude: city.latitude,
|
||||
|
|
@ -63,8 +63,8 @@ export class GeoService {
|
|||
});
|
||||
return state ? result.filter(e => e.state.toLowerCase() === state.toLowerCase()) : result;
|
||||
}
|
||||
findCitiesAndStatesStartingWith(prefix: string, state?: string): Array<{ id: string; name: string; type: 'city' | 'state'; state: string }> {
|
||||
const results: Array<{ id: string; name: string; type: 'city' | 'state'; state: string }> = [];
|
||||
findCitiesAndStatesStartingWith(prefix: string, state?: string): Array<CityAndStateResult> {
|
||||
const results: Array<CityAndStateResult> = [];
|
||||
|
||||
const lowercasePrefix = prefix.toLowerCase();
|
||||
|
||||
|
|
@ -73,10 +73,9 @@ export class GeoService {
|
|||
for (const state of this.geo.states) {
|
||||
if (state.name.toLowerCase().startsWith(lowercasePrefix)) {
|
||||
results.push({
|
||||
id: state.id.toString(),
|
||||
name: state.name,
|
||||
id: state.id,
|
||||
type: 'state',
|
||||
state: state.state_code,
|
||||
content: state,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -84,10 +83,9 @@ export class GeoService {
|
|||
for (const city of state.cities) {
|
||||
if (city.name.toLowerCase().startsWith(lowercasePrefix)) {
|
||||
results.push({
|
||||
id: city.id.toString(),
|
||||
name: city.name,
|
||||
id: city.id,
|
||||
type: 'city',
|
||||
state: state.state_code,
|
||||
content: { state: state.state_code, ...city },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -97,7 +95,7 @@ export class GeoService {
|
|||
return results.sort((a, b) => {
|
||||
if (a.type === 'state' && b.type === 'city') return -1;
|
||||
if (a.type === 'city' && b.type === 'state') return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
return a.content.name.localeCompare(b.content.name);
|
||||
});
|
||||
}
|
||||
getCityWithCoords(state: string, city: string): City {
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ export class BusinessListingService {
|
|||
const whereConditions: SQL[] = [];
|
||||
|
||||
if (criteria.city && criteria.searchType === 'exact') {
|
||||
whereConditions.push(ilike(businesses.city, `%${criteria.city}%`));
|
||||
whereConditions.push(ilike(businesses.city, `%${criteria.city.name}%`));
|
||||
}
|
||||
if (criteria.city && criteria.radius && criteria.searchType === 'radius' && criteria.radius) {
|
||||
const cityGeo = this.geoService.getCityWithCoords(criteria.state, criteria.city);
|
||||
const cityGeo = this.geoService.getCityWithCoords(criteria.state, criteria.city.name);
|
||||
whereConditions.push(sql`${getDistanceQuery(businesses, cityGeo.latitude, cityGeo.longitude)} <= ${criteria.radius}`);
|
||||
}
|
||||
if (criteria.types && criteria.types.length > 0) {
|
||||
|
|
@ -180,11 +180,13 @@ export class BusinessListingService {
|
|||
return convertDrizzleBusinessToBusiness(createdListing);
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
const formattedErrors = error.errors.map(err => ({
|
||||
field: err.path.join('.'),
|
||||
message: err.message,
|
||||
}));
|
||||
throw new BadRequestException(formattedErrors);
|
||||
const filteredErrors = error.errors
|
||||
.map(item => ({
|
||||
...item,
|
||||
field: item.path[0],
|
||||
}))
|
||||
.filter((item, index, self) => index === self.findIndex(t => t.path[0] === item.path[0]));
|
||||
throw new BadRequestException(filteredErrors);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
|
@ -200,11 +202,13 @@ export class BusinessListingService {
|
|||
return convertDrizzleBusinessToBusiness(updateListing);
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
const formattedErrors = error.errors.map(err => ({
|
||||
field: err.path.join('.'),
|
||||
message: err.message,
|
||||
}));
|
||||
throw new BadRequestException(formattedErrors);
|
||||
const filteredErrors = error.errors
|
||||
.map(item => ({
|
||||
...item,
|
||||
field: item.path[0],
|
||||
}))
|
||||
.filter((item, index, self) => index === self.findIndex(t => t.path[0] === item.path[0]));
|
||||
throw new BadRequestException(filteredErrors);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ export class CommercialPropertyService {
|
|||
const whereConditions: SQL[] = [];
|
||||
|
||||
if (criteria.city && criteria.searchType === 'exact') {
|
||||
whereConditions.push(ilike(schema.commercials.city, `%${criteria.city}%`));
|
||||
whereConditions.push(ilike(schema.commercials.city, `%${criteria.city.name}%`));
|
||||
}
|
||||
if (criteria.city && criteria.radius && criteria.searchType === 'radius' && criteria.radius) {
|
||||
const cityGeo = this.geoService.getCityWithCoords(criteria.state, criteria.city);
|
||||
const cityGeo = this.geoService.getCityWithCoords(criteria.state, criteria.city.name);
|
||||
whereConditions.push(sql`${getDistanceQuery(commercials, cityGeo.latitude, cityGeo.longitude)} <= ${criteria.radius}`);
|
||||
}
|
||||
if (criteria.types && criteria.types.length > 0) {
|
||||
|
|
@ -131,11 +131,13 @@ export class CommercialPropertyService {
|
|||
return convertDrizzleCommercialToCommercial(createdListing);
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
const formattedErrors = error.errors.map(err => ({
|
||||
field: err.path.join('.'),
|
||||
message: err.message,
|
||||
}));
|
||||
throw new BadRequestException(formattedErrors);
|
||||
const filteredErrors = error.errors
|
||||
.map(item => ({
|
||||
...item,
|
||||
field: item.path[0],
|
||||
}))
|
||||
.filter((item, index, self) => index === self.findIndex(t => t.path[0] === item.path[0]));
|
||||
throw new BadRequestException(filteredErrors);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
|
@ -157,11 +159,13 @@ export class CommercialPropertyService {
|
|||
return convertDrizzleCommercialToCommercial(updateListing);
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
const formattedErrors = error.errors.map(err => ({
|
||||
field: err.path.join('.'),
|
||||
message: err.message,
|
||||
}));
|
||||
throw new BadRequestException(formattedErrors);
|
||||
const filteredErrors = error.errors
|
||||
.map(item => ({
|
||||
...item,
|
||||
field: item.path[0],
|
||||
}))
|
||||
.filter((item, index, self) => index === self.findIndex(t => t.path[0] === item.path[0]));
|
||||
throw new BadRequestException(filteredErrors);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ export const LicensedInSchema = z.object({
|
|||
state: z.string().nonempty('State is required'),
|
||||
});
|
||||
export const GeoSchema = z.object({
|
||||
city: z.string(),
|
||||
name: z.string(),
|
||||
state: z.string().refine(val => USStates.safeParse(val).success, {
|
||||
message: 'Invalid state. Must be a valid 2-letter US state code.',
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { BusinessListing, CommercialPropertyListing, Sender, User } from './db.model.js';
|
||||
import { State } from './server.model.js';
|
||||
|
||||
export interface StatesResult {
|
||||
state: string;
|
||||
|
|
@ -59,7 +60,7 @@ export interface ListCriteria {
|
|||
page: number;
|
||||
types: string[];
|
||||
state: string;
|
||||
city: string;
|
||||
city: GeoResult;
|
||||
prompt: string;
|
||||
searchType: 'exact' | 'radius';
|
||||
// radius: '5' | '20' | '50' | '100' | '200' | '300' | '400' | '500';
|
||||
|
|
@ -224,18 +225,23 @@ export interface UploadParams {
|
|||
}
|
||||
export interface GeoResult {
|
||||
id: number;
|
||||
city: string;
|
||||
name: string;
|
||||
state: string;
|
||||
// state_code: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
}
|
||||
export interface CityAndStateResult {
|
||||
interface CityResult {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
state: string;
|
||||
type: 'city';
|
||||
content: GeoResult;
|
||||
}
|
||||
|
||||
interface StateResult {
|
||||
id: number;
|
||||
type: 'state';
|
||||
content: State;
|
||||
}
|
||||
export type CityAndStateResult = CityResult | StateResult;
|
||||
export interface CountyResult {
|
||||
id: number;
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ export class UserService {
|
|||
const whereConditions: SQL[] = [];
|
||||
whereConditions.push(eq(schema.users.customerType, 'professional'));
|
||||
if (criteria.city && criteria.searchType === 'exact') {
|
||||
whereConditions.push(ilike(schema.users.city, `%${criteria.city}%`));
|
||||
whereConditions.push(ilike(schema.users.city, `%${criteria.city.name}%`));
|
||||
}
|
||||
if (criteria.city && criteria.radius && criteria.searchType === 'radius' && criteria.radius) {
|
||||
const cityGeo = this.geoService.getCityWithCoords(criteria.state, criteria.city);
|
||||
const cityGeo = this.geoService.getCityWithCoords(criteria.state, criteria.city.name);
|
||||
whereConditions.push(sql`${getDistanceQuery(schema.users, cityGeo.latitude, cityGeo.longitude)} <= ${criteria.radius}`);
|
||||
}
|
||||
if (criteria.types && criteria.types.length > 0) {
|
||||
|
|
@ -139,11 +139,13 @@ export class UserService {
|
|||
}
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
const formattedErrors = error.errors.map(err => ({
|
||||
field: err.path.join('.'),
|
||||
message: err.message,
|
||||
}));
|
||||
throw new BadRequestException(formattedErrors);
|
||||
const filteredErrors = error.errors
|
||||
.map(item => ({
|
||||
...item,
|
||||
field: item.path[0],
|
||||
}))
|
||||
.filter((item, index, self) => index === self.findIndex(t => t.path[0] === item.path[0]));
|
||||
throw new BadRequestException(filteredErrors);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,11 +33,14 @@ type DrizzleUser = typeof users.$inferSelect;
|
|||
type DrizzleBusinessListing = typeof businesses.$inferSelect;
|
||||
type DrizzleCommercialPropertyListing = typeof commercials.$inferSelect;
|
||||
export function convertBusinessToDrizzleBusiness(businessListing: Partial<BusinessListing>): DrizzleBusinessListing {
|
||||
return flattenObject(businessListing);
|
||||
const drizzleBusinessListing = flattenObject(businessListing);
|
||||
drizzleBusinessListing.city = drizzleBusinessListing.name;
|
||||
delete drizzleBusinessListing.name;
|
||||
return drizzleBusinessListing;
|
||||
}
|
||||
export function convertDrizzleBusinessToBusiness(drizzleBusinessListing: Partial<DrizzleBusinessListing>): BusinessListing {
|
||||
const o = {
|
||||
location_city: drizzleBusinessListing.city,
|
||||
location_name: drizzleBusinessListing.city,
|
||||
location_state: drizzleBusinessListing.state,
|
||||
location_latitude: drizzleBusinessListing.latitude,
|
||||
location_longitude: drizzleBusinessListing.longitude,
|
||||
|
|
@ -50,11 +53,14 @@ export function convertDrizzleBusinessToBusiness(drizzleBusinessListing: Partial
|
|||
return unflattenObject(o);
|
||||
}
|
||||
export function convertCommercialToDrizzleCommercial(commercialPropertyListing: Partial<CommercialPropertyListing>): DrizzleCommercialPropertyListing {
|
||||
return flattenObject(commercialPropertyListing);
|
||||
const drizzleCommercialPropertyListing = flattenObject(commercialPropertyListing);
|
||||
drizzleCommercialPropertyListing.city = drizzleCommercialPropertyListing.name;
|
||||
delete drizzleCommercialPropertyListing.name;
|
||||
return drizzleCommercialPropertyListing;
|
||||
}
|
||||
export function convertDrizzleCommercialToCommercial(drizzleCommercialPropertyListing: Partial<DrizzleCommercialPropertyListing>): CommercialPropertyListing {
|
||||
const o = {
|
||||
location_city: drizzleCommercialPropertyListing.city,
|
||||
location_name: drizzleCommercialPropertyListing.city,
|
||||
location_state: drizzleCommercialPropertyListing.state,
|
||||
location_latitude: drizzleCommercialPropertyListing.latitude,
|
||||
location_longitude: drizzleCommercialPropertyListing.longitude,
|
||||
|
|
@ -67,12 +73,15 @@ export function convertDrizzleCommercialToCommercial(drizzleCommercialPropertyLi
|
|||
return unflattenObject(o);
|
||||
}
|
||||
export function convertUserToDrizzleUser(user: Partial<User>): DrizzleUser {
|
||||
return flattenObject(user);
|
||||
const drizzleUser = flattenObject(user);
|
||||
drizzleUser.city = drizzleUser.name;
|
||||
delete drizzleUser.name;
|
||||
return drizzleUser;
|
||||
}
|
||||
|
||||
export function convertDrizzleUserToUser(drizzleUser: Partial<DrizzleUser>): User {
|
||||
const o = {
|
||||
companyLocation_city: drizzleUser.city,
|
||||
companyLocation_name: drizzleUser.city,
|
||||
companyLocation_state: drizzleUser.state,
|
||||
companyLocation_latitude: drizzleUser.latitude,
|
||||
companyLocation_longitude: drizzleUser.longitude,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@
|
|||
<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)="modalService.reject()" 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" />
|
||||
|
|
@ -20,10 +26,12 @@
|
|||
<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>
|
||||
<label for="city" class="block mb-2 text-sm font-medium text-gray-900">Location - City</label>
|
||||
|
||||
<ng-select
|
||||
|
|
@ -42,7 +50,7 @@
|
|||
<ng-option [value]="city">{{ city.city }} - {{ city.state }}</ng-option>
|
||||
}
|
||||
</ng-select>
|
||||
</div>
|
||||
</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>
|
||||
|
|
@ -241,7 +249,7 @@
|
|||
<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>
|
||||
<!-- <label for="city" class="block mb-2 text-sm font-medium text-gray-900">Location - City</label>
|
||||
<ng-select
|
||||
class="custom"
|
||||
[multiple]="false"
|
||||
|
|
@ -257,7 +265,8 @@
|
|||
@for (city of cities$ | async; track city.id) {
|
||||
<ng-option [value]="city">{{ city.city }} - {{ selectOptions.getStateInitials(city.state) }}</ng-option>
|
||||
}
|
||||
</ng-select>
|
||||
</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">
|
||||
|
|
@ -363,13 +372,10 @@
|
|||
[typeahead]="countyInput$"
|
||||
[(ngModel)]="criteria.counties"
|
||||
>
|
||||
<!-- @for (county of counties$ | async; track county.id) {
|
||||
<ng-option [value]="city.city">{{ city.city }} - {{ selectOptions.getStateInitials(city.state) }}</ng-option>
|
||||
} -->
|
||||
</ng-select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="city" class="block mb-2 text-sm font-medium text-gray-900">Location - City</label>
|
||||
<!-- <label for="city" class="block mb-2 text-sm font-medium text-gray-900">Location - City</label>
|
||||
<ng-select
|
||||
class="custom"
|
||||
[multiple]="false"
|
||||
|
|
@ -383,9 +389,10 @@
|
|||
(ngModelChange)="setCity($event)"
|
||||
>
|
||||
@for (city of cities$ | async; track city.id) {
|
||||
<ng-option [value]="city">{{ city.city }} - {{ selectOptions.getStateInitials(city.state) }}</ng-option>
|
||||
<ng-option [value]="city">{{ city.name }} - {{ selectOptions.getStateInitials(city.state) }}</ng-option>
|
||||
}
|
||||
</ng-select>
|
||||
</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">
|
||||
|
|
|
|||
|
|
@ -10,12 +10,13 @@ import { ListingsService } from '../../services/listings.service';
|
|||
import { SelectOptionsService } from '../../services/select-options.service';
|
||||
import { UserService } from '../../services/user.service';
|
||||
import { SharedModule } from '../../shared/shared/shared.module';
|
||||
import { ValidatedCityComponent } from '../validated-city/validated-city.component';
|
||||
import { ModalService } from './modal.service';
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
selector: 'app-search-modal',
|
||||
standalone: true,
|
||||
imports: [SharedModule, AsyncPipe, NgIf, NgSelectModule],
|
||||
imports: [SharedModule, AsyncPipe, NgIf, NgSelectModule, ValidatedCityComponent],
|
||||
templateUrl: './search-modal.component.html',
|
||||
styleUrl: './search-modal.component.scss',
|
||||
})
|
||||
|
|
@ -98,7 +99,7 @@ export class SearchModalComponent {
|
|||
}
|
||||
setCity(city) {
|
||||
if (city) {
|
||||
this.criteria.city = city.city;
|
||||
this.criteria.city = city;
|
||||
this.criteria.state = city.state;
|
||||
} else {
|
||||
this.criteria.city = null;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div>
|
||||
<label for="type" class="block text-sm font-bold text-gray-700 mb-1 relative w-fit"
|
||||
<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 }}"
|
||||
|
|
@ -19,11 +19,11 @@
|
|||
[loading]="cityLoading"
|
||||
typeToSearchText="Please enter 2 or more characters"
|
||||
[typeahead]="cityInput$"
|
||||
ngModel="{{ value?.city }} {{ value ? '-' : '' }} {{ value?.state }}"
|
||||
ngModel="{{ value?.name }} {{ value ? '-' : '' }} {{ value?.state }}"
|
||||
(ngModelChange)="onInputChange($event)"
|
||||
>
|
||||
@for (city of cities$ | async; track city.id) {
|
||||
<ng-option [value]="city">{{ city.city }} - {{ city.state }}</ng-option>
|
||||
<ng-option [value]="city">{{ city.name }} - {{ city.state }}</ng-option>
|
||||
}
|
||||
</ng-select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
: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;
|
||||
// height: 42px;
|
||||
border-radius: 0.5rem;
|
||||
.ng-value-container .ng-input {
|
||||
top: 10px;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ import { ValidationMessagesService } from '../validation-messages.service';
|
|||
})
|
||||
export class ValidatedCityComponent extends BaseInputComponent {
|
||||
@Input() items;
|
||||
@Input() labelClasses: string;
|
||||
@Input() state: string;
|
||||
cities$: Observable<GeoResult[]>;
|
||||
cityInput$ = new Subject<string>();
|
||||
countyInput$ = new Subject<string>();
|
||||
|
|
@ -50,7 +52,7 @@ export class ValidatedCityComponent extends BaseInputComponent {
|
|||
distinctUntilChanged(),
|
||||
tap(() => (this.cityLoading = true)),
|
||||
switchMap(term =>
|
||||
this.geoService.findCitiesStartingWith(term).pipe(
|
||||
this.geoService.findCitiesStartingWith(term, this.state).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)),
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ export class DetailsBusinessListingComponent {
|
|||
}
|
||||
return [
|
||||
{ label: 'Category', value: this.selectOptions.getBusiness(this.listing.type) },
|
||||
{ label: 'Located in', value: `${this.listing.location.city}, ${this.selectOptions.getState(this.listing.location.state)}` },
|
||||
{ label: 'Located in', value: `${this.listing.location.name}, ${this.selectOptions.getState(this.listing.location.state)}` },
|
||||
{ label: 'Asking Price', value: `$${this.listing.price?.toLocaleString()}` },
|
||||
{ label: 'Sales revenue', value: `$${this.listing.salesRevenue?.toLocaleString()}` },
|
||||
{ label: 'Cash flow', value: `$${this.listing.cashFlow?.toLocaleString()}` },
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ export class DetailsCommercialPropertyListingComponent {
|
|||
this.propertyDetails = [
|
||||
{ label: 'Property Category', value: this.selectOptions.getCommercialProperty(this.listing.type) },
|
||||
{ label: 'Located in', value: this.selectOptions.getState(this.listing.location.state) },
|
||||
{ label: 'City', value: this.listing.location.city },
|
||||
{ label: 'City', value: this.listing.location.name },
|
||||
{ label: 'Asking Price:', value: `$${this.listing.price?.toLocaleString()}` },
|
||||
];
|
||||
//this.initFlowbite();
|
||||
|
|
|
|||
|
|
@ -199,7 +199,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.city }} - {{ user.companyLocation.state }}</span>
|
||||
<span class="p-2 flex-grow">{{ user.companyLocation.name }} - {{ user.companyLocation.state }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -113,8 +113,8 @@
|
|||
placeholder="Enter City or State ..."
|
||||
groupBy="type"
|
||||
>
|
||||
@for (city of cities$ | async; track city.id) {
|
||||
<ng-option [value]="city">{{ city.name }} - {{ city.state }}</ng-option>
|
||||
@for (city of cities$ | async; track city.id) { @let state = city.type==='city'?city.content.state:''; @let separator = city.type==='city'?' - ':'';
|
||||
<ng-option [value]="city">{{ city.content.name }}{{ separator }}{{ state }}</ng-option>
|
||||
}
|
||||
</ng-select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ export class HomeComponent {
|
|||
}
|
||||
async changeTab(tabname: 'business' | 'commercialProperty' | 'broker') {
|
||||
this.activeTabAction = tabname;
|
||||
this.cityOrState = null;
|
||||
if ('business' === tabname) {
|
||||
this.criteria = createEnhancedProxy(getCriteriaStateObject('businessListings'), this);
|
||||
} else if ('commercialProperty' === tabname) {
|
||||
|
|
@ -74,22 +75,7 @@ export class HomeComponent {
|
|||
this.criteria = undefined;
|
||||
}
|
||||
}
|
||||
// private createEnhancedProxy(obj: any) {
|
||||
// const component = this;
|
||||
|
||||
// const sessionStorageHandler = function (path, value, previous, applyData) {
|
||||
// let criteriaType = this.criteriaType;
|
||||
// sessionStorage.setItem(`${criteriaType}_criteria`, JSON.stringify(this));
|
||||
// };
|
||||
|
||||
// return onChange(obj, function (path, value, previous, applyData) {
|
||||
// // Call the original sessionStorageHandler
|
||||
// sessionStorageHandler.call(this, path, value, previous, applyData);
|
||||
|
||||
// // Notify about the criteria change using the component's context
|
||||
// component.criteriaChangeService.notifyCriteriaChange();
|
||||
// });
|
||||
// }
|
||||
search() {
|
||||
this.router.navigate([`${this.activeTabAction}Listings`]);
|
||||
}
|
||||
|
|
@ -126,7 +112,6 @@ export class HomeComponent {
|
|||
async openModal() {
|
||||
const accepted = await this.modalService.showModal(this.criteria);
|
||||
if (accepted) {
|
||||
//this.searchService.search(this.criteria);
|
||||
this.router.navigate([`${this.activeTabAction}Listings`]);
|
||||
}
|
||||
}
|
||||
|
|
@ -153,10 +138,10 @@ export class HomeComponent {
|
|||
setCityOrState(cityOrState: CityAndStateResult) {
|
||||
if (cityOrState) {
|
||||
if (cityOrState.type === 'state') {
|
||||
this.criteria.state = cityOrState.state;
|
||||
this.criteria.state = cityOrState.content.state_code;
|
||||
} else {
|
||||
this.criteria.city = cityOrState.name;
|
||||
this.criteria.state = cityOrState.state;
|
||||
this.criteria.city = cityOrState.content as GeoResult;
|
||||
this.criteria.state = cityOrState.content.state;
|
||||
this.criteria.searchType = 'radius';
|
||||
this.criteria.radius = 20;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@
|
|||
<p class="text-sm text-gray-600 mb-1">Asking price: {{ listing.price | currency }}</p>
|
||||
<p class="text-sm text-gray-600 mb-1">Sales revenue: {{ listing.salesRevenue | currency }}</p>
|
||||
<p class="text-sm text-gray-600 mb-1">Net profit: {{ listing.cashFlow | currency }}</p>
|
||||
<p class="text-sm text-gray-600 mb-1">Location: {{ listing.location.city }} - {{ listing.location.state }}</p>
|
||||
<p class="text-sm text-gray-600 mb-1">Location: {{ listing.location.name }} - {{ listing.location.state }}</p>
|
||||
<p class="text-sm text-gray-600 mb-1">Established: {{ listing.established }}</p>
|
||||
<img src="{{ env.imageBaseUrl }}/pictures/logo/{{ listing.imageName }}.avif?_ts={{ ts }}" alt="Company logo" class="absolute bottom-[70px] right-[30px] h-[35px] w-auto" />
|
||||
<div class="flex-grow"></div>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
<span class="text-gray-600 text-sm"><i [class]="selectOptions.getIconTypeOfCommercials(listing.type)" class="mr-1"></i> {{ selectOptions.getCommercialProperty(listing.type) }}</span>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold mb-2">{{ listing.title }}</h3>
|
||||
<p class="text-gray-600 mb-2">{{ listing.location.city }}</p>
|
||||
<p class="text-gray-600 mb-2">{{ listing.location.name }}</p>
|
||||
<p class="text-xl font-bold mb-4">{{ listing.price | currency }}</p>
|
||||
<button [routerLink]="['/details-commercial-property-listing', listing.id]" class="bg-green-500 text-white px-4 py-2 rounded-full w-full hover:bg-green-600 transition duration-300">
|
||||
View Full Listing <i class="fas fa-arrow-right ml-1"></i>
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@
|
|||
<input type="text" id="description" name="description" [(ngModel)]="user.description" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" />
|
||||
</div> -->
|
||||
<app-validated-input label="Company Name" name="companyName" [(ngModel)]="user.companyName"></app-validated-input>
|
||||
<app-validated-input label="Describe yourself" name="description" [(ngModel)]="user.description"></app-validated-input>
|
||||
<app-validated-input label="Describe Yourself" name="description" [(ngModel)]="user.description"></app-validated-input>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ export class AccountComponent {
|
|||
|
||||
async search(event: AutoCompleteCompleteEvent) {
|
||||
const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query));
|
||||
this.suggestions = result.map(r => `${r.city} - ${r.state}`).slice(0, 5);
|
||||
this.suggestions = result.map(r => `${r.name} - ${r.state}`).slice(0, 5);
|
||||
}
|
||||
addLicence() {
|
||||
this.user.licensedIn.push({ registerNo: '', state: '' });
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ export class EditBusinessListingComponent {
|
|||
|
||||
async search(event: AutoCompleteCompleteEvent) {
|
||||
const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query));
|
||||
this.suggestions = result.map(r => r.city).slice(0, 5);
|
||||
this.suggestions = result.map(r => r.name).slice(0, 5);
|
||||
}
|
||||
|
||||
changeListingCategory(value: 'business' | 'commercialProperty') {
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ export class EditCommercialPropertyListingComponent {
|
|||
|
||||
async search(event: AutoCompleteCompleteEvent) {
|
||||
const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query));
|
||||
this.suggestions = result.map(r => r.city).slice(0, 5);
|
||||
this.suggestions = result.map(r => r.name).slice(0, 5);
|
||||
}
|
||||
openFileDialog() {
|
||||
this.fileInput.nativeElement.click();
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
<div *ngFor="let listing of myListings" class="bg-white shadow-md rounded-lg p-4 mb-4">
|
||||
<h2 class="text-xl font-semibold mb-2">{{ listing.title }}</h2>
|
||||
<p class="text-gray-600 mb-2">Category: {{ listing.listingsCategory === 'commercialProperty' ? 'Commercial Property' : 'Business' }}</p>
|
||||
<p class="text-gray-600 mb-4">Located in: {{ listing.location.city }} - {{ listing.location.state }}</p>
|
||||
<p class="text-gray-600 mb-4">Located in: {{ listing.location.name }} - {{ listing.location.state }}</p>
|
||||
<div class="flex justify-end">
|
||||
<button class="bg-green-500 text-white p-2 rounded-full mr-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
|
|
|
|||
|
|
@ -93,8 +93,8 @@ export function createEmptyBusinessListingCriteria(): BusinessListingCriteria {
|
|||
start: 0,
|
||||
length: 0,
|
||||
page: 0,
|
||||
state: '',
|
||||
city: '',
|
||||
state: null,
|
||||
city: null,
|
||||
types: [],
|
||||
prompt: '',
|
||||
criteriaType: 'businessListings',
|
||||
|
|
@ -123,8 +123,8 @@ export function createEmptyCommercialPropertyListingCriteria(): CommercialProper
|
|||
start: 0,
|
||||
length: 0,
|
||||
page: 0,
|
||||
state: '',
|
||||
city: '',
|
||||
state: null,
|
||||
city: null,
|
||||
types: [],
|
||||
prompt: '',
|
||||
criteriaType: 'commercialPropertyListings',
|
||||
|
|
@ -141,7 +141,7 @@ export function createEmptyUserListingCriteria(): UserListingCriteria {
|
|||
start: 0,
|
||||
length: 0,
|
||||
page: 0,
|
||||
city: '',
|
||||
city: null,
|
||||
types: [],
|
||||
prompt: '',
|
||||
criteriaType: 'brokerListings',
|
||||
|
|
|
|||
Loading…
Reference in New Issue