counties, pagination, filter count, show total results
This commit is contained in:
parent
abcde3991d
commit
9db23c2177
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,18 +1,23 @@
|
||||||
import { Controller, Get, Param } from '@nestjs/common';
|
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
|
||||||
|
import { CountyRequest } from 'src/models/server.model.js';
|
||||||
import { GeoService } from './geo.service.js';
|
import { GeoService } from './geo.service.js';
|
||||||
|
|
||||||
@Controller('geo')
|
@Controller('geo')
|
||||||
export class GeoController {
|
export class GeoController {
|
||||||
constructor(private geoService:GeoService){}
|
constructor(private geoService: GeoService) {}
|
||||||
|
|
||||||
@Get(':prefix')
|
@Get(':prefix')
|
||||||
findByPrefix(@Param('prefix') prefix:string): any {
|
findByPrefix(@Param('prefix') prefix: string): any {
|
||||||
return this.geoService.findCitiesStartingWith(prefix);
|
return this.geoService.findCitiesStartingWith(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':prefix/:state')
|
@Get(':prefix/:state')
|
||||||
findByPrefixAndState(@Param('prefix') prefix:string,@Param('state') state:string): any {
|
findByPrefixAndState(@Param('prefix') prefix: string, @Param('state') state: string): any {
|
||||||
return this.geoService.findCitiesStartingWith(prefix,state);
|
return this.geoService.findCitiesStartingWith(prefix, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('counties')
|
||||||
|
findByPrefixAndStates(@Body() countyRequest: CountyRequest): any {
|
||||||
|
return this.geoService.findCountiesStartingWith(countyRequest.prefix, countyRequest.states);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import path, { join } from 'path';
|
import path, { join } from 'path';
|
||||||
import { GeoResult } from 'src/models/main.model.js';
|
import { CountyResult, GeoResult } from 'src/models/main.model.js';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { City, Geo, State } from '../models/server.model.js';
|
import { City, CountyData, Geo, State } from '../models/server.model.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
@ -11,6 +11,7 @@ const __dirname = path.dirname(__filename);
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GeoService {
|
export class GeoService {
|
||||||
geo: Geo;
|
geo: Geo;
|
||||||
|
counties: CountyData[];
|
||||||
constructor() {
|
constructor() {
|
||||||
this.loadGeo();
|
this.loadGeo();
|
||||||
}
|
}
|
||||||
|
|
@ -18,9 +19,32 @@ export class GeoService {
|
||||||
const filePath = join(__dirname, '../..', 'assets', 'geo.json');
|
const filePath = join(__dirname, '../..', 'assets', 'geo.json');
|
||||||
const rawData = readFileSync(filePath, 'utf8');
|
const rawData = readFileSync(filePath, 'utf8');
|
||||||
this.geo = JSON.parse(rawData);
|
this.geo = JSON.parse(rawData);
|
||||||
|
const countiesFilePath = join(__dirname, '../..', 'assets', 'counties.json');
|
||||||
|
const rawCountiesData = readFileSync(countiesFilePath, 'utf8');
|
||||||
|
this.counties = JSON.parse(rawCountiesData);
|
||||||
}
|
}
|
||||||
|
findCountiesStartingWith(prefix: string, states?: string[]) {
|
||||||
|
let results: CountyResult[] = [];
|
||||||
|
let idCounter = 1;
|
||||||
|
|
||||||
findCitiesStartingWith(prefix: string, state?: string): { city: string; state: string; state_code: string }[] {
|
this.counties.forEach(stateData => {
|
||||||
|
if (!states || states.includes(stateData.state)) {
|
||||||
|
stateData.counties.forEach(county => {
|
||||||
|
if (county.startsWith(prefix.toUpperCase())) {
|
||||||
|
results.push({
|
||||||
|
id: idCounter++,
|
||||||
|
name: county,
|
||||||
|
state: stateData.state_full,
|
||||||
|
state_code: stateData.state,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
findCitiesStartingWith(prefix: string, state?: string): GeoResult[] {
|
||||||
const result: GeoResult[] = [];
|
const result: GeoResult[] = [];
|
||||||
|
|
||||||
this.geo.states.forEach((state: State) => {
|
this.geo.states.forEach((state: State) => {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,10 @@ export class BusinessListingsController {
|
||||||
find(@Request() req, @Body() criteria: BusinessListingCriteria): any {
|
find(@Request() req, @Body() criteria: BusinessListingCriteria): any {
|
||||||
return this.listingsService.searchBusinessListings(criteria, req.user as JwtUser);
|
return this.listingsService.searchBusinessListings(criteria, req.user as JwtUser);
|
||||||
}
|
}
|
||||||
|
@Post('findTotal')
|
||||||
|
findTotal(@Body() criteria: BusinessListingCriteria): Promise<number> {
|
||||||
|
return this.listingsService.getBusinessListingsCount(criteria);
|
||||||
|
}
|
||||||
// @UseGuards(OptionalJwtAuthGuard)
|
// @UseGuards(OptionalJwtAuthGuard)
|
||||||
// @Post('search')
|
// @Post('search')
|
||||||
// search(@Request() req, @Body() criteria: BusinessListingCriteria): any {
|
// search(@Request() req, @Body() criteria: BusinessListingCriteria): any {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,10 @@ export class CommercialPropertyListingsController {
|
||||||
async find(@Request() req, @Body() criteria: CommercialPropertyListingCriteria): Promise<any> {
|
async find(@Request() req, @Body() criteria: CommercialPropertyListingCriteria): Promise<any> {
|
||||||
return await this.listingsService.searchCommercialProperties(criteria, req.user as JwtUser);
|
return await this.listingsService.searchCommercialProperties(criteria, req.user as JwtUser);
|
||||||
}
|
}
|
||||||
|
@Post('findTotal')
|
||||||
|
findTotal(@Body() criteria: CommercialPropertyListingCriteria): Promise<number> {
|
||||||
|
return this.listingsService.getCommercialPropertiesCount(criteria);
|
||||||
|
}
|
||||||
@Get('states/all')
|
@Get('states/all')
|
||||||
getStates(): any {
|
getStates(): any {
|
||||||
return this.listingsService.getStates();
|
return this.listingsService.getStates();
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ export interface ListCriteria {
|
||||||
start: number;
|
start: number;
|
||||||
length: number;
|
length: number;
|
||||||
page: number;
|
page: number;
|
||||||
pageCount: number;
|
|
||||||
types: string[];
|
types: string[];
|
||||||
city: string;
|
city: string;
|
||||||
prompt: string;
|
prompt: string;
|
||||||
|
|
@ -230,6 +229,12 @@ export interface GeoResult {
|
||||||
state: string;
|
state: string;
|
||||||
state_code: string;
|
state_code: string;
|
||||||
}
|
}
|
||||||
|
export interface CountyResult {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
state: string;
|
||||||
|
state_code: string;
|
||||||
|
}
|
||||||
export function isEmpty(value: any): boolean {
|
export function isEmpty(value: any): boolean {
|
||||||
// Check for undefined or null
|
// Check for undefined or null
|
||||||
if (value === undefined || value === null) {
|
if (value === undefined || value === null) {
|
||||||
|
|
|
||||||
|
|
@ -61,3 +61,12 @@ export interface Timezone {
|
||||||
abbreviation: string;
|
abbreviation: string;
|
||||||
tzName: string;
|
tzName: string;
|
||||||
}
|
}
|
||||||
|
export interface CountyData {
|
||||||
|
state: string;
|
||||||
|
state_full: string;
|
||||||
|
counties: string[];
|
||||||
|
}
|
||||||
|
export interface CountyRequest {
|
||||||
|
prefix: string;
|
||||||
|
states: string[];
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,10 @@ export class UserController {
|
||||||
this.logger.info(`Found users: ${JSON.stringify(foundUsers)}`);
|
this.logger.info(`Found users: ${JSON.stringify(foundUsers)}`);
|
||||||
return foundUsers;
|
return foundUsers;
|
||||||
}
|
}
|
||||||
|
@Post('findTotal')
|
||||||
|
findTotal(@Body() criteria: UserListingCriteria): Promise<number> {
|
||||||
|
return this.userService.getUserListingsCount(criteria);
|
||||||
|
}
|
||||||
@Get('states/all')
|
@Get('states/all')
|
||||||
async getStates(): Promise<any[]> {
|
async getStates(): Promise<any[]> {
|
||||||
this.logger.info(`Getting all states for users`);
|
this.logger.info(`Getting all states for users`);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as readline from 'readline';
|
||||||
|
|
||||||
|
interface CityData {
|
||||||
|
city: string;
|
||||||
|
stateShort: string;
|
||||||
|
stateFull: string;
|
||||||
|
county: string;
|
||||||
|
cityAlias: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateCountyData {
|
||||||
|
state: string;
|
||||||
|
state_full: string;
|
||||||
|
counties: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function parseData(filePath: string): Promise<CityData[]> {
|
||||||
|
const fileStream = fs.createReadStream(filePath);
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: fileStream,
|
||||||
|
crlfDelay: Infinity,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data: CityData[] = [];
|
||||||
|
let isFirstLine = true;
|
||||||
|
|
||||||
|
for await (const line of rl) {
|
||||||
|
if (isFirstLine) {
|
||||||
|
isFirstLine = false;
|
||||||
|
continue; // Skip the first line
|
||||||
|
}
|
||||||
|
const [city, stateShort, stateFull, county, cityAlias] = line.split('|');
|
||||||
|
data.push({ city, stateShort, stateFull, county, cityAlias });
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformData(data: CityData[]): StateCountyData[] {
|
||||||
|
const stateMap: { [key: string]: { stateFull: string; counties: Set<string> } } = {};
|
||||||
|
|
||||||
|
data.forEach(item => {
|
||||||
|
if (!stateMap[item.stateShort]) {
|
||||||
|
stateMap[item.stateShort] = {
|
||||||
|
stateFull: item.stateFull,
|
||||||
|
counties: new Set(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
stateMap[item.stateShort].counties.add(item.county);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.entries(stateMap).map(([state, value]) => ({
|
||||||
|
state,
|
||||||
|
state_full: value.stateFull,
|
||||||
|
counties: Array.from(value.counties).sort(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const filePath = './src/assets/counties_raw.csv'; // Ersetze diesen Pfad mit dem Pfad zu deiner Datei
|
||||||
|
const cityData = await parseData(filePath);
|
||||||
|
const stateCountyData = transformData(cityData);
|
||||||
|
|
||||||
|
console.log(JSON.stringify(stateCountyData, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => console.error(err));
|
||||||
|
|
@ -125,7 +125,7 @@
|
||||||
id="filterDropdownButton"
|
id="filterDropdownButton"
|
||||||
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 md:me-2"
|
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 md:me-2"
|
||||||
>
|
>
|
||||||
<i class="fas fa-filter mr-2"></i>Filter (1)
|
<i class="fas fa-filter mr-2"></i>Filter ({{ getNumberOfFiltersSet() }})
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
<button
|
<button
|
||||||
|
|
@ -242,7 +242,7 @@
|
||||||
id="filterDropdownMobileButton"
|
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"
|
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 (1)
|
<i class="fas fa-filter mr-2"></i>Filter ({{ getNumberOfFiltersSet() }})
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,11 @@ import { filter, Observable, Subject, Subscription } from 'rxjs';
|
||||||
import { User } from '../../../../../bizmatch-server/src/models/db.model';
|
import { User } from '../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { BusinessListingCriteria, CommercialPropertyListingCriteria, emailToDirName, KeycloakUser, UserListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
|
import { BusinessListingCriteria, CommercialPropertyListingCriteria, emailToDirName, KeycloakUser, UserListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
|
import { CriteriaChangeService } from '../../services/criteria-change.service';
|
||||||
import { SearchService } from '../../services/search.service';
|
import { SearchService } from '../../services/search.service';
|
||||||
import { SharedService } from '../../services/shared.service';
|
import { SharedService } from '../../services/shared.service';
|
||||||
import { UserService } from '../../services/user.service';
|
import { UserService } from '../../services/user.service';
|
||||||
import { getCriteriaStateObject, getSessionStorageHandlerWrapper, map2User } from '../../utils/utils';
|
import { compareObjects, createEmptyBusinessListingCriteria, createEmptyCommercialPropertyListingCriteria, createEmptyUserListingCriteria, getCriteriaStateObject, map2User } from '../../utils/utils';
|
||||||
import { DropdownComponent } from '../dropdown/dropdown.component';
|
import { DropdownComponent } from '../dropdown/dropdown.component';
|
||||||
import { ModalService } from '../search-modal/modal.service';
|
import { ModalService } from '../search-modal/modal.service';
|
||||||
@Component({
|
@Component({
|
||||||
|
|
@ -40,6 +41,7 @@ export class HeaderComponent {
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria;
|
criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria;
|
||||||
private routerSubscription: Subscription | undefined;
|
private routerSubscription: Subscription | undefined;
|
||||||
|
baseRoute: string;
|
||||||
constructor(
|
constructor(
|
||||||
public keycloakService: KeycloakService,
|
public keycloakService: KeycloakService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
|
@ -48,6 +50,7 @@ export class HeaderComponent {
|
||||||
private breakpointObserver: BreakpointObserver,
|
private breakpointObserver: BreakpointObserver,
|
||||||
private modalService: ModalService,
|
private modalService: ModalService,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
|
private criteriaChangeService: CriteriaChangeService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|
@ -75,18 +78,46 @@ export class HeaderComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private checkCurrentRoute(url: string): void {
|
private checkCurrentRoute(url: string): void {
|
||||||
const baseRoute = url.split('/')[1]; // Nimmt den ersten Teil der Route nach dem ersten '/'
|
this.baseRoute = url.split('/')[1]; // Nimmt den ersten Teil der Route nach dem ersten '/'
|
||||||
const specialRoutes = [, '', ''];
|
const specialRoutes = [, '', ''];
|
||||||
if ('businessListings' === baseRoute) {
|
if ('businessListings' === this.baseRoute) {
|
||||||
this.criteria = onChange(getCriteriaStateObject('business'), getSessionStorageHandlerWrapper('business'));
|
//this.criteria = onChange(getCriteriaStateObject('business'), getSessionStorageHandlerWrapper('business'));
|
||||||
} else if ('commercialPropertyListings' === baseRoute) {
|
//this.criteria = onChange(getCriteriaStateObject('business'), this.getSessionStorageHandler);
|
||||||
this.criteria = onChange(getCriteriaStateObject('commercialProperty'), getSessionStorageHandlerWrapper('commercialProperty'));
|
this.criteria = this.createEnhancedProxy(getCriteriaStateObject('business'));
|
||||||
} else if ('brokerListings' === baseRoute) {
|
} else if ('commercialPropertyListings' === this.baseRoute) {
|
||||||
this.criteria = onChange(getCriteriaStateObject('broker'), getSessionStorageHandlerWrapper('broker'));
|
// this.criteria = onChange(getCriteriaStateObject('commercialProperty'), getSessionStorageHandlerWrapper('commercialProperty'));
|
||||||
|
this.criteria = this.createEnhancedProxy(getCriteriaStateObject('commercialProperty'));
|
||||||
|
} else if ('brokerListings' === this.baseRoute) {
|
||||||
|
// this.criteria = onChange(getCriteriaStateObject('broker'), getSessionStorageHandlerWrapper('broker'));
|
||||||
|
this.criteria = this.createEnhancedProxy(getCriteriaStateObject('broker'));
|
||||||
} else {
|
} else {
|
||||||
this.criteria = undefined;
|
this.criteria = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private createEnhancedProxy(obj: any) {
|
||||||
|
const component = this;
|
||||||
|
|
||||||
|
const sessionStorageHandler = function (path, value, previous, applyData) {
|
||||||
|
let criteriaType = '';
|
||||||
|
if ('/businessListings' === window.location.pathname) {
|
||||||
|
criteriaType = 'business';
|
||||||
|
} else if ('/commercialPropertyListings' === window.location.pathname) {
|
||||||
|
criteriaType = 'commercialProperty';
|
||||||
|
} else if ('/brokerListings' === window.location.pathname) {
|
||||||
|
criteriaType = 'broker';
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {}
|
ngAfterViewInit() {}
|
||||||
|
|
||||||
async openModal() {
|
async openModal() {
|
||||||
|
|
@ -94,13 +125,6 @@ export class HeaderComponent {
|
||||||
if (accepted) {
|
if (accepted) {
|
||||||
this.searchService.search(this.criteria);
|
this.searchService.search(this.criteria);
|
||||||
}
|
}
|
||||||
// if (this.isActive('/businessListings')) {
|
|
||||||
// this.modalService.showModal(createEmptyBusinessListingCriteria());
|
|
||||||
// } else if (this.isActive('/commercialPropertyListings')) {
|
|
||||||
// this.modalService.showModal(createEmptyCommercialPropertyListingCriteria());
|
|
||||||
// } else if (this.isActive('/brokerListings')) {
|
|
||||||
// this.modalService.showModal(createEmptyUserListingCriteria());
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
navigateWithState(dest: string, state: any) {
|
navigateWithState(dest: string, state: any) {
|
||||||
this.router.navigate([dest], { state: state });
|
this.router.navigate([dest], { state: state });
|
||||||
|
|
@ -146,4 +170,15 @@ export class HeaderComponent {
|
||||||
this.destroy$.next();
|
this.destroy$.next();
|
||||||
this.destroy$.complete();
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
getNumberOfFiltersSet() {
|
||||||
|
if (this.criteria?.criteriaType === 'broker') {
|
||||||
|
return compareObjects(createEmptyUserListingCriteria(), this.criteria, ['start', 'length', 'page']);
|
||||||
|
} else if (this.criteria?.criteriaType === 'business') {
|
||||||
|
return compareObjects(createEmptyBusinessListingCriteria(), this.criteria, ['start', 'length', 'page']);
|
||||||
|
} else if (this.criteria?.criteriaType === 'commercialProperty') {
|
||||||
|
return compareObjects(createEmptyCommercialPropertyListingCriteria(), this.criteria, ['start', 'length', 'page']);
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
<!-- <div class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full"> -->
|
|
||||||
<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 *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 w-full max-w-4xl max-h-full">
|
||||||
<div class="relative bg-white rounded-lg shadow">
|
<div class="relative bg-white rounded-lg shadow">
|
||||||
|
|
@ -21,20 +20,12 @@
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="state" class="block mb-2 text-sm font-medium text-gray-900">Location - State</label>
|
<label for="state" class="block mb-2 text-sm font-medium text-gray-900">Location - State</label>
|
||||||
<!-- <select id="state" [(ngModel)]="criteria.state" 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">
|
|
||||||
<option selected>Arkansas</option>
|
|
||||||
</select> -->
|
|
||||||
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [(ngModel)]="criteria.state" name="state"> </ng-select>
|
<ng-select class="custom" [items]="selectOptions?.states" bindLabel="name" bindValue="value" [(ngModel)]="criteria.state" name="state"> </ng-select>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
||||||
<!-- <input
|
|
||||||
type="text"
|
|
||||||
id="city"
|
|
||||||
[(ngModel)]="criteria.city"
|
|
||||||
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. Houston"
|
|
||||||
/> -->
|
|
||||||
<ng-select
|
<ng-select
|
||||||
class="custom"
|
class="custom"
|
||||||
[multiple]="false"
|
[multiple]="false"
|
||||||
|
|
@ -220,13 +211,6 @@
|
||||||
</div>
|
</div>
|
||||||
<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>
|
||||||
<!-- <input
|
|
||||||
type="text"
|
|
||||||
id="city"
|
|
||||||
[(ngModel)]="criteria.city"
|
|
||||||
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. Houston"
|
|
||||||
/> -->
|
|
||||||
<ng-select
|
<ng-select
|
||||||
class="custom"
|
class="custom"
|
||||||
[multiple]="false"
|
[multiple]="false"
|
||||||
|
|
@ -304,19 +288,26 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="counties" class="block mb-2 text-sm font-medium text-gray-900">Locations served - Counties</label>
|
<label for="counties" class="block mb-2 text-sm font-medium text-gray-900">Locations served - Counties</label>
|
||||||
<select id="counties" [(ngModel)]="criteria.counties" 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">
|
<ng-select
|
||||||
<option selected>Arkansas</option>
|
[items]="counties$ | async"
|
||||||
</select>
|
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"
|
||||||
|
>
|
||||||
|
<!-- @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>
|
||||||
<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>
|
||||||
<!-- <input
|
|
||||||
type="text"
|
|
||||||
id="city"
|
|
||||||
[(ngModel)]="criteria.city"
|
|
||||||
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. Houston"
|
|
||||||
/> -->
|
|
||||||
<ng-select
|
<ng-select
|
||||||
class="custom"
|
class="custom"
|
||||||
[multiple]="false"
|
[multiple]="false"
|
||||||
|
|
@ -359,7 +350,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b">
|
<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">
|
<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
|
Search ({{ numberOfResults$ | async }})
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
import { AsyncPipe, NgIf } from '@angular/common';
|
import { AsyncPipe, NgIf } from '@angular/common';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { NgSelectModule } from '@ng-select/ng-select';
|
import { NgSelectModule } from '@ng-select/ng-select';
|
||||||
import { catchError, concat, distinctUntilChanged, Observable, of, Subject, switchMap, tap } from 'rxjs';
|
import { catchError, concat, debounceTime, distinctUntilChanged, map, Observable, of, Subject, Subscription, switchMap, tap } from 'rxjs';
|
||||||
import { BusinessListingCriteria, CommercialPropertyListingCriteria, GeoResult, KeyValue, KeyValueStyle, UserListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
|
import { BusinessListingCriteria, CommercialPropertyListingCriteria, CountyResult, GeoResult, KeyValue, KeyValueStyle, UserListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
import { CriteriaChangeService } from '../../services/criteria-change.service';
|
||||||
import { GeoService } from '../../services/geo.service';
|
import { GeoService } from '../../services/geo.service';
|
||||||
|
import { ListingsService } from '../../services/listings.service';
|
||||||
import { SelectOptionsService } from '../../services/select-options.service';
|
import { SelectOptionsService } from '../../services/select-options.service';
|
||||||
|
import { UserService } from '../../services/user.service';
|
||||||
import { SharedModule } from '../../shared/shared/shared.module';
|
import { SharedModule } from '../../shared/shared/shared.module';
|
||||||
import { ModalService } from './modal.service';
|
import { ModalService } from './modal.service';
|
||||||
|
|
||||||
|
|
@ -17,17 +20,33 @@ import { ModalService } from './modal.service';
|
||||||
})
|
})
|
||||||
export class SearchModalComponent {
|
export class SearchModalComponent {
|
||||||
cities$: Observable<GeoResult[]>;
|
cities$: Observable<GeoResult[]>;
|
||||||
|
counties$: Observable<CountyResult[]>;
|
||||||
cityLoading = false;
|
cityLoading = false;
|
||||||
|
countyLoading = false;
|
||||||
cityInput$ = new Subject<string>();
|
cityInput$ = new Subject<string>();
|
||||||
constructor(public selectOptions: SelectOptionsService, public modalService: ModalService, private geoService: GeoService) {}
|
countyInput$ = new Subject<string>();
|
||||||
|
private criteriaChangeSubscription: Subscription;
|
||||||
|
public criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria;
|
||||||
|
numberOfResults$: Observable<number>;
|
||||||
|
constructor(
|
||||||
|
public selectOptions: SelectOptionsService,
|
||||||
|
public modalService: ModalService,
|
||||||
|
private geoService: GeoService,
|
||||||
|
private criteriaChangeService: CriteriaChangeService,
|
||||||
|
private listingService: ListingsService,
|
||||||
|
private userService: UserService,
|
||||||
|
) {}
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.setupCriteriaChangeListener();
|
||||||
this.modalService.message$.subscribe(msg => {
|
this.modalService.message$.subscribe(msg => {
|
||||||
this.criteria = msg;
|
this.criteria = msg;
|
||||||
|
this.setTotalNumberOfResults();
|
||||||
});
|
});
|
||||||
this.loadCities();
|
this.loadCities();
|
||||||
|
this.loadCounties();
|
||||||
}
|
}
|
||||||
public criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria;
|
|
||||||
|
|
||||||
|
ngOnChanges() {}
|
||||||
categoryClicked(checked: boolean, value: string) {
|
categoryClicked(checked: boolean, value: string) {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
this.criteria.types.push(value);
|
this.criteria.types.push(value);
|
||||||
|
|
@ -54,13 +73,34 @@ export class SearchModalComponent {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
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)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private setupCriteriaChangeListener() {
|
||||||
|
this.criteriaChangeSubscription = this.criteriaChangeService.criteriaChange$.pipe(debounceTime(400)).subscribe(() => this.setTotalNumberOfResults());
|
||||||
|
}
|
||||||
trackByFn(item: GeoResult) {
|
trackByFn(item: GeoResult) {
|
||||||
return item.id;
|
return item.id;
|
||||||
}
|
}
|
||||||
search() {
|
search() {
|
||||||
console.log('Search criteria:', this.criteria);
|
console.log('Search criteria:', this.criteria);
|
||||||
}
|
}
|
||||||
|
getCounties() {
|
||||||
|
this.geoService.findCountiesStartingWith('');
|
||||||
|
}
|
||||||
closeModal() {
|
closeModal() {
|
||||||
console.log('Closing modal');
|
console.log('Closing modal');
|
||||||
}
|
}
|
||||||
|
|
@ -70,4 +110,16 @@ export class SearchModalComponent {
|
||||||
isTypeOfProfessionalClicked(v: KeyValue) {
|
isTypeOfProfessionalClicked(v: KeyValue) {
|
||||||
return this.criteria.types.find(t => t === v.value);
|
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 === 'business' || this.criteria.criteriaType === 'commercialProperty') {
|
||||||
|
this.numberOfResults$ = this.listingService.getNumberOfListings(this.criteria, this.criteria.criteriaType);
|
||||||
|
} else if (this.criteria.criteriaType === 'broker') {
|
||||||
|
this.numberOfResults$ = this.userService.getNumberOfBroker(this.criteria);
|
||||||
|
} else {
|
||||||
|
this.numberOfResults$ = of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,3 +95,6 @@
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if(pageCount>1){
|
||||||
|
<app-paginator [page]="page" [pageCount]="pageCount" (pageChange)="onPageChange($event)"></app-paginator>
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ import { ChangeDetectorRef, Component } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||||
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { ListingType, UserListingCriteria, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { LISTINGS_PER_PAGE, ListingType, UserListingCriteria, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { PaginatorComponent } from '../../../components/paginator/paginator.component';
|
||||||
import { ImageService } from '../../../services/image.service';
|
import { ImageService } from '../../../services/image.service';
|
||||||
import { ListingsService } from '../../../services/listings.service';
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
import { SearchService } from '../../../services/search.service';
|
import { SearchService } from '../../../services/search.service';
|
||||||
|
|
@ -15,7 +16,7 @@ import { getCriteriaStateObject } from '../../../utils/utils';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-broker-listings',
|
selector: 'app-broker-listings',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, FormsModule, RouterModule, NgOptimizedImage],
|
imports: [CommonModule, FormsModule, RouterModule, NgOptimizedImage, PaginatorComponent],
|
||||||
templateUrl: './broker-listings.component.html',
|
templateUrl: './broker-listings.component.html',
|
||||||
styleUrls: ['./broker-listings.component.scss', '../../pages.scss'],
|
styleUrls: ['./broker-listings.component.scss', '../../pages.scss'],
|
||||||
})
|
})
|
||||||
|
|
@ -39,6 +40,8 @@ export class BrokerListingsComponent {
|
||||||
env = environment;
|
env = environment;
|
||||||
public category: 'business' | 'commercialProperty' | 'professionals_brokers' | undefined;
|
public category: 'business' | 'commercialProperty' | 'professionals_brokers' | undefined;
|
||||||
emailToDirName = emailToDirName;
|
emailToDirName = emailToDirName;
|
||||||
|
page = 1;
|
||||||
|
pageCount = 1;
|
||||||
constructor(
|
constructor(
|
||||||
public selectOptions: SelectOptionsService,
|
public selectOptions: SelectOptionsService,
|
||||||
private listingsService: ListingsService,
|
private listingsService: ListingsService,
|
||||||
|
|
@ -51,14 +54,6 @@ export class BrokerListingsComponent {
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
) {
|
) {
|
||||||
this.criteria = getCriteriaStateObject('broker');
|
this.criteria = getCriteriaStateObject('broker');
|
||||||
// this.route.data.subscribe(async () => {
|
|
||||||
// if (this.router.getCurrentNavigation().extras.state) {
|
|
||||||
// } else {
|
|
||||||
// this.first = this.criteria.page * this.criteria.length;
|
|
||||||
// this.rows = this.criteria.length;
|
|
||||||
// }
|
|
||||||
// this.init();
|
|
||||||
// });
|
|
||||||
this.init();
|
this.init();
|
||||||
this.searchService.currentCriteria.subscribe(criteria => {
|
this.searchService.currentCriteria.subscribe(criteria => {
|
||||||
if (criteria && criteria.criteriaType === 'broker') {
|
if (criteria && criteria.criteriaType === 'broker') {
|
||||||
|
|
@ -74,24 +69,20 @@ export class BrokerListingsComponent {
|
||||||
async init() {
|
async init() {
|
||||||
this.search();
|
this.search();
|
||||||
}
|
}
|
||||||
refine() {
|
|
||||||
this.criteria.start = 0;
|
|
||||||
this.criteria.page = 0;
|
|
||||||
this.search();
|
|
||||||
}
|
|
||||||
async search() {
|
async search() {
|
||||||
const usersReponse = await this.userService.search(this.criteria);
|
const usersReponse = await this.userService.search(this.criteria);
|
||||||
this.users = usersReponse.results;
|
this.users = usersReponse.results;
|
||||||
this.totalRecords = usersReponse.totalCount;
|
this.totalRecords = usersReponse.totalCount;
|
||||||
|
this.pageCount = this.totalRecords % LISTINGS_PER_PAGE === 0 ? this.totalRecords / LISTINGS_PER_PAGE : Math.floor(this.totalRecords / LISTINGS_PER_PAGE) + 1;
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
this.cdRef.detectChanges();
|
this.cdRef.detectChanges();
|
||||||
}
|
}
|
||||||
onPageChange(event: any) {
|
onPageChange(page: any) {
|
||||||
this.criteria.start = event.first;
|
this.criteria.start = (page - 1) * LISTINGS_PER_PAGE;
|
||||||
this.criteria.length = event.rows;
|
this.criteria.length = LISTINGS_PER_PAGE;
|
||||||
this.criteria.page = event.page;
|
this.criteria.page = page;
|
||||||
this.criteria.pageCount = event.pageCount;
|
|
||||||
this.search();
|
this.search();
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {}
|
reset() {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,9 @@
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if(pageCount>1){
|
||||||
<app-paginator [page]="page" [pageCount]="pageCount" (pageChange)="onPageChange($event)"></app-paginator>
|
<app-paginator [page]="page" [pageCount]="pageCount" (pageChange)="onPageChange($event)"></app-paginator>
|
||||||
|
}
|
||||||
|
|
||||||
<!-- <div class="container mx-auto px-4 py-8">
|
<!-- <div class="container mx-auto px-4 py-8">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
|
|
||||||
|
|
@ -50,14 +50,6 @@ export class BusinessListingsComponent {
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
) {
|
) {
|
||||||
this.criteria = getCriteriaStateObject('business');
|
this.criteria = getCriteriaStateObject('business');
|
||||||
// this.route.data.subscribe(async () => {
|
|
||||||
// if (this.router.getCurrentNavigation().extras.state) {
|
|
||||||
// } else {
|
|
||||||
// this.first = this.criteria.page * this.criteria.length;
|
|
||||||
// this.rows = this.criteria.length;
|
|
||||||
// }
|
|
||||||
// this.init();
|
|
||||||
// });
|
|
||||||
this.init();
|
this.init();
|
||||||
this.searchService.currentCriteria.subscribe(criteria => {
|
this.searchService.currentCriteria.subscribe(criteria => {
|
||||||
if (criteria && criteria.criteriaType === 'business') {
|
if (criteria && criteria.criteriaType === 'business') {
|
||||||
|
|
@ -66,20 +58,14 @@ export class BusinessListingsComponent {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async ngOnInit() {
|
async ngOnInit() {}
|
||||||
//initFlowbite();
|
|
||||||
}
|
|
||||||
async init() {
|
async init() {
|
||||||
this.reset();
|
this.reset();
|
||||||
const statesResult = await this.listingsService.getAllStates('business');
|
const statesResult = await this.listingsService.getAllStates('business');
|
||||||
this.states = statesResult.map(ls => ({ name: this.selectOptions.getState(ls.state as string), value: ls.state, count: ls.count }));
|
this.states = statesResult.map(ls => ({ name: this.selectOptions.getState(ls.state as string), value: ls.state, count: ls.count }));
|
||||||
this.search();
|
this.search();
|
||||||
}
|
}
|
||||||
// refine() {
|
|
||||||
// this.criteria.start = 0;
|
|
||||||
// this.criteria.page = 0;
|
|
||||||
// this.search();
|
|
||||||
// }
|
|
||||||
async search() {
|
async search() {
|
||||||
//this.listings = await this.listingsService.getListingsByPrompt(this.criteria);
|
//this.listings = await this.listingsService.getListingsByPrompt(this.criteria);
|
||||||
const listingReponse = await this.listingsService.getListings(this.criteria, 'business');
|
const listingReponse = await this.listingsService.getListings(this.criteria, 'business');
|
||||||
|
|
@ -90,10 +76,9 @@ export class BusinessListingsComponent {
|
||||||
this.cdRef.detectChanges();
|
this.cdRef.detectChanges();
|
||||||
}
|
}
|
||||||
onPageChange(page: any) {
|
onPageChange(page: any) {
|
||||||
this.criteria.start = (page - 1) * LISTINGS_PER_PAGE + 1;
|
this.criteria.start = (page - 1) * LISTINGS_PER_PAGE;
|
||||||
this.criteria.length = LISTINGS_PER_PAGE;
|
this.criteria.length = LISTINGS_PER_PAGE;
|
||||||
this.criteria.page = page;
|
this.criteria.page = page;
|
||||||
// this.criteria.pageCount = event.pageCount;
|
|
||||||
this.search();
|
this.search();
|
||||||
}
|
}
|
||||||
imageErrorHandler(listing: ListingType) {
|
imageErrorHandler(listing: ListingType) {
|
||||||
|
|
|
||||||
|
|
@ -78,3 +78,6 @@
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if(pageCount>1){
|
||||||
|
<app-paginator [page]="page" [pageCount]="pageCount" (pageChange)="onPageChange($event)"></app-paginator>
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ import { ChangeDetectorRef, Component } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||||
import { CommercialPropertyListing } from '../../../../../../bizmatch-server/src/models/db.model';
|
import { CommercialPropertyListing } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { CommercialPropertyListingCriteria, ResponseCommercialPropertyListingArray } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { CommercialPropertyListingCriteria, LISTINGS_PER_PAGE, ResponseCommercialPropertyListingArray } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { PaginatorComponent } from '../../../components/paginator/paginator.component';
|
||||||
import { ImageService } from '../../../services/image.service';
|
import { ImageService } from '../../../services/image.service';
|
||||||
import { ListingsService } from '../../../services/listings.service';
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
import { SearchService } from '../../../services/search.service';
|
import { SearchService } from '../../../services/search.service';
|
||||||
|
|
@ -14,7 +15,7 @@ import { getCriteriaStateObject } from '../../../utils/utils';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-commercial-property-listings',
|
selector: 'app-commercial-property-listings',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, FormsModule, RouterModule],
|
imports: [CommonModule, FormsModule, RouterModule, PaginatorComponent],
|
||||||
templateUrl: './commercial-property-listings.component.html',
|
templateUrl: './commercial-property-listings.component.html',
|
||||||
styleUrls: ['./commercial-property-listings.component.scss', '../../pages.scss'],
|
styleUrls: ['./commercial-property-listings.component.scss', '../../pages.scss'],
|
||||||
})
|
})
|
||||||
|
|
@ -35,6 +36,8 @@ export class CommercialPropertyListingsComponent {
|
||||||
totalRecords: number = 0;
|
totalRecords: number = 0;
|
||||||
ts = new Date().getTime();
|
ts = new Date().getTime();
|
||||||
env = environment;
|
env = environment;
|
||||||
|
page = 1;
|
||||||
|
pageCount = 1;
|
||||||
constructor(
|
constructor(
|
||||||
public selectOptions: SelectOptionsService,
|
public selectOptions: SelectOptionsService,
|
||||||
private listingsService: ListingsService,
|
private listingsService: ListingsService,
|
||||||
|
|
@ -46,14 +49,6 @@ export class CommercialPropertyListingsComponent {
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
) {
|
) {
|
||||||
this.criteria = getCriteriaStateObject('commercialProperty');
|
this.criteria = getCriteriaStateObject('commercialProperty');
|
||||||
// this.route.data.subscribe(async () => {
|
|
||||||
// if (this.router.getCurrentNavigation().extras.state) {
|
|
||||||
// } else {
|
|
||||||
// this.first = this.criteria.page * this.criteria.length;
|
|
||||||
// this.rows = this.criteria.length;
|
|
||||||
// }
|
|
||||||
// this.init();
|
|
||||||
// });
|
|
||||||
this.init();
|
this.init();
|
||||||
this.searchService.currentCriteria.subscribe(criteria => {
|
this.searchService.currentCriteria.subscribe(criteria => {
|
||||||
if (criteria && criteria.criteriaType === 'commercialProperty') {
|
if (criteria && criteria.criteriaType === 'commercialProperty') {
|
||||||
|
|
@ -77,14 +72,14 @@ export class CommercialPropertyListingsComponent {
|
||||||
const listingReponse = await this.listingsService.getListings(this.criteria, 'commercialProperty');
|
const listingReponse = await this.listingsService.getListings(this.criteria, 'commercialProperty');
|
||||||
this.listings = (<ResponseCommercialPropertyListingArray>listingReponse).results;
|
this.listings = (<ResponseCommercialPropertyListingArray>listingReponse).results;
|
||||||
this.totalRecords = (<ResponseCommercialPropertyListingArray>listingReponse).totalCount;
|
this.totalRecords = (<ResponseCommercialPropertyListingArray>listingReponse).totalCount;
|
||||||
|
this.pageCount = this.totalRecords % LISTINGS_PER_PAGE === 0 ? this.totalRecords / LISTINGS_PER_PAGE : Math.floor(this.totalRecords / LISTINGS_PER_PAGE) + 1;
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
this.cdRef.detectChanges();
|
this.cdRef.detectChanges();
|
||||||
}
|
}
|
||||||
onPageChange(event: any) {
|
onPageChange(page: any) {
|
||||||
this.criteria.start = event.first;
|
this.criteria.start = (page - 1) * LISTINGS_PER_PAGE;
|
||||||
this.criteria.length = event.rows;
|
this.criteria.length = LISTINGS_PER_PAGE;
|
||||||
this.criteria.page = event.page;
|
this.criteria.page = page;
|
||||||
this.criteria.pageCount = event.pageCount;
|
|
||||||
this.search();
|
this.search();
|
||||||
}
|
}
|
||||||
reset() {
|
reset() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class CriteriaChangeService {
|
||||||
|
private criteriaChangeSubject = new Subject<void>();
|
||||||
|
|
||||||
|
criteriaChange$ = this.criteriaChangeSubject.asObservable();
|
||||||
|
|
||||||
|
notifyCriteriaChange() {
|
||||||
|
this.criteriaChangeSubject.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { GeoResult } from '../../../../bizmatch-server/src/models/main.model';
|
import { CountyResult, GeoResult } from '../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
|
|
@ -15,4 +15,7 @@ export class GeoService {
|
||||||
const stateString = state ? `/${state}` : '';
|
const stateString = state ? `/${state}` : '';
|
||||||
return this.http.get<GeoResult[]>(`${this.apiBaseUrl}/bizmatch/geo/${prefix}${stateString}`);
|
return this.http.get<GeoResult[]>(`${this.apiBaseUrl}/bizmatch/geo/${prefix}${stateString}`);
|
||||||
}
|
}
|
||||||
|
findCountiesStartingWith(prefix: string, states?: string[]): Observable<CountyResult[]> {
|
||||||
|
return this.http.post<CountyResult[]>(`${this.apiBaseUrl}/bizmatch/geo/counties`, { prefix, states });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ export class ListingsService {
|
||||||
const result = await lastValueFrom(this.http.post<ResponseBusinessListingArray | ResponseCommercialPropertyListingArray>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/find`, criteria));
|
const result = await lastValueFrom(this.http.post<ResponseBusinessListingArray | ResponseCommercialPropertyListingArray>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/find`, criteria));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
getNumberOfListings(criteria: BusinessListingCriteria | CommercialPropertyListingCriteria, listingsCategory: 'business' | 'commercialProperty'): Observable<number> {
|
||||||
|
return this.http.post<number>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/findTotal`, criteria);
|
||||||
|
}
|
||||||
async getListingsByPrompt(criteria: BusinessListingCriteria | CommercialPropertyListingCriteria): Promise<BusinessListing[]> {
|
async getListingsByPrompt(criteria: BusinessListingCriteria | CommercialPropertyListingCriteria): Promise<BusinessListing[]> {
|
||||||
const result = await lastValueFrom(this.http.post<BusinessListing[]>(`${this.apiBaseUrl}/bizmatch/listings/business/search`, criteria));
|
const result = await lastValueFrom(this.http.post<BusinessListing[]>(`${this.apiBaseUrl}/bizmatch/listings/business/search`, criteria));
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom, Observable } from 'rxjs';
|
||||||
import urlcat from 'urlcat';
|
import urlcat from 'urlcat';
|
||||||
import { User } from '../../../../bizmatch-server/src/models/db.model';
|
import { User } from '../../../../bizmatch-server/src/models/db.model';
|
||||||
import { ResponseUsersArray, StatesResult, UserListingCriteria } from '../../../../bizmatch-server/src/models/main.model';
|
import { ResponseUsersArray, StatesResult, UserListingCriteria } from '../../../../bizmatch-server/src/models/main.model';
|
||||||
|
|
@ -30,6 +30,9 @@ export class UserService {
|
||||||
async search(criteria?: UserListingCriteria): Promise<ResponseUsersArray> {
|
async search(criteria?: UserListingCriteria): Promise<ResponseUsersArray> {
|
||||||
return await lastValueFrom(this.http.post<ResponseUsersArray>(`${this.apiBaseUrl}/bizmatch/user/search`, criteria));
|
return await lastValueFrom(this.http.post<ResponseUsersArray>(`${this.apiBaseUrl}/bizmatch/user/search`, criteria));
|
||||||
}
|
}
|
||||||
|
getNumberOfBroker(criteria?: UserListingCriteria): Observable<number> {
|
||||||
|
return this.http.post<number>(`${this.apiBaseUrl}/bizmatch/user/findTotal`, criteria);
|
||||||
|
}
|
||||||
async getAllStates(): Promise<any> {
|
async getAllStates(): Promise<any> {
|
||||||
return await lastValueFrom(this.http.get<StatesResult[]>(`${this.apiBaseUrl}/bizmatch/user/states/all`));
|
return await lastValueFrom(this.http.get<StatesResult[]>(`${this.apiBaseUrl}/bizmatch/user/states/all`));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,6 @@ export function createEmptyBusinessListingCriteria(): BusinessListingCriteria {
|
||||||
start: 0,
|
start: 0,
|
||||||
length: 0,
|
length: 0,
|
||||||
page: 0,
|
page: 0,
|
||||||
pageCount: 0,
|
|
||||||
state: '',
|
state: '',
|
||||||
city: '',
|
city: '',
|
||||||
types: [],
|
types: [],
|
||||||
|
|
@ -117,7 +116,6 @@ export function createEmptyCommercialPropertyListingCriteria(): CommercialProper
|
||||||
start: 0,
|
start: 0,
|
||||||
length: 0,
|
length: 0,
|
||||||
page: 0,
|
page: 0,
|
||||||
pageCount: 0,
|
|
||||||
state: '',
|
state: '',
|
||||||
city: '',
|
city: '',
|
||||||
types: [],
|
types: [],
|
||||||
|
|
@ -135,7 +133,6 @@ export function createEmptyUserListingCriteria(): UserListingCriteria {
|
||||||
start: 0,
|
start: 0,
|
||||||
length: 0,
|
length: 0,
|
||||||
page: 0,
|
page: 0,
|
||||||
pageCount: 0,
|
|
||||||
city: '',
|
city: '',
|
||||||
types: [],
|
types: [],
|
||||||
prompt: '',
|
prompt: '',
|
||||||
|
|
@ -288,3 +285,78 @@ export function initFlowbiteFix() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// export function arraysEqual(arr1: any[], arr2: any[]): boolean {
|
||||||
|
// if (arr1.length !== arr2.length) return false;
|
||||||
|
// for (let i = 0; i < arr1.length; i++) {
|
||||||
|
// if (arr1[i] !== arr2[i]) return false;
|
||||||
|
// }
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
export function compareObjects<T extends object>(obj1: T, obj2: T, ignoreProperties: (keyof T)[] = []): number {
|
||||||
|
let differences = 0;
|
||||||
|
const keys = Object.keys(obj1) as Array<keyof T>;
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
if (ignoreProperties.includes(key)) {
|
||||||
|
continue; // Überspringe diese Eigenschaft, wenn sie in der Ignore-Liste ist
|
||||||
|
}
|
||||||
|
|
||||||
|
const value1 = obj1[key];
|
||||||
|
const value2 = obj2[key];
|
||||||
|
|
||||||
|
if (!areValuesEqual(value1, value2)) {
|
||||||
|
differences++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return differences;
|
||||||
|
}
|
||||||
|
|
||||||
|
function areValuesEqual(value1: any, value2: any): boolean {
|
||||||
|
if (Array.isArray(value1) || Array.isArray(value2)) {
|
||||||
|
return arraysEqual(value1, value2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value1 === 'string' || typeof value2 === 'string') {
|
||||||
|
return isEqualString(value1, value2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value1 === 'number' || typeof value2 === 'number') {
|
||||||
|
return isEqualNumber(value1, value2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value1 === 'boolean' || typeof value2 === 'boolean') {
|
||||||
|
return isEqualBoolean(value1, value2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value1 === value2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEqualString(value1: any, value2: any): boolean {
|
||||||
|
const isEmptyOrNullish1 = value1 === undefined || value1 === null || value1 === '';
|
||||||
|
const isEmptyOrNullish2 = value2 === undefined || value2 === null || value2 === '';
|
||||||
|
return (isEmptyOrNullish1 && isEmptyOrNullish2) || value1 === value2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEqualNumber(value1: any, value2: any): boolean {
|
||||||
|
const isZeroOrNullish1 = value1 === undefined || value1 === null || value1 === 0;
|
||||||
|
const isZeroOrNullish2 = value2 === undefined || value2 === null || value2 === 0;
|
||||||
|
return (isZeroOrNullish1 && isZeroOrNullish2) || value1 === value2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEqualBoolean(value1: any, value2: any): boolean {
|
||||||
|
const isFalseOrNullish1 = value1 === undefined || value1 === null || value1 === false;
|
||||||
|
const isFalseOrNullish2 = value2 === undefined || value2 === null || value2 === false;
|
||||||
|
return (isFalseOrNullish1 && isFalseOrNullish2) || value1 === value2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function arraysEqual(arr1: any[] | null | undefined, arr2: any[] | null | undefined): boolean {
|
||||||
|
if (arr1 === arr2) return true;
|
||||||
|
if (arr1 == null || arr2 == null) return false;
|
||||||
|
if (arr1.length !== arr2.length) return false;
|
||||||
|
|
||||||
|
for (let i = 0; i < arr1.length; i++) {
|
||||||
|
if (!areValuesEqual(arr1[i], arr2[i])) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue