fix inputnumber, Umbau auf redis-om, Neubenamung

This commit is contained in:
Andreas Knuth 2024-03-11 18:27:43 +01:00
parent 6ad40b6dca
commit be146fdc6a
25 changed files with 1830 additions and 224 deletions

View File

@ -1,7 +1,6 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { AppController } from './app.controller.js'; import { AppController } from './app.controller.js';
import { AppService } from './app.service.js'; import { AppService } from './app.service.js';
import { ListingsController } from './listings/listings.controller.js';
import { FileService } from './file/file.service.js'; import { FileService } from './file/file.service.js';
import { AuthService } from './auth/auth.service.js'; import { AuthService } from './auth/auth.service.js';
import { AuthController } from './auth/auth.controller.js'; import { AuthController } from './auth/auth.controller.js';
@ -25,6 +24,7 @@ import { UserModule } from './user/user.module.js';
import { ListingsModule } from './listings/listings.module.js'; import { ListingsModule } from './listings/listings.module.js';
import { AccountModule } from './account/account.module.js'; import { AccountModule } from './account/account.module.js';
import { SelectOptionsModule } from './select-options/select-options.module.js'; import { SelectOptionsModule } from './select-options/select-options.module.js';
import { CommercialPropertyListingsController } from './listings/commercial-property-listings.controller.js';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);

View File

@ -1,25 +1,23 @@
import { Body, Controller, Delete, Get, Inject, Param, Post, Put } from '@nestjs/common'; import { Body, Controller, Delete, Get, Inject, Param, Post, Put } from '@nestjs/common';
import { FileService } from '../file/file.service.js'; import { FileService } from '../file/file.service.js';
import { convertStringToNullUndefined } from '../utils.js'; import { convertStringToNullUndefined } from '../utils.js';
import { RedisService } from '../redis/redis.service.js';
import { ListingsService } from './listings.service.js'; import { ListingsService } from './listings.service.js';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston'; import { Logger } from 'winston';
@Controller('listings') @Controller('business-listings')
export class ListingsController { export class BusinessListingsController {
// private readonly logger = new Logger(ListingsController.name);
constructor(private readonly listingsService:ListingsService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) { constructor(private readonly listingsService:ListingsService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
} }
@Get() @Get()
findAll(): any { findAll(): any {
return this.listingsService.getAllListings(); return this.listingsService.getAllBusinessListings();
} }
@Get(':id') @Get(':id')
findById(@Param('id') id:string): any { findById(@Param('id') id:string): any {
return this.listingsService.getListingById(id); return this.listingsService.getBusinessListingById(id);
} }
// @Get(':type/:location/:minPrice/:maxPrice/:realEstateChecked') // @Get(':type/:location/:minPrice/:maxPrice/:realEstateChecked')
// find(@Param('type') type:string,@Param('location') location:string,@Param('minPrice') minPrice:string,@Param('maxPrice') maxPrice:string,@Param('realEstateChecked') realEstateChecked:boolean): any { // find(@Param('type') type:string,@Param('location') location:string,@Param('minPrice') minPrice:string,@Param('maxPrice') maxPrice:string,@Param('realEstateChecked') realEstateChecked:boolean): any {
@ -27,24 +25,16 @@ export class ListingsController {
// } // }
@Post('search') @Post('search')
find(@Body() criteria: any): any { find(@Body() criteria: any): any {
return this.listingsService.find(criteria); return this.listingsService.findBusinessListings(criteria);
}
/**
* @param listing updates a new listing
*/
@Put(':id')
updateById(@Param('id') id:string, @Body() listing: any){
this.logger.info(`Update by ID: ${id}`);
this.listingsService.setListing(listing,id)
} }
/** /**
* @param listing creates a new listing * @param listing creates a new listing
*/ */
@Post() @Post()
create(@Body() listing: any){ save(@Body() listing: any){
this.logger.info(`Create Listing`); this.logger.info(`Save Listing`);
this.listingsService.setListing(listing) this.listingsService.saveListing(listing)
} }
/** /**
@ -52,7 +42,7 @@ export class ListingsController {
*/ */
@Delete(':id') @Delete(':id')
deleteById(@Param('id') id:string){ deleteById(@Param('id') id:string){
this.listingsService.deleteListing(id) this.listingsService.deleteBusinessListing(id)
} }
} }

View File

@ -0,0 +1,44 @@
import { Body, Controller, Delete, Get, Inject, Param, Post } from '@nestjs/common';
import { ListingsService } from './listings.service.js';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
@Controller('commercial-property-listings')
export class CommercialPropertyListingsController {
constructor(private readonly listingsService:ListingsService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
}
@Get()
findAll(): any {
return this.listingsService.getAllCommercialListings();
}
@Get(':id')
findById(@Param('id') id:string): any {
return this.listingsService.getCommercialPropertyListingById(id);
}
@Post('search')
find(@Body() criteria: any): any {
return this.listingsService.findCommercialPropertyListings(criteria);
}
/**
* @param listing creates a new listing
*/
@Post()
save(@Body() listing: any){
this.logger.info(`Save Listing`);
this.listingsService.saveListing(listing)
}
/**
* @param id deletes a listing
*/
@Delete(':id')
deleteById(@Param('id') id:string){
this.listingsService.deleteCommercialPropertyListing(id)
}
}

View File

@ -1,9 +1,12 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ListingsController } from './listings.controller.js'; import { BusinessListingsController } from './business-listings.controller.js';
import { ListingsService } from './listings.service.js'; import { ListingsService } from './listings.service.js';
import { CommercialPropertyListingsController } from './commercial-property-listings.controller.js';
import { RedisModule } from '../redis/redis.module.js';
@Module({ @Module({
controllers: [ListingsController], imports: [RedisModule],
controllers: [BusinessListingsController, CommercialPropertyListingsController],
providers: [ListingsService] providers: [ListingsService]
}) })
export class ListingsModule {} export class ListingsModule {}

View File

@ -1,65 +1,118 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { import {
BusinessListing, BusinessListing,
InvestmentsListing, CommercialPropertyListing,
ListingCriteria, ListingCriteria,
ProfessionalsBrokersListing, ListingType
} from '../models/main.model.js'; } from '../models/main.model.js';
import { convertStringToNullUndefined } from '../utils.js'; import { convertStringToNullUndefined } from '../utils.js';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston'; import { Logger } from 'winston';
import { EntityData, EntityId, Repository, Schema, SchemaDefinition } from 'redis-om';
import { REDIS_CLIENT } from '../redis/redis.module.js';
@Injectable() @Injectable()
export class ListingsService { export class ListingsService {
constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {} businessListingRepository:Repository;
commercialPropertyListingRepository:Repository;
async setListing( baseListingSchemaDef : SchemaDefinition = {
value: BusinessListing | ProfessionalsBrokersListing | InvestmentsListing, id: { type: 'string' },
id?: string, userId: { type: 'string' },
) { listingsCategory: { type: 'string' },
// if (!id) { title: { type: 'string' },
// id = await this.redisService.getId(LISTINGS); description: { type: 'string' },
// value.id = id; country: { type: 'string' },
// this.logger.info(`No ID - creating new one:${id}`) state:{ type: 'string' },
// } else { city:{ type: 'string' },
// this.logger.info(`ID available:${id}`) zipCode: { type: 'number' },
// } type: { type: 'string' },
//this.redisService.setJson(id, value); price: { type: 'number' },
favoritesForUser:{ type: 'string[]' },
hideImage:{ type: 'boolean' },
draft:{ type: 'boolean' },
created:{ type: 'date' },
updated:{ type: 'date' }
}
businessListingSchemaDef : SchemaDefinition = {
...this.baseListingSchemaDef,
salesRevenue: { type: 'number' },
cashFlow: { type: 'number' },
employees: { type: 'number' },
established: { type: 'number' },
internalListingNumber: { type: 'number' },
realEstateIncluded:{ type: 'boolean' },
leasedLocation:{ type: 'boolean' },
franchiseResale:{ type: 'boolean' },
supportAndTraining: { type: 'string' },
reasonForSale: { type: 'string' },
brokerLicencing: { type: 'string' },
internals: { type: 'string' },
}
commercialPropertyListingSchemaDef : SchemaDefinition = {
...this.baseListingSchemaDef,
imageNames:{ type: 'string[]' },
}
businessListingSchema = new Schema('businessListing',this.businessListingSchemaDef, {
dataStructure: 'JSON'
})
commercialPropertyListingSchema = new Schema('commercialPropertyListing',this.commercialPropertyListingSchemaDef, {
dataStructure: 'JSON'
})
constructor(@Inject(REDIS_CLIENT) private readonly redis: any){
this.businessListingRepository = new Repository(this.businessListingSchema, redis);
this.commercialPropertyListingRepository = new Repository(this.commercialPropertyListingSchema, redis)
this.businessListingRepository.createIndex();
this.commercialPropertyListingRepository.createIndex();
}
async saveListing(listing: BusinessListing | CommercialPropertyListing) {
const repo=listing.listingsCategory==='business'?this.businessListingRepository:this.commercialPropertyListingRepository;
let result
if (listing.id){
result = await repo.save(listing.id,listing as any)
} else {
result = await repo.save(listing as any)
listing.id=result[EntityId];
result = await repo.save(listing.id,listing as any)
}
return result;
}
async getCommercialPropertyListingById(id: string) {
return await this.commercialPropertyListingRepository.fetch(id)
}
async getBusinessListingById(id: string) {
return await this.businessListingRepository.fetch(id)
}
async deleteBusinessListing(id: string){
return await this.businessListingRepository.remove(id);
}
async deleteCommercialPropertyListing(id: string){
return await this.commercialPropertyListingRepository.remove(id);
}
async getAllBusinessListings(start?: number, end?: number) {
return await this.businessListingRepository.search().return.all()
}
async getAllCommercialListings(start?: number, end?: number) {
return await this.commercialPropertyListingRepository.search().return.all()
} }
async getListingById(id: string) {
//return await this.redisService.getJson(id, LISTINGS); async findBusinessListings(criteria:ListingCriteria): Promise<any> {
let listings = await this.getAllBusinessListings();
return this.find(criteria,listings);
} }
deleteListing(id: string){ async findCommercialPropertyListings(criteria:ListingCriteria): Promise<any> {
//this.redisService.delete(id); let listings = await this.getAllCommercialListings();
this.logger.info(`delete listing with ID:${id}`) return this.find(criteria,listings);
} }
async getAllListings(start?: number, end?: number) { async find(criteria:ListingCriteria, listings: any[]): Promise<any> {
// const searchResult = await this.redisService.search(LISTINGS, '*');
// const listings = searchResult.slice(1).reduce((acc, item, index, array) => {
// if (index % 2 === 1) {
// try {
// const listing = JSON.parse(item[1]);
// acc.push(listing);
// } catch (error) {
// console.error('Fehler beim Parsen des JSON-Strings: ', error);
// }
// }
// return acc;
// }, []);
// return listings;
return [];
}
async find(criteria:ListingCriteria): Promise<any> {
let listings = await this.getAllListings();
listings=listings.filter(l=>l.listingsCategory===criteria.listingsCategory); listings=listings.filter(l=>l.listingsCategory===criteria.listingsCategory);
if (convertStringToNullUndefined(criteria.type)){ if (convertStringToNullUndefined(criteria.type)){
console.log(criteria.type); console.log(criteria.type);
listings=listings.filter(l=>l.type===criteria.type); listings=listings.filter(l=>l.type===criteria.type);
} }
if (convertStringToNullUndefined(criteria.location)){ if (convertStringToNullUndefined(criteria.state)){
console.log(criteria.location); console.log(criteria.state);
listings=listings.filter(l=>l.location===criteria.location); listings=listings.filter(l=>l.state===criteria.state);
} }
if (convertStringToNullUndefined(criteria.minPrice)){ if (convertStringToNullUndefined(criteria.minPrice)){
console.log(criteria.minPrice); console.log(criteria.minPrice);

View File

@ -1,9 +0,0 @@
import { Body, Controller, Get, HttpStatus, Param, Post, Res } from '@nestjs/common';
import { Response } from 'express';
import { RedisService } from './redis.service.js';
@Controller('redis')
export class RedisController {
constructor(private redisService:RedisService){}
}

View File

@ -24,8 +24,6 @@ export class RedisModule {}
export const REDIS_CLIENT = "REDIS_CLIENT"; export const REDIS_CLIENT = "REDIS_CLIENT";
// redis.service.ts // redis.service.ts
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { RedisService } from './redis.service.js';
import { RedisController } from './redis.controller.js';
import { createClient } from 'redis'; import { createClient } from 'redis';

View File

@ -1,50 +0,0 @@
// redis.service.ts
import { Injectable } from '@nestjs/common';
import { BusinessListing, InvestmentsListing,ProfessionalsBrokersListing } from '../models/main.model.js';
import fs from 'fs-extra';
import { createClient } from 'redis';
export const LISTINGS = 'LISTINGS';
export const SUBSCRIPTIONS = 'SUBSCRIPTIONS';
export const USERS = 'USERS'
export const redis = createClient({ url: 'redis://localhost:6379' })
@Injectable()
export class RedisService {
//private redis = new Redis(); // Verbindungsparameter nach Bedarf anpassen
// private redis = new Redis({
// port: 6379, // Der TLS-Port von Redis
//host: '2.56.188.138',
// host: '127.0.0.1',
//password: 'bizmatchRedis:5600Wuppertal11', // ACL Benutzername und Passwort
// tls: {
// key: fs.readFileSync('/home/aknuth/ssl/private/redis.key'),
// cert: fs.readFileSync('/home/aknuth/ssl/certs/redis.crt')
// }
// });
// ######################################
// general methods
// ######################################
// async setJson(id: string, value: any): Promise<void> {
// await this.redis.call("JSON.SET", `${LISTINGS}:${id}`, "$", JSON.stringify(value));
// }
// async delete(id: string): Promise<void> {
// await this.redis.del(`${LISTINGS}:${id}`);
// }
// async getJson(id: string, prefix:string): Promise<any> {
// const result:string = await this.redis.call("JSON.GET", `${prefix}:${id}`) as string;
// return JSON.parse(result);
// }
// async getId(prefix:'LISTINGS'|'SUBSCRIPTIONS'|'USERS'):Promise<string>{
// const counter = await this.redis.call("INCR",`${prefix}_ID_COUNTER`) as number;
// return counter.toString().padStart(15, '0')
// }
// async search(prefix:'LISTINGS'|'SUBSCRIPTIONS'|'USERS',clause:string):Promise<any>{
// const result = await this.redis.call(`FT.SEARCH`, `${prefix}_INDEX`, `${clause}`, 'LIMIT', 0, 200);
// return result;
// }
}

View File

@ -30,7 +30,7 @@ export class SelectOptionsService {
public listingCategories: Array<KeyValue> = [ public listingCategories: Array<KeyValue> = [
{ name: 'Business', value: 'business' }, { name: 'Business', value: 'business' },
// { name: 'Professionals/Brokers Directory', value: 'professionals_brokers' }, // { name: 'Professionals/Brokers Directory', value: 'professionals_brokers' },
{ name: 'Investment Property', value: 'investment' }, { name: 'Commercial Property', value: 'commercialProperty' },
] ]
public categories: Array<KeyValueStyle> = [ public categories: Array<KeyValueStyle> = [
{ name: 'Broker', value: 'broker', icon:'pi-image',bgColorClass:'bg-green-100',textColorClass:'text-green-600' }, { name: 'Broker', value: 'broker', icon:'pi-image',bgColorClass:'bg-green-100',textColorClass:'text-green-600' },

View File

@ -4,13 +4,12 @@ import { Entity, Repository, Schema } from 'redis-om';
import { User } from '../models/main.model.js'; import { User } from '../models/main.model.js';
import { REDIS_CLIENT } from '../redis/redis.module.js'; import { REDIS_CLIENT } from '../redis/redis.module.js';
import { UserEntity } from '../models/server.model.js'; import { UserEntity } from '../models/server.model.js';
// export const redis = createClient({ url: 'redis://localhost:6379' })
@Injectable() @Injectable()
export class UserService { export class UserService {
userRepository:Repository; userRepository:Repository;
userSchema = new Schema('user',{ userSchema = new Schema('user',{
// id: string; id: { type: 'string' },
firstname: { type: 'string' }, firstname: { type: 'string' },
lastname: { type: 'string' }, lastname: { type: 'string' },
email: { type: 'string' }, email: { type: 'string' },
@ -25,22 +24,14 @@ export class UserService {
}, { }, {
dataStructure: 'JSON' dataStructure: 'JSON'
}) })
constructor(@Inject(REDIS_CLIENT) private readonly client: any){ constructor(@Inject(REDIS_CLIENT) private readonly redis: any){
// const redis = createClient({ url: 'redis://localhost:6379' }) this.userRepository = new Repository(this.userSchema, redis)
this.userRepository = new Repository(this.userSchema, client)
} }
async getUserById( id:string){ async getUserById( id:string){
return await this.userRepository.fetch(id); return await this.userRepository.fetch(id);
} }
async saveUser(user:any):Promise<UserEntity>{ async saveUser(user:any):Promise<UserEntity>{
return await this.userRepository.save(user.id,user) as UserEntity return await this.userRepository.save(user.id,user) as UserEntity
} }
// createUser(){
// }
// updateById(id:string){
// }
} }

View File

@ -36,7 +36,7 @@
"on-change": "^5.0.1", "on-change": "^5.0.1",
"primeflex": "^3.3.1", "primeflex": "^3.3.1",
"primeicons": "^6.0.1", "primeicons": "^6.0.1",
"primeng": "^17.6.0", "primeng": "^17.10.0",
"rxjs": "~7.8.1", "rxjs": "~7.8.1",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"urlcat": "^3.1.0", "urlcat": "^3.1.0",

View File

@ -1,4 +1,4 @@
import { APP_INITIALIZER, ApplicationConfig, importProvidersFrom } from '@angular/core'; import { APP_INITIALIZER, ApplicationConfig, LOCALE_ID, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { routes } from './app.routes'; import { routes } from './app.routes';
@ -26,7 +26,8 @@ export const appConfig: ApplicationConfig = {
multi: true, multi: true,
deps: [SelectOptionsService], deps: [SelectOptionsService],
}, },
provideRouter(routes),provideAnimations() provideRouter(routes),provideAnimations(),
// {provide: LOCALE_ID, useValue: 'en-US' }
] ]
}; };
function initUserService(userService:UserService) { function initUserService(userService:UserService) {

View File

@ -45,8 +45,8 @@ export class HeaderComponent {
fragment:'' fragment:''
}, },
{ {
label: 'Investment Property', label: 'Commercial Property',
routerLink: '/listings/investment', routerLink: '/listings/commercialProperty',
fragment:'' fragment:''
} }
]; ];

View File

@ -0,0 +1,115 @@
// @layer primeng {
app-inputnumber,
.p-inputnumber {
display: inline-flex;
}
.p-inputnumber-button {
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
}
.p-inputnumber-buttons-stacked .p-button.p-inputnumber-button .p-button-label,
.p-inputnumber-buttons-horizontal .p-button.p-inputnumber-button .p-button-label {
display: none;
}
.p-inputnumber-buttons-stacked .p-button.p-inputnumber-button-up {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
padding: 0;
}
.p-inputnumber-buttons-stacked .p-inputnumber-input {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.p-inputnumber-buttons-stacked .p-button.p-inputnumber-button-down {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-left-radius: 0;
padding: 0;
}
.p-inputnumber-buttons-stacked .p-inputnumber-button-group {
display: flex;
flex-direction: column;
}
.p-inputnumber-buttons-stacked .p-inputnumber-button-group .p-button.p-inputnumber-button {
flex: 1 1 auto;
}
.p-inputnumber-buttons-horizontal .p-button.p-inputnumber-button-up {
order: 3;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.p-inputnumber-buttons-horizontal .p-inputnumber-input {
order: 2;
border-radius: 0;
}
.p-inputnumber-buttons-horizontal .p-button.p-inputnumber-button-down {
order: 1;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.p-inputnumber-buttons-vertical {
flex-direction: column;
}
.p-inputnumber-buttons-vertical .p-button.p-inputnumber-button-up {
order: 1;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
width: 100%;
}
.p-inputnumber-buttons-vertical .p-inputnumber-input {
order: 2;
border-radius: 0;
text-align: center;
}
.p-inputnumber-buttons-vertical .p-button.p-inputnumber-button-down {
order: 3;
border-top-left-radius: 0;
border-top-right-radius: 0;
width: 100%;
}
.p-inputnumber-input {
flex: 1 1 auto;
}
.p-fluid app-inputnumber,
.p-fluid .p-inputnumber {
width: 100%;
}
.p-fluid .p-inputnumber .p-inputnumber-input {
width: 1%;
}
.p-fluid .p-inputnumber-buttons-vertical .p-inputnumber-input {
width: 100%;
}
.p-inputnumber-clear-icon {
position: absolute;
top: 50%;
margin-top: -0.5rem;
cursor: pointer;
}
.p-inputnumber-clearable {
position: relative;
}
// }

File diff suppressed because it is too large Load Diff

View File

@ -85,7 +85,7 @@
<div class="text-900 w-full md:w-10">{{listing.category}}</div> <div class="text-900 w-full md:w-10">{{listing.category}}</div>
</li> </li>
} --> } -->
@if (listing && (listing.listingsCategory==='investment')){ @if (listing && (listing.listingsCategory==='commercialProperty')){
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground"> <li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
<div class="text-500 w-full md:w-2 font-medium">Located in</div> <div class="text-500 w-full md:w-2 font-medium">Located in</div>
<div class="text-900 w-full md:w-10">{{selectOptions.getState(listing.state)}}</div> <div class="text-900 w-full md:w-10">{{selectOptions.getState(listing.state)}}</div>

View File

@ -5,7 +5,7 @@
<div <div
class="align-items-center flex-grow-1 justify-content-between hidden lg:flex absolute lg:static w-full left-0 top-100 px-6 lg:px-0 shadow-2 lg:shadow-none z-2"> class="align-items-center flex-grow-1 justify-content-between hidden lg:flex absolute lg:static w-full left-0 top-100 px-6 lg:px-0 shadow-2 lg:shadow-none z-2">
<section></section> <section></section>
<ul <!-- <ul
class="list-none p-0 m-0 flex lg:align-items-center text-blue-900 select-none flex-column lg:flex-row cursor-pointer"> class="list-none p-0 m-0 flex lg:align-items-center text-blue-900 select-none flex-column lg:flex-row cursor-pointer">
<li> <li>
<a pRipple <a pRipple
@ -25,7 +25,7 @@
<span>Pricing</span> <span>Pricing</span>
</a> </a>
</li> </li>
</ul> </ul> -->
<div <div
class="flex justify-content-between lg:block border-top-1 lg:border-top-none border-gray-800 py-3 lg:py-0 mt-3 lg:mt-0"> class="flex justify-content-between lg:block border-top-1 lg:border-top-none border-gray-800 py-3 lg:py-0 mt-3 lg:mt-0">
<!-- <p-button label="Account" class="ml-3 font-bold" [outlined]="true" severity="secondary" [routerLink]="['/account']"></p-button> --> <!-- <p-button label="Account" class="ml-3 font-bold" [outlined]="true" severity="secondary" [routerLink]="['/account']"></p-button> -->
@ -63,9 +63,9 @@
<li><button pButton pRipple icon="pi pi-globe" (click)="activeTabAction = 'professionals_brokers'" <li><button pButton pRipple icon="pi pi-globe" (click)="activeTabAction = 'professionals_brokers'"
label="Professionals/Brokers Directory" label="Professionals/Brokers Directory"
[ngClass]="{'p-button-text text-700': activeTabAction != 'professionals_brokers'}"></button></li> [ngClass]="{'p-button-text text-700': activeTabAction != 'professionals_brokers'}"></button></li>
<li><button pButton pRipple icon="pi pi-shield" (click)="activeTabAction = 'investment'" <li><button pButton pRipple icon="pi pi-shield" (click)="activeTabAction = 'commercialProperty'"
label="Investment Property" label="Commercial Property"
[ngClass]="{'p-button-text text-700': activeTabAction != 'investment'}"></button> [ngClass]="{'p-button-text text-700': activeTabAction != 'commercialProperty'}"></button>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -29,24 +29,24 @@
offLabel="Real Estate included"></p-toggleButton> offLabel="Real Estate included"></p-toggleButton>
</div> </div>
} }
@if (listingCategory==='investment'){ @if (listingCategory==='commercialProperty'){
<div class="col-2"> <div class="col-2">
<p-dropdown [options]="states" [(ngModel)]="criteria.location" optionLabel="name" optionValue="value" <p-dropdown [options]="states" [(ngModel)]="criteria.state" optionLabel="name" optionValue="value"
[showClear]="true" placeholder="Location" [style]="{ width: '100%'}"></p-dropdown> [showClear]="true" placeholder="Location" [style]="{ width: '100%'}"></p-dropdown>
</div> </div>
} }
@if (listingCategory==='professionals_brokers'){ <!-- @if (listingCategory==='professionals_brokers'){ -->
<div class="col-2"> <!-- <div class="col-2">
<p-dropdown [options]="selectOptions.categories" [(ngModel)]="criteria.category" optionLabel="name" <p-dropdown [options]="selectOptions.categories" [(ngModel)]="criteria.category" optionLabel="name"
optionValue="value" [showClear]="true" placeholder="Category" optionValue="value" [showClear]="true" placeholder="Category"
[style]="{ width: '100%'}"></p-dropdown> [style]="{ width: '100%'}"></p-dropdown>
</div> </div>
<div class="col-2"> <div class="col-2">
<p-dropdown [options]="states" [(ngModel)]="criteria.location" optionLabel="name" optionValue="value" <p-dropdown [options]="states" [(ngModel)]="criteria.state" optionLabel="name" optionValue="value"
[showClear]="true" placeholder="Location" [style]="{ width: '100%'}"></p-dropdown> [showClear]="true" placeholder="Location" [style]="{ width: '100%'}"></p-dropdown>
</div> </div> -->
} <!-- } -->
<div [ngClass]="{'col-offset-9':type==='investment','col-offset-7':type==='professionals_brokers'}" class="col-1"> <div [ngClass]="{'col-offset-9':type==='commercialProperty','col-offset-7':type==='professionals_brokers'}" class="col-1">
<p-button label="Refine" (click)="search()"></p-button> <p-button label="Refine" (click)="search()"></p-button>
</div> </div>
</div> </div>
@ -70,24 +70,15 @@
<span class="text-900 font-medium text-2xl">{{selectOptions.getBusiness(listing.type)}}</span> <span class="text-900 font-medium text-2xl">{{selectOptions.getBusiness(listing.type)}}</span>
</div> </div>
} }
@if (listing.listingsCategory==='professionals_brokers'){ @if (listing.listingsCategory==='commercialProperty'){
<div class="flex align-items-center"> <!-- <div class="flex align-items-center">
<span [class]="selectOptions.getBgColor(listing.category)" class="inline-flex border-circle align-items-center justify-content-center mr-3"
style="width:38px;height:38px">
<i [class]="selectOptions.getIconAndTextColor(listing.category)" class="text-xl"></i>
</span>
<span class="text-900 font-medium text-2xl">{{selectOptions.getCategory(listing.category)}}</span>
</div>
}
@if (listing.listingsCategory==='investment'){
<div class="flex align-items-center">
<span <span
class="inline-flex border-circle align-items-center justify-content-center bg-green-100 mr-3" class="inline-flex border-circle align-items-center justify-content-center bg-green-100 mr-3"
style="width:38px;height:38px"> style="width:38px;height:38px">
<i class="pi pi-globe text-xl text-green-600"></i> <i class="pi pi-globe text-xl text-green-600"></i>
</span> </span>
<span class="text-900 font-medium text-2xl">Investment</span> <span class="text-900 font-medium text-2xl">Investment</span>
</div> </div> -->
} }
<div class="text-900 my-3 text-xl font-medium">{{listing.title}}</div> <div class="text-900 my-3 text-xl font-medium">{{listing.title}}</div>
@if (listing.listingsCategory==='business'){ @if (listing.listingsCategory==='business'){
@ -97,17 +88,11 @@
<p class="mt-0 mb-1 text-700 line-height-3">Location: {{selectOptions.getState(listing.state)}}</p> <p class="mt-0 mb-1 text-700 line-height-3">Location: {{selectOptions.getState(listing.state)}}</p>
<p class="mt-0 mb-1 text-700 line-height-3">Established: {{listing.established}}</p> <p class="mt-0 mb-1 text-700 line-height-3">Established: {{listing.established}}</p>
} }
@if (listing.listingsCategory==='professionals_brokers'){ @if (listing.listingsCategory==='commercialProperty'){
<!-- <p class="mt-0 mb-1 text-700 line-height-3">Category: {{listing.category}}</p> --> <!-- <p class="mt-0 mb-1 text-700 line-height-3">Location: {{selectOptions.getState(listing.state)}}</p>
<p class="mt-0 mb-1 text-700 line-height-3">Location: {{selectOptions.getState(listing.state)}}</p>
<p class="mt-0 mb-1 text-700 line-height-3">EMail: {{listing.email}}</p> <p class="mt-0 mb-1 text-700 line-height-3">EMail: {{listing.email}}</p>
<p class="mt-0 mb-1 text-700 line-height-3">Website: {{listing.website}}</p> <p class="mt-0 mb-1 text-700 line-height-3">Website: {{listing.website}}</p>
} <p class="mt-0 mb-1 text-700 line-height-3">Phone Number: {{listing.phoneNumber}}</p> -->
@if (listing.listingsCategory==='investment'){
<p class="mt-0 mb-1 text-700 line-height-3">Location: {{selectOptions.getState(listing.state)}}</p>
<p class="mt-0 mb-1 text-700 line-height-3">EMail: {{listing.email}}</p>
<p class="mt-0 mb-1 text-700 line-height-3">Website: {{listing.website}}</p>
<p class="mt-0 mb-1 text-700 line-height-3">Phone Number: {{listing.phoneNumber}}</p>
} }
<div class="mt-auto ml-auto"> <div class="mt-auto ml-auto">
<img *ngIf="!listing.hideImage" src="{{environment.apiBaseUrl}}/profile_{{listing.userId}}" (error)="imageErrorHandler(listing)" class="rounded-image"/> <img *ngIf="!listing.hideImage" src="{{environment.apiBaseUrl}}/profile_{{listing.userId}}" (error)="imageErrorHandler(listing)" class="rounded-image"/>

View File

@ -40,7 +40,7 @@ export class ListingsComponent {
first: number = 0; first: number = 0;
rows: number = 12; rows: number = 12;
totalRecords:number = 0; totalRecords:number = 0;
public listingCategory: 'business' | 'professionals_brokers' | 'investment' | undefined; public listingCategory: 'business' | 'commercialProperty' | undefined;
constructor(public selectOptions: SelectOptionsService, private listingsService:ListingsService,private activatedRoute: ActivatedRoute, private router:Router, private cdRef:ChangeDetectorRef) { constructor(public selectOptions: SelectOptionsService, private listingsService:ListingsService,private activatedRoute: ActivatedRoute, private router:Router, private cdRef:ChangeDetectorRef) {
this.criteria = onChange(getCriteriaStateObject(),getSessionStorageHandler); this.criteria = onChange(getCriteriaStateObject(),getSessionStorageHandler);

View File

@ -47,7 +47,7 @@
<p-autoComplete [(ngModel)]="listing.city" [suggestions]="suggestions" (completeMethod)="search($event)"></p-autoComplete> <p-autoComplete [(ngModel)]="listing.city" [suggestions]="suggestions" (completeMethod)="search($event)"></p-autoComplete>
</div> </div>
</div> </div>
<!-- @if (listing.listingsCategory==='investment'){ <!-- @if (listing.listingsCategory==='commercialProperty'){
<div> <div>
<div class="mb-4"> <div class="mb-4">
<label for="email" class="block font-medium text-900 mb-2">Email</label> <label for="email" class="block font-medium text-900 mb-2">Email</label>
@ -76,28 +76,29 @@
<div class="grid"> <div class="grid">
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="price" class="block font-medium text-900 mb-2">Price</label> <label for="price" class="block font-medium text-900 mb-2">Price</label>
<p-inputNumber mode="currency" currency="USD" inputId="price" type="text" [(ngModel)]="listing.price"></p-inputNumber> <!-- <p-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price" ></p-inputNumber> -->
<app-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price"></app-inputNumber>
</div> </div>
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="salesRevenue" class="block font-medium text-900 mb-2">Sales Revenue</label> <label for="salesRevenue" class="block font-medium text-900 mb-2">Sales Revenue</label>
<p-inputNumber mode="currency" currency="USD" inputId="salesRevenue" type="text" [(ngModel)]="listing.salesRevenue"></p-inputNumber> <app-inputNumber mode="currency" currency="USD" inputId="salesRevenue" [(ngModel)]="listing.salesRevenue"></app-inputNumber>
</div> </div>
</div> </div>
<div class="grid"> <div class="grid">
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="cashFlow" class="block font-medium text-900 mb-2">Cash Flow</label> <label for="cashFlow" class="block font-medium text-900 mb-2">Cash Flow</label>
<p-inputNumber mode="currency" currency="USD" inputId="cashFlow" type="text" [(ngModel)]="listing.cashFlow"></p-inputNumber> <app-inputNumber mode="currency" currency="USD" inputId="cashFlow" [(ngModel)]="listing.cashFlow"></app-inputNumber>
</div> </div>
</div> </div>
<div class="grid"> <div class="grid">
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="employees" class="block font-medium text-900 mb-2">Years Established Since</label> <label for="employees" class="block font-medium text-900 mb-2">Years Established Since</label>
<p-inputNumber mode="employees" mode="decimal" inputId="employees" type="text" [(ngModel)]="listing.established"></p-inputNumber> <app-inputNumber mode="decimal" inputId="established" [(ngModel)]="listing.established"></app-inputNumber>
</div> </div>
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="employees" class="block font-medium text-900 mb-2">Employees</label> <label for="employees" class="block font-medium text-900 mb-2">Employees</label>
<p-inputNumber mode="employees" mode="decimal" inputId="employees" type="text" [(ngModel)]="listing.employees"></p-inputNumber> <app-inputNumber mode="decimal" inputId="employees" [(ngModel)]="listing.employees"></app-inputNumber>
</div> </div>
</div> </div>
<div class="grid"> <div class="grid">
@ -130,7 +131,7 @@
</div> </div>
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="internalListingNumber" class="block font-medium text-900 mb-2">Internal Listing Number</label> <label for="internalListingNumber" class="block font-medium text-900 mb-2">Internal Listing Number</label>
<p-inputNumber mode="decimal" inputId="internalListingNumber" type="text" [(ngModel)]="listing.internalListingNumber"></p-inputNumber> <app-inputNumber mode="decimal" inputId="internalListingNumber" type="text" [(ngModel)]="listing.internalListingNumber"></app-inputNumber>
</div> </div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
@ -149,9 +150,9 @@
} }
<div> <div>
@if (mode==='create'){ @if (mode==='create'){
<button pButton pRipple label="Post Listing" class="w-auto" (click)="create()"></button> <button pButton pRipple label="Post Listing" class="w-auto" (click)="save()"></button>
} @else { } @else {
<button pButton pRipple label="Update Listing" class="w-auto" (click)="update(listing.id)"></button> <button pButton pRipple label="Update Listing" class="w-auto" (click)="save()"></button>
} }
</div> </div>
</div> </div>

View File

@ -20,26 +20,27 @@ import { TableModule } from 'primeng/table';
import { createGenericObject } from '../../../utils/utils'; import { createGenericObject } from '../../../utils/utils';
import { ListingsService } from '../../../services/listings.service'; import { ListingsService } from '../../../services/listings.service';
import { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { InputNumberModule } from 'primeng/inputnumber';
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe'; import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module'; import { SharedModule } from '../../../shared/shared/shared.module';
import { MessageService } from 'primeng/api'; import { MessageService } from 'primeng/api';
import { AutoCompleteCompleteEvent, BusinessListing, ListingType, User } from '../../../../../../common-models/src/main.model'; import { AutoCompleteCompleteEvent, BusinessListing, ListingType, User } from '../../../../../../common-models/src/main.model';
import { GeoResult, GeoService } from '../../../services/geo.service'; import { GeoResult, GeoService } from '../../../services/geo.service';
import { InputNumberComponent, InputNumberModule } from '../../../components/inputnumber/inputnumber.component';
@Component({ @Component({
selector: 'create-listing', selector: 'create-listing',
standalone: true, standalone: true,
imports: [SharedModule,ArrayToStringPipe], imports: [SharedModule,ArrayToStringPipe, InputNumberModule],
providers:[MessageService], providers:[MessageService],
templateUrl: './edit-listing.component.html', templateUrl: './edit-listing.component.html',
styleUrl: './edit-listing.component.scss' styleUrl: './edit-listing.component.scss'
}) })
export class EditListingComponent { export class EditListingComponent {
listingCategory:'Business'|'Professionals/Brokers Directory'|'Investment Property'; listingCategory:'Business'|'Commercial Property';
category:string; category:string;
location:string; location:string;
mode:'edit'|'create'; mode:'edit'|'create';
@ -47,6 +48,7 @@ export class EditListingComponent {
listing:ListingType = createGenericObject<BusinessListing>(); listing:ListingType = createGenericObject<BusinessListing>();
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined; private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
user:User; user:User;
value:12;
constructor(public selectOptions:SelectOptionsService, constructor(public selectOptions:SelectOptionsService,
private router: Router, private router: Router,
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
@ -66,6 +68,7 @@ export class EditListingComponent {
async ngOnInit(){ async ngOnInit(){
if (this.mode==='edit'){ if (this.mode==='edit'){
this.listing=await lastValueFrom(this.listingsService.getListingById(this.id)); this.listing=await lastValueFrom(this.listingsService.getListingById(this.id));
this.listing.price=123456
} else { } else {
this.listing=createGenericObject<BusinessListing>(); this.listing=createGenericObject<BusinessListing>();
this.listing.userId=this.user.id this.listing.userId=this.user.id
@ -76,19 +79,19 @@ export class EditListingComponent {
// const lines = value.split('\n'); // const lines = value.split('\n');
// (<BusinessListing>this.listing).summary = lines.filter(l=>l.trim().length>0); // (<BusinessListing>this.listing).summary = lines.filter(l=>l.trim().length>0);
// } // }
async update(id:string){ // async update(id:string){
await this.listingsService.update(this.listing,this.listing.id); // await this.listingsService.update(this.listing,this.listing.id);
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing has been updated', life: 3000 }); // this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing has been updated', life: 3000 });
} // }
async create(){ async save(){
await this.listingsService.create(this.listing); await this.listingsService.save(this.listing);
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing has been created', life: 3000 }); this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 });
} }
suggestions: string[] | undefined; suggestions: string[] | undefined;
async search(event: AutoCompleteCompleteEvent) { async search(event: AutoCompleteCompleteEvent) {
const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query,this.listing.state))//[...Array(5).keys()].map(item => event.query + '-' + item); const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query,this.listing.state))
this.suggestions = result.map(r=>r.city).slice(0,5); this.suggestions = result.map(r=>r.city).slice(0,5);
} }
} }

View File

@ -12,22 +12,22 @@ export class ListingsService {
constructor(private http: HttpClient) { } constructor(private http: HttpClient) { }
getAllListings():Observable<BusinessListing[]>{ getAllListings():Observable<BusinessListing[]>{
return this.http.get<BusinessListing[]>(`${this.apiBaseUrl}/bizmatch/listings`); return this.http.get<BusinessListing[]>(`${this.apiBaseUrl}/bizmatch/business-listings`);
} }
async getListings(criteria:ListingCriteria):Promise<BusinessListing[]>{ async getListings(criteria:ListingCriteria):Promise<BusinessListing[]>{
const result = await lastValueFrom(this.http.post<BusinessListing[]>(`${this.apiBaseUrl}/bizmatch/listings/search`,criteria)); const result = await lastValueFrom(this.http.post<BusinessListing[]>(`${this.apiBaseUrl}/bizmatch/business-listings/search`,criteria));
return result; return result;
} }
getListingById(id:string):Observable<BusinessListing>{ getListingById(id:string):Observable<BusinessListing>{
return this.http.get<BusinessListing>(`${this.apiBaseUrl}/bizmatch/listings/${id}`); return this.http.get<BusinessListing>(`${this.apiBaseUrl}/bizmatch/business-listings/${id}`);
} }
async update(listing:any,id:string){ // async update(listing:any,id:string){
await lastValueFrom(this.http.put<BusinessListing>(`${this.apiBaseUrl}/bizmatch/listings/${id}`,listing)); // await lastValueFrom(this.http.put<BusinessListing>(`${this.apiBaseUrl}/bizmatch/listings/${id}`,listing));
} // }
async create(listing:any){ async save(listing:any){
await lastValueFrom(this.http.post<BusinessListing>(`${this.apiBaseUrl}/bizmatch/listings`,listing)); await lastValueFrom(this.http.post<BusinessListing>(`${this.apiBaseUrl}/bizmatch/business-listings`,listing));
} }
async deleteListing(id:string){ async deleteListing(id:string){
await lastValueFrom(this.http.delete<BusinessListing>(`${this.apiBaseUrl}/bizmatch/listings/${id}`)); await lastValueFrom(this.http.delete<BusinessListing>(`${this.apiBaseUrl}/bizmatch/business-listings/${id}`));
} }
} }

View File

@ -22,6 +22,7 @@ import { ConfirmPopupModule } from 'primeng/confirmpopup';
import { ToastModule } from 'primeng/toast'; import { ToastModule } from 'primeng/toast';
import { AutoCompleteModule } from 'primeng/autocomplete'; import { AutoCompleteModule } from 'primeng/autocomplete';
import { InputSwitchModule } from 'primeng/inputswitch'; import { InputSwitchModule } from 'primeng/inputswitch';
@NgModule({ @NgModule({
declarations: [], declarations: [],
imports: [ imports: [

View File

@ -4,5 +4,6 @@ import { AppComponent } from './app/app.component';
import { provideHttpClient } from '@angular/common/http'; import { provideHttpClient } from '@angular/common/http';
import { provideAnimations } from '@angular/platform-browser/animations'; import { provideAnimations } from '@angular/platform-browser/animations';
bootstrapApplication(AppComponent, appConfig) bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err)); .catch((err) => console.error(err));

View File

@ -46,32 +46,31 @@ export interface BusinessListing extends Listing {
brokerLicencing?: string; brokerLicencing?: string;
internals?: string; internals?: string;
} }
export interface ProfessionalsBrokersListing extends Listing { // export interface ProfessionalsBrokersListing extends Listing {
listingsCategory: 'professionals_brokers'; //enum // listingsCategory: 'professionals_brokers'; //enum
summary: string; // summary: string;
address?: string; // address?: string;
email?: string; // email?: string;
website?: string; // website?: string;
category?: 'Professionals' | 'Broker'; // category?: 'Professionals' | 'Broker';
} // }
export interface InvestmentsListing extends Listing { export interface CommercialPropertyListing extends Listing {
listingsCategory: 'investment'; //enum listingsCategory: 'commercialProperty'; //enum
email?: string; email?: string;
website?: string; website?: string;
phoneNumber?: string; phoneNumber?: string;
} }
export type ListingType = export type ListingType =
| BusinessListing | BusinessListing
| ProfessionalsBrokersListing | CommercialPropertyListing;
| InvestmentsListing;
export interface ListingCriteria { export interface ListingCriteria {
type:string, type:string,
location:string, state:string,
minPrice:string, minPrice:string,
maxPrice:string, maxPrice:string,
realEstateChecked:boolean, realEstateChecked:boolean,
listingsCategory:'business'|'professionals_brokers'|'investment', listingsCategory:'business'|'professionals_brokers'|'commercialProperty',
category:'professional|broker' category:'professional|broker'
} }
export interface UserBase { export interface UserBase {