Ticket Fixing: #111, #110, #108 (SortBy)

This commit is contained in:
Andreas Knuth 2024-09-09 17:35:08 +02:00
parent 9ecc0c2429
commit 06d83a478d
23 changed files with 308 additions and 70 deletions

View File

@ -1,5 +1,5 @@
import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { and, arrayContains, count, eq, gte, ilike, inArray, lte, ne, or, SQL, sql } from 'drizzle-orm'; import { and, arrayContains, asc, count, desc, eq, gte, ilike, inArray, lte, ne, or, SQL, sql } from 'drizzle-orm';
import { NodePgDatabase } from 'drizzle-orm/node-postgres'; import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston'; import { Logger } from 'winston';
@ -130,6 +130,36 @@ export class BusinessListingService {
query.where(whereClause); query.where(whereClause);
} }
// Sortierung
switch (criteria.sortBy) {
case 'priceAsc':
query.orderBy(asc(businesses.price));
break;
case 'priceDesc':
query.orderBy(desc(businesses.price));
break;
case 'srAsc':
query.orderBy(asc(businesses.salesRevenue));
break;
case 'srDesc':
query.orderBy(desc(businesses.salesRevenue));
break;
case 'cfAsc':
query.orderBy(asc(businesses.cashFlow));
break;
case 'cfDesc':
query.orderBy(desc(businesses.cashFlow));
break;
case 'creationDateFirst':
query.orderBy(asc(businesses.created));
break;
case 'creationDateLast':
query.orderBy(desc(businesses.created));
break;
default:
// Keine spezifische Sortierung, Standardverhalten kann hier eingefügt werden
break;
}
// Paginierung // Paginierung
query.limit(length).offset(start); query.limit(length).offset(start);

View File

@ -1,5 +1,5 @@
import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { and, arrayContains, count, eq, gte, ilike, inArray, lte, ne, or, SQL, sql } from 'drizzle-orm'; import { and, arrayContains, asc, count, desc, eq, gte, ilike, inArray, lte, ne, or, SQL, sql } from 'drizzle-orm';
import { NodePgDatabase } from 'drizzle-orm/node-postgres'; import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston'; import { Logger } from 'winston';
@ -66,6 +66,24 @@ export class CommercialPropertyService {
const whereClause = and(...whereConditions); const whereClause = and(...whereConditions);
query.where(whereClause); query.where(whereClause);
} }
// Sortierung
switch (criteria.sortBy) {
case 'priceAsc':
query.orderBy(asc(commercials.price));
break;
case 'priceDesc':
query.orderBy(desc(commercials.price));
break;
case 'creationDateFirst':
query.orderBy(asc(commercials.created));
break;
case 'creationDateLast':
query.orderBy(desc(commercials.created));
break;
default:
// Keine spezifische Sortierung, Standardverhalten kann hier eingefügt werden
break;
}
// Paginierung // Paginierung
query.limit(length).offset(start); query.limit(length).offset(start);

View File

@ -22,6 +22,8 @@ export interface UserData {
created?: Date; created?: Date;
updated?: Date; updated?: Date;
} }
export type SortByOptions = 'priceAsc' | 'priceDesc' | 'creationDateFirst' | 'creationDateLast' | 'nameAsc' | 'nameDesc' | 'srAsc' | 'srDesc' | 'cfAsc' | 'cfDesc';
export type SortByTypes = 'professional' | 'listing' | 'business' | 'commercial';
export type Gender = 'male' | 'female'; export type Gender = 'male' | 'female';
export type CustomerType = 'buyer' | 'seller' | 'professional'; export type CustomerType = 'buyer' | 'seller' | 'professional';
export type CustomerSubType = 'broker' | 'cpa' | 'attorney' | 'titleCompany' | 'surveyor' | 'appraiser'; export type CustomerSubType = 'broker' | 'cpa' | 'attorney' | 'titleCompany' | 'surveyor' | 'appraiser';
@ -143,8 +145,7 @@ export const GeoSchema = z.object({
}, },
), ),
}); });
const phoneRegex = /^\(\d{3}\)\s\d{3}-\d{4}$/; const phoneRegex = /^(\+1|1)?[-.\s]?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/;
export const UserSchema = z export const UserSchema = z
.object({ .object({
id: z.string().uuid().optional().nullable(), id: z.string().uuid().optional().nullable(),

View File

@ -1,5 +1,5 @@
import Stripe from 'stripe'; import Stripe from 'stripe';
import { BusinessListing, CommercialPropertyListing, Sender, User } from './db.model'; import { BusinessListing, CommercialPropertyListing, Sender, SortByOptions, SortByTypes, User } from './db.model';
import { State } from './server.model'; import { State } from './server.model';
export interface StatesResult { export interface StatesResult {
@ -11,6 +11,12 @@ export interface KeyValue {
name: string; name: string;
value: string; value: string;
} }
export interface KeyValueAsSortBy {
name: string;
value: SortByOptions;
type?: SortByTypes;
selectName?: string;
}
export interface KeyValueRatio { export interface KeyValueRatio {
label: string; label: string;
value: number; value: number;
@ -63,6 +69,7 @@ export interface ListCriteria {
state: string; state: string;
city: GeoResult; city: GeoResult;
prompt: string; prompt: string;
sortBy: SortByOptions;
searchType: 'exact' | 'radius'; searchType: 'exact' | 'radius';
// radius: '5' | '20' | '50' | '100' | '200' | '300' | '400' | '500'; // radius: '5' | '20' | '50' | '100' | '200' | '300' | '400' | '500';
radius: number; radius: number;

View File

@ -15,6 +15,7 @@ export class SelectOptionsController {
typesOfCommercialProperty: this.selectOptionsService.typesOfCommercialProperty, typesOfCommercialProperty: this.selectOptionsService.typesOfCommercialProperty,
customerSubTypes: this.selectOptionsService.customerSubTypes, customerSubTypes: this.selectOptionsService.customerSubTypes,
distances: this.selectOptionsService.distances, distances: this.selectOptionsService.distances,
sortByOptions: this.selectOptionsService.sortByOptions,
}; };
} }
} }

View File

@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ImageType, KeyValue, KeyValueStyle } from '../models/main.model'; import { ImageType, KeyValue, KeyValueAsSortBy, KeyValueStyle } from '../models/main.model';
@Injectable() @Injectable()
export class SelectOptionsService { export class SelectOptionsService {
@ -35,7 +35,19 @@ export class SelectOptionsService {
{ name: '$1M', value: '1000000' }, { name: '$1M', value: '1000000' },
{ name: '$5M', value: '5000000' }, { name: '$5M', value: '5000000' },
]; ];
public sortByOptions: Array<KeyValueAsSortBy> = [
{ name: 'Price Asc', value: 'priceAsc', type: 'listing' },
{ name: 'Price Desc', value: 'priceDesc', type: 'listing' },
{ name: 'Sales Revenue Asc', value: 'srAsc', type: 'business' },
{ name: 'Sales Revenue Desc', value: 'srDesc', type: 'business' },
{ name: 'Cash Flow Asc', value: 'cfAsc', type: 'business' },
{ name: 'Cash Flow Desc', value: 'cfDesc', type: 'business' },
{ name: 'Creation Date First', value: 'creationDateFirst', type: 'listing' },
{ name: 'Creation Date Last', value: 'creationDateLast', type: 'listing' },
{ name: 'Name Asc', value: 'nameAsc', type: 'professional' },
{ name: 'Name Desc', value: 'nameDesc', type: 'professional' },
{ name: 'Sort', value: null, selectName: 'Default Sorting' },
];
public distances: Array<KeyValue> = [ public distances: Array<KeyValue> = [
{ name: '5 miles', value: '5' }, { name: '5 miles', value: '5' },
{ name: '20 miles', value: '20' }, { name: '20 miles', value: '20' },

View File

@ -1,5 +1,5 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { and, count, eq, ilike, inArray, or, SQL, sql } from 'drizzle-orm'; import { and, asc, count, desc, eq, ilike, inArray, or, SQL, sql } from 'drizzle-orm';
import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver'; import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston'; import { Logger } from 'winston';
@ -64,7 +64,18 @@ export class UserService {
const whereClause = and(...whereConditions); const whereClause = and(...whereConditions);
query.where(whereClause); query.where(whereClause);
} }
// Sortierung
switch (criteria.sortBy) {
case 'nameAsc':
query.orderBy(asc(schema.users.lastname));
break;
case 'nameDesc':
query.orderBy(desc(schema.users.lastname));
break;
default:
// Keine spezifische Sortierung, Standardverhalten kann hier eingefügt werden
break;
}
// Paginierung // Paginierung
query.limit(length).offset(start); query.limit(length).offset(start);

View File

@ -1,5 +1,5 @@
<!-- <div class="container"> --> <!-- <div class="container"> -->
<div class="min-h-screen flex flex-col" [ngClass]="{ 'bg-slate-100 print:bg-white': actualRoute !== 'home' }"> <div class="flex flex-col" [ngClass]="{ 'bg-slate-100 print:bg-white': actualRoute !== 'home' }">
@if (actualRoute !=='home'){ @if (actualRoute !=='home'){
<header></header> <header></header>
} }

View File

@ -139,6 +139,13 @@ export const routes: Routes = [
path: 'pricing', path: 'pricing',
component: PricingComponent, component: PricingComponent,
}, },
{
path: 'pricingOverview',
component: PricingComponent,
data: {
pricingOverview: true,
},
},
{ {
path: 'pricing/:id', path: 'pricing/:id',
component: PricingComponent, component: PricingComponent,

View File

@ -11,6 +11,7 @@
<div class="flex flex-col lg:flex-row items-center order-3 lg:order-2"> <div class="flex flex-col lg:flex-row items-center order-3 lg:order-2">
<a class="text-sm text-blue-600 hover:underline hover:cursor-pointer mx-2" data-drawer-target="terms-of-use" data-drawer-show="terms-of-use" aria-controls="terms-of-use">Terms of use</a> <a class="text-sm text-blue-600 hover:underline hover:cursor-pointer mx-2" data-drawer-target="terms-of-use" data-drawer-show="terms-of-use" aria-controls="terms-of-use">Terms of use</a>
<a class="text-sm text-blue-600 hover:underline hover:cursor-pointer mx-2" data-drawer-target="privacy" data-drawer-show="privacy" aria-controls="privacy">Privacy statement</a> <a class="text-sm text-blue-600 hover:underline hover:cursor-pointer mx-2" data-drawer-target="privacy" data-drawer-show="privacy" aria-controls="privacy">Privacy statement</a>
<a class="text-sm text-blue-600 hover:underline hover:cursor-pointer mx-2" routerLink="/pricingOverview">Pricing</a>
</div> </div>
<div class="flex flex-col lg:flex-row items-center order-2 lg:order-3"> <div class="flex flex-col lg:flex-row items-center order-2 lg:order-3">

View File

@ -3,18 +3,44 @@
<a routerLink="/home" class="flex items-center space-x-3 rtl:space-x-reverse"> <a routerLink="/home" class="flex items-center space-x-3 rtl:space-x-reverse">
<img src="assets/images/header-logo.png" class="h-8" alt="Flowbite Logo" /> <img src="assets/images/header-logo.png" class="h-8" alt="Flowbite Logo" />
</a> </a>
<div class="flex items-center md:order-2 space-x-3 md:space-x-0 rtl:space-x-reverse"> <div class="flex items-center md:order-2 space-x-3 rtl:space-x-reverse">
<!-- Filter button --> <!-- Filter button -->
@if(isListingUrl()){ @if(isFilterUrl()){
<button <button
type="button" type="button"
#triggerButton #triggerButton
(click)="openModal()" (click)="openModal()"
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"
> >
<i class="fas fa-filter mr-2"></i>Filter ({{ getNumberOfFiltersSet() }}) <i class="fas fa-filter mr-2"></i>Filter ({{ getNumberOfFiltersSet() }})
</button> </button>
<!-- Sort button -->
<div class="relative">
<button
type="button"
id="sortDropdownButton"
class="max-sm:hidden px-4 py-2 text-sm font-medium 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"
(click)="toggleSortDropdown()"
[ngClass]="{ 'text-blue-500': selectOptions.getSortByOption(criteria.sortBy) !== 'Sort', 'text-gray-900': selectOptions.getSortByOption(criteria.sortBy) === 'Sort' }"
>
<i class="fas fa-sort mr-2"></i>{{ selectOptions.getSortByOption(criteria.sortBy) }}
</button>
<!-- Sort options dropdown -->
<div *ngIf="sortDropdownVisible" class="absolute right-0 z-50 w-48 md:mt-2 max-md:mt-20 max-md:mr-[-2.5rem] bg-white border border-gray-200 rounded-lg shadow-lg dark:bg-gray-800 dark:border-gray-600">
<ul class="py-1 text-sm text-gray-700 dark:text-gray-200">
@for(item of sortByOptions; track item){
<li (click)="sortBy(item.value)" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer">{{ item.selectName ? item.selectName : item.name }}</li>
}
<!-- <li (click)="sortBy('priceAsc')" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer">Price Ascending</li>
<li (click)="sortBy('priceDesc')" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer">Price Descending</li>
<li (click)="sortBy('creationDateFirst')" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer">Creation Date First</li>
<li (click)="sortBy('creationDateLast')" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer">Creation Date Last</li>
<li (click)="sortBy(null)" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer">Default Sorting</li> -->
</ul>
</div>
</div>
} }
<button <button
type="button" type="button"
@ -68,6 +94,35 @@
<a routerLink="/logout" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Logout</a> <a routerLink="/logout" (click)="closeDropdown()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Logout</a>
</li> </li>
</ul> </ul>
<ul class="py-2 md:hidden">
<li>
<a
routerLink="/businessListings"
[ngClass]="{ 'text-blue-700': isActive('/businessListings'), 'text-gray-700': !isActive('/businessListings') }"
class="block px-4 py-2 text-sm font-semibold"
(click)="closeMenusAndSetCriteria('businessListings')"
>Businesses</a
>
</li>
<li>
<a
routerLink="/commercialPropertyListings"
[ngClass]="{ 'text-blue-700': isActive('/commercialPropertyListings'), 'text-gray-700': !isActive('/commercialPropertyListings') }"
class="block px-4 py-2 text-sm font-semibold"
(click)="closeMenusAndSetCriteria('commercialPropertyListings')"
>Properties</a
>
</li>
<li>
<a
routerLink="/brokerListings"
[ngClass]="{ 'text-blue-700': isActive('/brokerListings'), 'text-gray-700': !isActive('/brokerListings') }"
class="block px-4 py-2 text-sm font-semibold"
(click)="closeMenusAndSetCriteria('brokerListings')"
>Professionals</a
>
</li>
</ul>
</div> </div>
} @else { } @else {
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 dark:divide-gray-600" id="user-unknown"> <div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 dark:divide-gray-600" id="user-unknown">
@ -79,9 +134,38 @@
<a routerLink="/pricing" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Register</a> <a routerLink="/pricing" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Register</a>
</li> </li>
</ul> </ul>
<ul class="py-2 md:hidden">
<li>
<a
routerLink="/businessListings"
[ngClass]="{ 'text-blue-700': isActive('/businessListings'), 'text-gray-700': !isActive('/businessListings') }"
class="block px-4 py-2 text-sm font-bold"
(click)="closeMenusAndSetCriteria('businessListings')"
>Businesses</a
>
</li>
<li>
<a
routerLink="/commercialPropertyListings"
[ngClass]="{ 'text-blue-700': isActive('/commercialPropertyListings'), 'text-gray-700': !isActive('/commercialPropertyListings') }"
class="block px-4 py-2 text-sm font-bold"
(click)="closeMenusAndSetCriteria('commercialPropertyListings')"
>Properties</a
>
</li>
<li>
<a
routerLink="/brokerListings"
[ngClass]="{ 'text-blue-700': isActive('/brokerListings'), 'text-gray-700': !isActive('/brokerListings') }"
class="block px-4 py-2 text-sm font-bold"
(click)="closeMenusAndSetCriteria('brokerListings')"
>Professionals</a
>
</li>
</ul>
</div> </div>
} }
<button <!-- <button
data-collapse-toggle="navbar-user" data-collapse-toggle="navbar-user"
type="button" type="button"
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
@ -92,7 +176,7 @@
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14"> <svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15" /> <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15" />
</svg> </svg>
</button> </button> -->
</div> </div>
<div class="items-center justify-between hidden w-full md:flex md:w-auto md:order-1" id="navbar-user"> <div class="items-center justify-between hidden w-full md:flex md:w-auto md:order-1" id="navbar-user">
<ul <ul
@ -133,7 +217,7 @@
</div> </div>
</div> </div>
<!-- Mobile filter button --> <!-- Mobile filter button -->
@if(isListingUrl()){ @if(isFilterUrl()){
<div class="md:hidden flex justify-center pb-4"> <div class="md:hidden flex justify-center pb-4">
<button <button
(click)="openModal()" (click)="openModal()"
@ -143,6 +227,16 @@
> >
<i class="fas fa-filter mr-2"></i>Filter ({{ getNumberOfFiltersSet() }}) <i class="fas fa-filter mr-2"></i>Filter ({{ getNumberOfFiltersSet() }})
</button> </button>
<!-- Sorting -->
<button
(click)="toggleSortDropdown()"
type="button"
id="sortDropdownMobileButton"
class="mx-4 w-1/2 px-4 py-2 text-sm font-medium 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"
[ngClass]="{ 'text-blue-500': selectOptions.getSortByOption(criteria.sortBy) !== 'Sort', 'text-gray-900': selectOptions.getSortByOption(criteria.sortBy) === 'Sort' }"
>
<i class="fas fa-sort mr-2"></i>{{ selectOptions.getSortByOption(criteria.sortBy) }}
</button>
</div> </div>
} }
</nav> </nav>

View File

@ -1,6 +1,6 @@
import { BreakpointObserver } from '@angular/cdk/layout'; import { BreakpointObserver } from '@angular/cdk/layout';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component } from '@angular/core'; import { Component, HostListener } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { NavigationEnd, Router, RouterModule } from '@angular/router'; import { NavigationEnd, Router, RouterModule } from '@angular/router';
import { faUserGear } from '@fortawesome/free-solid-svg-icons'; import { faUserGear } from '@fortawesome/free-solid-svg-icons';
@ -8,11 +8,12 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { Collapse, Dropdown, initFlowbite } from 'flowbite'; import { Collapse, Dropdown, initFlowbite } from 'flowbite';
import { KeycloakService } from 'keycloak-angular'; import { KeycloakService } from 'keycloak-angular';
import { filter, Observable, Subject, Subscription } from 'rxjs'; import { filter, Observable, Subject, Subscription } from 'rxjs';
import { User } from '../../../../../bizmatch-server/src/models/db.model'; import { SortByOptions, 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, KeyValueAsSortBy, 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 { CriteriaChangeService } from '../../services/criteria-change.service';
import { SearchService } from '../../services/search.service'; import { SearchService } from '../../services/search.service';
import { SelectOptionsService } from '../../services/select-options.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 { assignProperties, compareObjects, createEmptyBusinessListingCriteria, createEmptyCommercialPropertyListingCriteria, createEmptyUserListingCriteria, getCriteriaProxy, map2User } from '../../utils/utils'; import { assignProperties, compareObjects, createEmptyBusinessListingCriteria, createEmptyCommercialPropertyListingCriteria, createEmptyUserListingCriteria, getCriteriaProxy, map2User } from '../../utils/utils';
@ -43,6 +44,8 @@ export class HeaderComponent {
criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria; criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria;
private routerSubscription: Subscription | undefined; private routerSubscription: Subscription | undefined;
baseRoute: string; baseRoute: string;
sortDropdownVisible: boolean;
sortByOptions: KeyValueAsSortBy[] = [];
constructor( constructor(
public keycloakService: KeycloakService, public keycloakService: KeycloakService,
private router: Router, private router: Router,
@ -52,8 +55,15 @@ export class HeaderComponent {
private modalService: ModalService, private modalService: ModalService,
private searchService: SearchService, private searchService: SearchService,
private criteriaChangeService: CriteriaChangeService, private criteriaChangeService: CriteriaChangeService,
public selectOptions: SelectOptionsService,
) {} ) {}
@HostListener('document:click', ['$event'])
handleGlobalClick(event: Event) {
const target = event.target as HTMLElement;
if (target.id !== 'sortDropdownButton' && target.id !== 'sortDropdownMobileButton') {
this.sortDropdownVisible = false;
}
}
async ngOnInit() { async ngOnInit() {
const token = await this.keycloakService.getToken(); const token = await this.keycloakService.getToken();
this.keycloakUser = map2User(token); this.keycloakUser = map2User(token);
@ -73,9 +83,11 @@ export class HeaderComponent {
}); });
this.checkCurrentRoute(this.router.url); this.checkCurrentRoute(this.router.url);
this.setupSortByOptions();
this.routerSubscription = this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: any) => { this.routerSubscription = this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: any) => {
this.checkCurrentRoute(event.urlAfterRedirects); this.checkCurrentRoute(event.urlAfterRedirects);
this.setupSortByOptions();
}); });
this.userService.currentUser.pipe(untilDestroyed(this)).subscribe(u => { this.userService.currentUser.pipe(untilDestroyed(this)).subscribe(u => {
@ -90,7 +102,19 @@ export class HeaderComponent {
this.criteria = getCriteriaProxy(this.baseRoute, this); this.criteria = getCriteriaProxy(this.baseRoute, this);
this.searchService.search(this.criteria); this.searchService.search(this.criteria);
} }
setupSortByOptions() {
this.sortByOptions = [];
if (this.isProfessionalListing()) {
this.sortByOptions = [...this.sortByOptions, ...this.selectOptions.sortByOptions.filter(s => s.type === 'professional')];
}
if (this.isBusinessListing()) {
this.sortByOptions = [...this.sortByOptions, ...this.selectOptions.sortByOptions.filter(s => s.type === 'business' || s.type === 'listing')];
}
if (this.isCommercialPropertyListing()) {
this.sortByOptions = [...this.sortByOptions, ...this.selectOptions.sortByOptions.filter(s => s.type === 'commercial' || s.type === 'listing')];
}
this.sortByOptions = [...this.sortByOptions, ...this.selectOptions.sortByOptions.filter(s => !s.type)];
}
ngAfterViewInit() {} ngAfterViewInit() {}
async openModal() { async openModal() {
@ -113,9 +137,21 @@ export class HeaderComponent {
isActive(route: string): boolean { isActive(route: string): boolean {
return this.router.url === route; return this.router.url === route;
} }
isListingUrl(): boolean { isFilterUrl(): boolean {
return ['/businessListings', '/commercialPropertyListings', '/brokerListings'].includes(this.router.url); return ['/businessListings', '/commercialPropertyListings', '/brokerListings'].includes(this.router.url);
} }
isBusinessListing(): boolean {
return ['/businessListings'].includes(this.router.url);
}
isCommercialPropertyListing(): boolean {
return ['/commercialPropertyListings'].includes(this.router.url);
}
isProfessionalListing(): boolean {
return ['/brokerListings'].includes(this.router.url);
}
// isSortingUrl(): boolean {
// return ['/businessListings', '/commercialPropertyListings'].includes(this.router.url);
// }
closeDropdown() { closeDropdown() {
const dropdownButton = document.getElementById('user-menu-button'); const dropdownButton = document.getElementById('user-menu-button');
const dropdownMenu = this.user ? document.getElementById('user-login') : document.getElementById('user-unknown'); const dropdownMenu = this.user ? document.getElementById('user-login') : document.getElementById('user-unknown');
@ -148,11 +184,11 @@ export class HeaderComponent {
} }
getNumberOfFiltersSet() { getNumberOfFiltersSet() {
if (this.criteria?.criteriaType === 'brokerListings') { if (this.criteria?.criteriaType === 'brokerListings') {
return compareObjects(createEmptyUserListingCriteria(), this.criteria, ['start', 'length', 'page', 'searchType', 'radius']); return compareObjects(createEmptyUserListingCriteria(), this.criteria, ['start', 'length', 'page', 'searchType', 'radius', 'sortBy']);
} else if (this.criteria?.criteriaType === 'businessListings') { } else if (this.criteria?.criteriaType === 'businessListings') {
return compareObjects(createEmptyBusinessListingCriteria(), this.criteria, ['start', 'length', 'page', 'searchType', 'radius']); return compareObjects(createEmptyBusinessListingCriteria(), this.criteria, ['start', 'length', 'page', 'searchType', 'radius', 'sortBy']);
} else if (this.criteria?.criteriaType === 'commercialPropertyListings') { } else if (this.criteria?.criteriaType === 'commercialPropertyListings') {
return compareObjects(createEmptyCommercialPropertyListingCriteria(), this.criteria, ['start', 'length', 'page', 'searchType', 'radius']); return compareObjects(createEmptyCommercialPropertyListingCriteria(), this.criteria, ['start', 'length', 'page', 'searchType', 'radius', 'sortBy']);
} else { } else {
return 0; return 0;
} }
@ -160,4 +196,12 @@ export class HeaderComponent {
isAdmin() { isAdmin() {
return this.keycloakService.getUserRoles(true).includes('ADMIN'); return this.keycloakService.getUserRoles(true).includes('ADMIN');
} }
sortBy(sortBy: SortByOptions) {
this.criteria.sortBy = sortBy;
this.sortDropdownVisible = false;
this.searchService.search(this.criteria);
}
toggleSortDropdown() {
this.sortDropdownVisible = !this.sortDropdownVisible;
}
} }

View File

@ -19,7 +19,7 @@
<div class="p-6 space-y-6"> <div class="p-6 space-y-6">
<div class="flex space-x-4 mb-4"> <div class="flex space-x-4 mb-4">
<button class="text-blue-600 font-medium border-b-2 border-blue-600 pb-2">Classic Search</button> <button class="text-blue-600 font-medium border-b-2 border-blue-600 pb-2">Classic Search</button>
<button class="text-gray-500">AI Search <span class="bg-gray-200 text-xs font-semibold px-2 py-1 rounded">BETA</span></button> <!-- <button class="text-gray-500">AI Search <span class="bg-gray-200 text-xs font-semibold px-2 py-1 rounded">BETA</span></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> <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"> <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 Clear all Filter

View File

@ -59,9 +59,7 @@ export class SearchModalComponent {
this.loadCounties(); this.loadCounties();
} }
ngOnChanges() { ngOnChanges() {}
console.log('sdf');
}
categoryClicked(checked: boolean, value: string) { categoryClicked(checked: boolean, value: string) {
if (checked) { if (checked) {
this.criteria.types.push(value); this.criteria.types.push(value);

View File

@ -22,7 +22,7 @@
<div class="py-4 print:hidden"> <div class="py-4 print:hidden">
@if(listing && listingUser && (listingUser?.email===user?.email || isAdmin())){ @if(listing && listingUser && (listingUser?.email===user?.email || isAdmin())){
<div class="inline"> <div class="inline">
<button class="share share-edit text-white font-bold text-xs py-1.5 px-2 inline-flex items-center" [routerLink]="['/editBusinessListing', listing.id]"> <button class="share share-edit text-white font-bold text-xs py-1.5 px-2 inline-flex items-center" [routerLink]="['/editCommercialPropertyListing', listing.id]">
<i class="fa-regular fa-pen-to-square"></i> <i class="fa-regular fa-pen-to-square"></i>
<span class="ml-2">Edit</span> <span class="ml-2">Edit</span>
</button> </button>

View File

@ -18,11 +18,15 @@
<span class="bg-red-100 text-red-800 text-sm font-medium me-2 ml-2 px-2.5 py-0.5 rounded dark:bg-red-900 dark:text-red-300">Draft</span> <span class="bg-red-100 text-red-800 text-sm font-medium me-2 ml-2 px-2.5 py-0.5 rounded dark:bg-red-900 dark:text-red-300">Draft</span>
} }
</h2> </h2>
<div class="flex justify-between">
<!-- State Badge --> <!-- State Badge -->
<span class="w-fit inline-flex items-center justify-center px-2 py-1 mb-4 text-xs font-bold leading-none bg-gray-200 text-gray-700 rounded-full"> <span class="w-fit inline-flex items-center justify-center px-2 py-1 mb-4 text-xs font-bold leading-none bg-gray-200 text-gray-700 rounded-full">
{{ selectOptions.getState(listing.location.state) }} {{ selectOptions.getState(listing.location.state) }}
</span> </span>
<p class="text-sm text-gray-600 mb-4">
<strong>{{ getDaysListed(listing) }} days listed</strong>
</p>
</div>
<!-- Asking Price hervorgehoben --> <!-- Asking Price hervorgehoben -->
<p class="text-base font-bold text-gray-800 mb-2"> <p class="text-base font-bold text-gray-800 mb-2">
<strong>Asking price:</strong> <span class="text-green-600"> {{ listing.price | currency : 'USD' : 'symbol' : '1.0-0' }}</span> <strong>Asking price:</strong> <span class="text-green-600"> {{ listing.price | currency : 'USD' : 'symbol' : '1.0-0' }}</span>
@ -31,6 +35,7 @@
<p class="text-sm text-gray-600 mb-2"><strong>Net profit:</strong> {{ listing.cashFlow | currency : 'USD' : 'symbol' : '1.0-0' }}</p> <p class="text-sm text-gray-600 mb-2"><strong>Net profit:</strong> {{ listing.cashFlow | currency : 'USD' : 'symbol' : '1.0-0' }}</p>
<p class="text-sm text-gray-600 mb-2"><strong>Location:</strong> {{ listing.location.name }}</p> <p class="text-sm text-gray-600 mb-2"><strong>Location:</strong> {{ listing.location.name }}</p>
<p class="text-sm text-gray-600 mb-4"><strong>Established:</strong> {{ listing.established }}</p> <p class="text-sm text-gray-600 mb-4"><strong>Established:</strong> {{ listing.established }}</p>
<img src="{{ env.imageBaseUrl }}/pictures/logo/{{ listing.imageName }}.avif?_ts={{ ts }}" alt="Company logo" class="absolute bottom-[80px] right-[20px] h-[45px] w-auto" /> <img src="{{ env.imageBaseUrl }}/pictures/logo/{{ listing.imageName }}.avif?_ts={{ ts }}" alt="Company logo" class="absolute bottom-[80px] right-[20px] h-[45px] w-auto" />
<!-- Position und Größe des Bildes angepasst --> <!-- Position und Größe des Bildes angepasst -->
<div class="flex-grow"></div> <div class="flex-grow"></div>
@ -48,37 +53,6 @@
</div> </div>
</div> </div>
<!-- <div class="container mx-auto p-4">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
@for (listing of listings; track listing.id) {
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<div class="p-4 flex flex-col h-full relative z-[0]">
<div class="flex items-center mb-2">
<i [class]="selectOptions.getIconAndTextColorType(listing.type)" class="mr-2"></i>
<span [class]="selectOptions.getTextColorType(listing.type)" class="font-semibold">{{ selectOptions.getBusiness(listing.type) }}</span>
</div>
<h2 class="text-lg font-semibold mb-2">
{{ listing.title }}
@if(listing.draft){
<span class="bg-red-100 text-red-800 text-sm font-medium me-2 ml-2 px-2.5 py-0.5 rounded dark:bg-red-900 dark:text-red-300">Draft</span>
}
</h2>
<p class="text-sm text-gray-600 mb-1">Asking price: {{ listing.price | currency : 'USD' : 'symbol' : '1.0-0' }}</p>
<p class="text-sm text-gray-600 mb-1">Sales revenue: {{ listing.salesRevenue | currency : 'USD' : 'symbol' : '1.0-0' }}</p>
<p class="text-sm text-gray-600 mb-1">Net profit: {{ listing.cashFlow | currency : 'USD' : 'symbol' : '1.0-0' }}</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>
<button class="bg-green-500 text-white px-4 py-2 rounded-md w-full flex items-center justify-center mt-2" [routerLink]="['/details-business-listing', listing.id]">
View Full Listing
<i class="fas fa-arrow-right ml-2"></i>
</button>
</div>
</div>
}
</div>
</div> -->
@if(pageCount>1){ @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>
} }

View File

@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { ChangeDetectorRef, Component } from '@angular/core'; 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 dayjs from 'dayjs';
import { BusinessListing } from '../../../../../../bizmatch-server/src/models/db.model'; import { BusinessListing } from '../../../../../../bizmatch-server/src/models/db.model';
import { BusinessListingCriteria, LISTINGS_PER_PAGE, ListingType, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model'; import { BusinessListingCriteria, LISTINGS_PER_PAGE, ListingType, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../../../environments/environment'; import { environment } from '../../../../environments/environment';
@ -83,4 +84,7 @@ export class BusinessListingsComponent {
reset() { reset() {
this.criteria.title = null; this.criteria.title = null;
} }
getDaysListed(listing: BusinessListing) {
return dayjs().diff(listing.created, 'day');
}
} }

View File

@ -8,9 +8,14 @@
<img src="assets/images/placeholder_properties.jpg" alt="Image" class="w-full h-48 object-cover" /> <img src="assets/images/placeholder_properties.jpg" alt="Image" class="w-full h-48 object-cover" />
} }
<div class="p-4 flex flex-col flex-grow"> <div class="p-4 flex flex-col flex-grow">
<div class="flex items-center justify-between mb-2"> <span [class]="selectOptions.getTextColorTypeOfCommercial(listing.type)" class="text-sm font-semibold"
><i [class]="selectOptions.getIconAndTextColorTypeOfCommercials(listing.type)" class="mr-1"></i> {{ selectOptions.getCommercialProperty(listing.type) }}</span
>
<div class="flex items-center justify-between my-2">
<span class="bg-gray-200 text-gray-700 text-xs font-semibold px-2 py-1 rounded">{{ selectOptions.getState(listing.location.state) }}</span> <span class="bg-gray-200 text-gray-700 text-xs font-semibold px-2 py-1 rounded">{{ selectOptions.getState(listing.location.state) }}</span>
<span class="text-gray-600 text-sm"><i [class]="selectOptions.getIconTypeOfCommercials(listing.type)" class="mr-1"></i> {{ selectOptions.getCommercialProperty(listing.type) }}</span> <p class="text-sm text-gray-600 mb-4">
<strong>{{ getDaysListed(listing) }} days listed</strong>
</p>
</div> </div>
<h3 class="text-lg font-semibold mb-2"> <h3 class="text-lg font-semibold mb-2">
{{ listing.title }} {{ listing.title }}

View File

@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { ChangeDetectorRef, Component } from '@angular/core'; 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 dayjs from 'dayjs';
import { CommercialPropertyListing } from '../../../../../../bizmatch-server/src/models/db.model'; import { CommercialPropertyListing } from '../../../../../../bizmatch-server/src/models/db.model';
import { CommercialPropertyListingCriteria, LISTINGS_PER_PAGE, 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';
@ -81,4 +82,7 @@ export class CommercialPropertyListingsComponent {
getTS() { getTS() {
return new Date().getTime(); return new Date().getTime();
} }
getDaysListed(listing: CommercialPropertyListing) {
return dayjs().diff(listing.created, 'day');
}
} }

View File

@ -33,9 +33,11 @@
</li> </li>
</ul> </ul>
</div> </div>
@if(!pricingOverview){
<div class="px-6 py-4 mt-auto"> <div class="px-6 py-4 mt-auto">
<button (click)="register()" class="w-full bg-blue-500 text-white rounded-full px-4 py-2 font-semibold hover:bg-blue-600 transition duration-300">Sign Up Now</button> <button (click)="register()" class="w-full bg-blue-500 text-white rounded-full px-4 py-2 font-semibold hover:bg-blue-600 transition duration-300">Sign Up Now</button>
</div> </div>
}
</div> </div>
} }
@ -79,9 +81,11 @@
</li> </li>
</ul> </ul>
</div> </div>
@if(!pricingOverview){
<div class="px-6 py-4 mt-auto"> <div class="px-6 py-4 mt-auto">
<button (click)="register('price_1PpSkpDjmFBOcNBs9UDPgBos')" class="w-full bg-blue-500 text-white rounded-full px-4 py-2 font-semibold hover:bg-blue-600 transition duration-300">Get Started</button> <button (click)="register('price_1PpSkpDjmFBOcNBs9UDPgBos')" class="w-full bg-blue-500 text-white rounded-full px-4 py-2 font-semibold hover:bg-blue-600 transition duration-300">Get Started</button>
</div> </div>
}
</div> </div>
<!-- Business Broker Plan --> <!-- Business Broker Plan -->
@ -128,9 +132,11 @@
</li> </li>
</ul> </ul>
</div> </div>
@if(!pricingOverview){
<div class="px-6 py-4 mt-auto"> <div class="px-6 py-4 mt-auto">
<button (click)="register('price_1PpSmRDjmFBOcNBsaaSp2nk9')" class="w-full bg-blue-500 text-white rounded-full px-4 py-2 font-semibold hover:bg-blue-600 transition duration-300">Start Listing Now</button> <button (click)="register('price_1PpSmRDjmFBOcNBsaaSp2nk9')" class="w-full bg-blue-500 text-white rounded-full px-4 py-2 font-semibold hover:bg-blue-600 transition duration-300">Start Listing Now</button>
</div> </div>
}
</div> </div>
</div> </div>

View File

@ -21,6 +21,7 @@ import { map2User } from '../../utils/utils';
export class PricingComponent { export class PricingComponent {
private apiBaseUrl = environment.apiBaseUrl; private apiBaseUrl = environment.apiBaseUrl;
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined; private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
pricingOverview: boolean | undefined = this.activatedRoute.snapshot.data['pricingOverview'] as boolean | undefined;
keycloakUser: KeycloakUser; keycloakUser: KeycloakUser;
user: User; user: User;
constructor(public keycloakService: KeycloakService, private http: HttpClient, private stripeService: StripeService, private activatedRoute: ActivatedRoute, private userService: UserService, private router: Router) {} constructor(public keycloakService: KeycloakService, private http: HttpClient, private stripeService: StripeService, private activatedRoute: ActivatedRoute, private userService: UserService, private router: Router) {}
@ -36,12 +37,14 @@ export class PricingComponent {
this.router.navigate([`/account`]); this.router.navigate([`/account`]);
} else if (this.id) { } else if (this.id) {
this.checkout({ priceId: atob(this.id), email: this.keycloakUser.email, name: `${this.keycloakUser.firstName} ${this.keycloakUser.lastName}` }); this.checkout({ priceId: atob(this.id), email: this.keycloakUser.email, name: `${this.keycloakUser.firstName} ${this.keycloakUser.lastName}` });
} else if (!this.id) { } else if (!this.id && !this.pricingOverview) {
this.user = await this.userService.getByMail(this.keycloakUser.email); this.user = await this.userService.getByMail(this.keycloakUser.email);
if (this.user.subscriptionId) { if (this.user.subscriptionId) {
this.router.navigate([`/account`]); this.router.navigate([`/account`]);
} }
} }
} else {
this.pricingOverview = false;
} }
} }

View File

@ -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 { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { KeyValue, KeyValueStyle } from '../../../../bizmatch-server/src/models/main.model'; import { KeyValue, KeyValueAsSortBy, KeyValueStyle } from '../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
@Injectable({ @Injectable({
@ -22,6 +22,7 @@ export class SelectOptionsService {
this.gender = allSelectOptions.gender; this.gender = allSelectOptions.gender;
this.typesOfCommercialProperty = allSelectOptions.typesOfCommercialProperty; this.typesOfCommercialProperty = allSelectOptions.typesOfCommercialProperty;
this.distances = allSelectOptions.distances; this.distances = allSelectOptions.distances;
this.sortByOptions = allSelectOptions.sortByOptions;
} }
public typesOfBusiness: Array<KeyValueStyle>; public typesOfBusiness: Array<KeyValueStyle>;
@ -38,6 +39,10 @@ export class SelectOptionsService {
public states: Array<any>; public states: Array<any>;
public customerSubTypes: Array<KeyValue>; public customerSubTypes: Array<KeyValue>;
public distances: Array<KeyValue>; public distances: Array<KeyValue>;
public sortByOptions: Array<KeyValueAsSortBy>;
getSortByOption(value: string) {
return this.sortByOptions.find(l => l.value === value)?.name;
}
getState(value: string): string { getState(value: string): string {
return this.states.find(l => l.value === value)?.name; return this.states.find(l => l.value === value)?.name;
} }
@ -75,4 +80,11 @@ export class SelectOptionsService {
getIconTypeOfCommercials(value: string): string { getIconTypeOfCommercials(value: string): string {
return this.typesOfCommercialProperty.find(c => c.value === value)?.icon; return this.typesOfCommercialProperty.find(c => c.value === value)?.icon;
} }
getIconAndTextColorTypeOfCommercials(value: string): string {
const category = this.typesOfCommercialProperty.find(c => c.value === value);
return `${category?.icon} ${category?.textColorClass}`;
}
getTextColorTypeOfCommercial(value: string): string {
return this.typesOfCommercialProperty.find(c => c.value === value)?.textColorClass;
}
} }

View File

@ -15,6 +15,7 @@ export function createEmptyBusinessListingCriteria(): BusinessListingCriteria {
city: null, city: null,
types: [], types: [],
prompt: '', prompt: '',
sortBy: null,
criteriaType: 'businessListings', criteriaType: 'businessListings',
minPrice: null, minPrice: null,
maxPrice: null, maxPrice: null,
@ -45,6 +46,7 @@ export function createEmptyCommercialPropertyListingCriteria(): CommercialProper
city: null, city: null,
types: [], types: [],
prompt: '', prompt: '',
sortBy: null,
criteriaType: 'commercialPropertyListings', criteriaType: 'commercialPropertyListings',
minPrice: null, minPrice: null,
maxPrice: null, maxPrice: null,
@ -62,6 +64,7 @@ export function createEmptyUserListingCriteria(): UserListingCriteria {
city: null, city: null,
types: [], types: [],
prompt: '', prompt: '',
sortBy: null,
criteriaType: 'brokerListings', criteriaType: 'brokerListings',
brokerName: '', brokerName: '',
companyName: '', companyName: '',
@ -79,6 +82,7 @@ export function resetBusinessListingCriteria(criteria: BusinessListingCriteria)
criteria.city = null; criteria.city = null;
criteria.types = []; criteria.types = [];
criteria.prompt = ''; criteria.prompt = '';
criteria.sortBy = null;
criteria.criteriaType = 'businessListings'; criteria.criteriaType = 'businessListings';
criteria.minPrice = null; criteria.minPrice = null;
criteria.maxPrice = null; criteria.maxPrice = null;
@ -107,6 +111,7 @@ export function resetCommercialPropertyListingCriteria(criteria: CommercialPrope
criteria.city = null; criteria.city = null;
criteria.types = []; criteria.types = [];
criteria.prompt = ''; criteria.prompt = '';
criteria.sortBy = null;
criteria.criteriaType = 'commercialPropertyListings'; criteria.criteriaType = 'commercialPropertyListings';
criteria.minPrice = null; criteria.minPrice = null;
criteria.maxPrice = null; criteria.maxPrice = null;
@ -122,6 +127,7 @@ export function resetUserListingCriteria(criteria: UserListingCriteria) {
criteria.city = null; criteria.city = null;
criteria.types = []; criteria.types = [];
criteria.prompt = ''; criteria.prompt = '';
criteria.sortBy = null;
criteria.criteriaType = 'brokerListings'; criteria.criteriaType = 'brokerListings';
criteria.brokerName = ''; criteria.brokerName = '';
criteria.companyName = ''; criteria.companyName = '';