geo coding, user service, listing for business
This commit is contained in:
parent
06f349d1c3
commit
6ad40b6dca
|
|
@ -1,7 +0,0 @@
|
|||
/// <reference types="multer" />
|
||||
import { FileService } from '../file/file.service.js';
|
||||
export declare class AccountController {
|
||||
private fileService;
|
||||
constructor(fileService: FileService);
|
||||
uploadFile(file: Express.Multer.File, id: string): void;
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export declare class AccountService {
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
import { AppService } from './app.service.js';
|
||||
export declare class AppController {
|
||||
private readonly appService;
|
||||
constructor(appService: AppService);
|
||||
getHello(): string;
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export declare class AppModule {
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export declare class AppService {
|
||||
getHello(): string;
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
[
|
||||
{
|
||||
"id":"1",
|
||||
"userId":"1",
|
||||
"listingsCategory": "business",
|
||||
"title": "Industrial Service Company In Corpus Christi For Sale - 1954",
|
||||
"summary": ["Asking price: $5,500,000","Sales revenue: $1,200,000","Net profit: $650,000"],
|
||||
"description": ["This company services a wide variety of industries. Asking price includes Business and the Real Estate and is approx 30,000 sq ft with room for expansion including approx 5 acres. Absentee run business."],
|
||||
"type": "2",
|
||||
"location": "Texas",
|
||||
"price":5500000,
|
||||
"salesRevenue":1200000,
|
||||
"cashFlow":650000,
|
||||
"brokerLicencing":"TREC Broker #516788",
|
||||
"established":1954,
|
||||
"realEstateIncluded":true
|
||||
},
|
||||
{
|
||||
"id":"2",
|
||||
"userId":"1",
|
||||
"listingsCategory": "business",
|
||||
"title": "Coastal Bend Manufacturing Business Plastic Injection For Sale - 1950",
|
||||
"summary": ["Asking price: $165,000","Sales revenue: Undisclosed","Net profit: Undisclosed"],
|
||||
"description": [""],
|
||||
"type": "12",
|
||||
"location": "Texas",
|
||||
"price":165000,
|
||||
"salesRevenue":null,
|
||||
"cashFlow":null,
|
||||
"brokerLicencing":"TREC Broker #516788",
|
||||
"established":1950,
|
||||
"realEstateIncluded":false
|
||||
},
|
||||
{
|
||||
"id":"3",
|
||||
"userId":"1",
|
||||
"listingsCategory": "business",
|
||||
"title": "Corner Property On Everhart South-side Corpus Christi For Sale - 1944",
|
||||
"summary": ["Asking price: $830,000","Sales revenue: Undisclosed","Net profit: Undisclosed"],
|
||||
"description": [""],
|
||||
"type": "3",
|
||||
"location": "Texas",
|
||||
"price":830000,
|
||||
"salesRevenue":null,
|
||||
"cashFlow":null,
|
||||
"brokerLicencing":"TREC Broker #516788",
|
||||
"established":1944,
|
||||
"realEstateIncluded":false
|
||||
},
|
||||
{
|
||||
"id":"4",
|
||||
"userId":"1",
|
||||
"listingsCategory": "business",
|
||||
"title": "Corpus Christi Dessert Business For Sale - 1941",
|
||||
"summary": ["Asking price: $124,900","Sales revenue: $225,000","Net profit: $50,000"],
|
||||
"description": [""],
|
||||
"type": "13",
|
||||
"location": "Texas",
|
||||
"price":830000,
|
||||
"salesRevenue":225000,
|
||||
"cashFlow":50000,
|
||||
"brokerLicencing":"TREC Broker #516788",
|
||||
"established":1941,
|
||||
"realEstateIncluded":false
|
||||
}
|
||||
]
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
[{
|
||||
"id":"1",
|
||||
"userId":"e0811669-c7eb-4e5e-a699-e8334d5c5b01",
|
||||
"level":"Business Broker",
|
||||
"start":"2024-02-12T21:54:20.603Z",
|
||||
"modified":"2024-02-12T21:54:20.603Z",
|
||||
"end":"9999-02-12T21:54:20.603Z",
|
||||
"status":"active",
|
||||
"invoices":[{
|
||||
"date":"2024-02-12T21:54:20.603Z",
|
||||
"id":"C991853B99",
|
||||
"price":0
|
||||
}]
|
||||
}]
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
/// <reference types="multer" />
|
||||
export declare class FileService {
|
||||
private subscriptions;
|
||||
constructor();
|
||||
private loadSubscriptions;
|
||||
getSubscriptions(): any;
|
||||
storeFile(file: Express.Multer.File, id: string): Promise<void>;
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import { ListingsService } from './listings.service.js';
|
||||
import { Logger } from 'winston';
|
||||
export declare class ListingsController {
|
||||
private readonly listingsService;
|
||||
private readonly logger;
|
||||
constructor(listingsService: ListingsService, logger: Logger);
|
||||
findAll(): any;
|
||||
findById(id: string): any;
|
||||
find(criteria: any): any;
|
||||
updateById(id: string, listing: any): void;
|
||||
create(listing: any): void;
|
||||
deleteById(id: string): void;
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import { BusinessListing, InvestmentsListing, ListingCriteria, ProfessionalsBrokersListing } from '../models/main.model.js';
|
||||
import { RedisService } from '../redis/redis.service.js';
|
||||
import { Logger } from 'winston';
|
||||
export declare class ListingsService {
|
||||
private redisService;
|
||||
private readonly logger;
|
||||
constructor(redisService: RedisService, logger: Logger);
|
||||
setListing(value: BusinessListing | ProfessionalsBrokersListing | InvestmentsListing, id?: string): Promise<void>;
|
||||
getListingById(id: string): Promise<any>;
|
||||
deleteListing(id: string): void;
|
||||
getAllListings(start?: number, end?: number): Promise<any>;
|
||||
find(criteria: ListingCriteria): Promise<any>;
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { MailService } from './mail.service.js';
|
||||
import { MailInfo } from '../models/server.model.js';
|
||||
export declare class MailController {
|
||||
private mailService;
|
||||
constructor(mailService: MailService);
|
||||
sendEMail(id: string, mailInfo: MailInfo): any;
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export declare class MailModule {
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import { MailerService } from '@nestjs-modules/mailer';
|
||||
import { AuthService } from '../auth/auth.service.js';
|
||||
import { MailInfo } from '../models/server.model.js';
|
||||
export declare class MailService {
|
||||
private mailerService;
|
||||
private authService;
|
||||
constructor(mailerService: MailerService, authService: AuthService);
|
||||
sendInquiry(userId: string, mailInfo: MailInfo): Promise<void>;
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<p>Hey {{ name }},</p>
|
||||
<p>You got an inquiry a</p>
|
||||
<p>
|
||||
{{inquiry}}
|
||||
</p>
|
||||
|
|
@ -1 +0,0 @@
|
|||
export {};
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
export interface KeyValue {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
export interface KeyValueStyle {
|
||||
name: string;
|
||||
value: string;
|
||||
icon: string;
|
||||
bgColorClass: string;
|
||||
textColorClass: string;
|
||||
}
|
||||
export type SelectOption<T = number> = {
|
||||
value: T;
|
||||
label: string;
|
||||
};
|
||||
export interface Listing {
|
||||
id: string;
|
||||
userId: string;
|
||||
title: string;
|
||||
description: Array<string>;
|
||||
location: string;
|
||||
favoritesForUser: Array<string>;
|
||||
hideImage?: boolean;
|
||||
created: Date;
|
||||
updated: Date;
|
||||
}
|
||||
export interface BusinessListing extends Listing {
|
||||
listingsCategory: 'business';
|
||||
summary: Array<string>;
|
||||
type: string;
|
||||
price?: number;
|
||||
realEstateIncluded?: boolean;
|
||||
salesRevenue?: number;
|
||||
cashFlow?: number;
|
||||
netProfit?: number;
|
||||
inventory?: string;
|
||||
employees?: number;
|
||||
established?: number;
|
||||
reasonForSale?: string;
|
||||
brokerLicencing?: string;
|
||||
internals?: string;
|
||||
}
|
||||
export interface ProfessionalsBrokersListing extends Listing {
|
||||
listingsCategory: 'professionals_brokers';
|
||||
summary: string;
|
||||
address?: string;
|
||||
email?: string;
|
||||
website?: string;
|
||||
category?: 'Professionals' | 'Broker';
|
||||
}
|
||||
export interface InvestmentsListing extends Listing {
|
||||
listingsCategory: 'investment';
|
||||
email?: string;
|
||||
website?: string;
|
||||
phoneNumber?: string;
|
||||
}
|
||||
export type ListingType = BusinessListing | ProfessionalsBrokersListing | InvestmentsListing;
|
||||
export interface ListingCriteria {
|
||||
type: string;
|
||||
location: string;
|
||||
minPrice: string;
|
||||
maxPrice: string;
|
||||
realEstateChecked: boolean;
|
||||
listingsCategory: 'business' | 'professionals_brokers' | 'investment';
|
||||
category: 'professional|broker';
|
||||
}
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
email: string;
|
||||
}
|
||||
export interface Subscription {
|
||||
id: string;
|
||||
userId: string;
|
||||
level: string;
|
||||
start: Date;
|
||||
modified: Date;
|
||||
end: Date;
|
||||
status: string;
|
||||
invoices: Array<Invoice>;
|
||||
}
|
||||
export interface Invoice {
|
||||
id: string;
|
||||
date: Date;
|
||||
price: number;
|
||||
}
|
||||
export interface JwtToken {
|
||||
exp: number;
|
||||
iat: number;
|
||||
auth_time: number;
|
||||
jti: string;
|
||||
iss: string;
|
||||
aud: string;
|
||||
sub: string;
|
||||
typ: string;
|
||||
azp: string;
|
||||
nonce: string;
|
||||
session_state: string;
|
||||
acr: string;
|
||||
realm_access: Realmaccess;
|
||||
resource_access: Resourceaccess;
|
||||
scope: string;
|
||||
sid: string;
|
||||
email_verified: boolean;
|
||||
name: string;
|
||||
preferred_username: string;
|
||||
given_name: string;
|
||||
family_name: string;
|
||||
email: string;
|
||||
user_id: string;
|
||||
}
|
||||
interface Resourceaccess {
|
||||
account: Realmaccess;
|
||||
}
|
||||
interface Realmaccess {
|
||||
roles: string[];
|
||||
}
|
||||
export interface PageEvent {
|
||||
first: number;
|
||||
rows: number;
|
||||
page: number;
|
||||
pageCount: number;
|
||||
}
|
||||
export {};
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
export interface KeyValue {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
export interface KeyValueStyle {
|
||||
name: string;
|
||||
value: string;
|
||||
icon:string;
|
||||
bgColorClass:string;
|
||||
textColorClass:string;
|
||||
}
|
||||
export type SelectOption<T = number> = {
|
||||
value: T;
|
||||
label: string;
|
||||
};
|
||||
export interface Listing {
|
||||
id: string;
|
||||
userId: string;
|
||||
title: string;
|
||||
description: Array<string>;
|
||||
location: string;//enum
|
||||
favoritesForUser:Array<string>;
|
||||
hideImage?:boolean;
|
||||
created:Date;
|
||||
updated:Date;
|
||||
}
|
||||
export interface BusinessListing extends Listing {
|
||||
listingsCategory: 'business'; //enum
|
||||
summary: Array<string>;
|
||||
type: string; //enum
|
||||
price?: number;
|
||||
realEstateIncluded?: boolean;
|
||||
salesRevenue?: number;
|
||||
cashFlow?: number;
|
||||
netProfit?: number;
|
||||
inventory?: string;
|
||||
employees?: number;
|
||||
established?: number;
|
||||
reasonForSale?: string;
|
||||
brokerLicencing?: string;
|
||||
internals?: string;
|
||||
}
|
||||
export interface ProfessionalsBrokersListing extends Listing {
|
||||
listingsCategory: 'professionals_brokers'; //enum
|
||||
summary: string;
|
||||
address?: string;
|
||||
email?: string;
|
||||
website?: string;
|
||||
category?: 'Professionals' | 'Broker';
|
||||
}
|
||||
export interface InvestmentsListing extends Listing {
|
||||
listingsCategory: 'investment'; //enum
|
||||
email?: string;
|
||||
website?: string;
|
||||
phoneNumber?: string;
|
||||
}
|
||||
export type ListingType =
|
||||
| BusinessListing
|
||||
| ProfessionalsBrokersListing
|
||||
| InvestmentsListing;
|
||||
|
||||
export interface ListingCriteria {
|
||||
type:string,
|
||||
location:string,
|
||||
minPrice:string,
|
||||
maxPrice:string,
|
||||
realEstateChecked:boolean,
|
||||
listingsCategory:'business'|'professionals_brokers'|'investment',
|
||||
category:'professional|broker'
|
||||
}
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
email: string;
|
||||
}
|
||||
export interface Subscription {
|
||||
id: string;
|
||||
userId:string
|
||||
level: string;
|
||||
start: Date;
|
||||
modified: Date;
|
||||
end: Date;
|
||||
status: string;
|
||||
invoices: Array<Invoice>;
|
||||
}
|
||||
export interface Invoice {
|
||||
id: string,
|
||||
date: Date,
|
||||
price: number
|
||||
}
|
||||
export interface JwtToken {
|
||||
exp: number;
|
||||
iat: number;
|
||||
auth_time: number;
|
||||
jti: string;
|
||||
iss: string;
|
||||
aud: string;
|
||||
sub: string;
|
||||
typ: string;
|
||||
azp: string;
|
||||
nonce: string;
|
||||
session_state: string;
|
||||
acr: string;
|
||||
realm_access: Realmaccess;
|
||||
resource_access: Resourceaccess;
|
||||
scope: string;
|
||||
sid: string;
|
||||
email_verified: boolean;
|
||||
name: string;
|
||||
preferred_username: string;
|
||||
given_name: string;
|
||||
family_name: string;
|
||||
email: string;
|
||||
user_id: string;
|
||||
}
|
||||
interface Resourceaccess {
|
||||
account: Realmaccess;
|
||||
}
|
||||
interface Realmaccess {
|
||||
roles: string[];
|
||||
}
|
||||
export interface PageEvent {
|
||||
first: number;
|
||||
rows: number;
|
||||
page: number;
|
||||
pageCount: number;
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
export interface MailInfo {
|
||||
sender: Sender;
|
||||
userId: string;
|
||||
}
|
||||
export interface Sender {
|
||||
name: string;
|
||||
email: string;
|
||||
phoneNumber: string;
|
||||
state: string;
|
||||
comments: string;
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import { RedisService } from './redis.service.js';
|
||||
export declare class RedisController {
|
||||
private redisService;
|
||||
constructor(redisService: RedisService);
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export declare class RedisModule {
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
export declare const LISTINGS = "LISTINGS";
|
||||
export declare const SUBSCRIPTIONS = "SUBSCRIPTIONS";
|
||||
export declare const USERS = "USERS";
|
||||
export declare class RedisService {
|
||||
private redis;
|
||||
setJson(id: string, value: any): Promise<void>;
|
||||
delete(id: string): Promise<void>;
|
||||
getJson(id: string, prefix: string): Promise<any>;
|
||||
getId(prefix: 'LISTINGS' | 'SUBSCRIPTIONS' | 'USERS'): Promise<string>;
|
||||
search(prefix: 'LISTINGS' | 'SUBSCRIPTIONS' | 'USERS', clause: string): Promise<any>;
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
import { SelectOptionsService } from './select-options.service.js';
|
||||
export declare class SelectOptionsController {
|
||||
private selectOptionsService;
|
||||
constructor(selectOptionsService: SelectOptionsService);
|
||||
getSelectOption(): any;
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { KeyValue, KeyValueStyle } from '../models/main.model.js';
|
||||
export declare class SelectOptionsService {
|
||||
constructor();
|
||||
typesOfBusiness: Array<KeyValueStyle>;
|
||||
prices: Array<KeyValue>;
|
||||
listingCategories: Array<KeyValue>;
|
||||
categories: Array<KeyValueStyle>;
|
||||
private usStates;
|
||||
locations: Array<any>;
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
import { FileService } from '../file/file.service.js';
|
||||
export declare class SubscriptionsController {
|
||||
private readonly fileService;
|
||||
constructor(fileService: FileService);
|
||||
findAll(): any;
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export declare function convertStringToNullUndefined(value: any): any;
|
||||
|
|
@ -30,7 +30,6 @@
|
|||
"@nestjs/platform-express": "^10.0.0",
|
||||
"@nestjs/serve-static": "^4.0.1",
|
||||
"handlebars": "^4.7.8",
|
||||
"ioredis": "^5.3.2",
|
||||
"ky": "^1.2.0",
|
||||
"nest-winston": "^1.9.4",
|
||||
"nodemailer": "^6.9.10",
|
||||
|
|
@ -39,6 +38,8 @@
|
|||
"passport-google-oauth20": "^2.0.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"redis": "^4.6.13",
|
||||
"redis-om": "^0.4.3",
|
||||
"reflect-metadata": "^0.2.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"urlcat": "^3.1.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { AccountController } from './account.controller.js';
|
||||
import { AccountService } from './account.service.js';
|
||||
import { FileService } from '../file/file.service.js';
|
||||
|
||||
@Module({
|
||||
controllers: [AccountController],
|
||||
providers: [AccountService,FileService]
|
||||
})
|
||||
export class AccountModule {}
|
||||
|
|
@ -20,11 +20,16 @@ import { utilities as nestWinstonModuleUtilities, WinstonModule } from 'nest-win
|
|||
import * as winston from 'winston';
|
||||
import { MailModule } from './mail/mail.module.js';
|
||||
import { AuthModule } from './auth/auth.module.js';
|
||||
import { GeoModule } from './geo/geo.module.js';
|
||||
import { UserModule } from './user/user.module.js';
|
||||
import { ListingsModule } from './listings/listings.module.js';
|
||||
import { AccountModule } from './account/account.module.js';
|
||||
import { SelectOptionsModule } from './select-options/select-options.module.js';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule.forRoot(), RedisModule, MailModule, AuthModule,
|
||||
imports: [ConfigModule.forRoot(), MailModule, AuthModule,
|
||||
ServeStaticModule.forRoot({
|
||||
rootPath: join(__dirname, '..', 'public'), // `public` ist das Verzeichnis, wo Ihre statischen Dateien liegen
|
||||
}),
|
||||
|
|
@ -43,9 +48,15 @@ const __dirname = path.dirname(__filename);
|
|||
// other transports...
|
||||
],
|
||||
// other options
|
||||
})
|
||||
}),
|
||||
GeoModule,
|
||||
UserModule,
|
||||
ListingsModule,
|
||||
AccountModule,
|
||||
SelectOptionsModule,
|
||||
RedisModule
|
||||
],
|
||||
controllers: [AppController, ListingsController, SelectOptionsController, SubscriptionsController, AccountController],
|
||||
providers: [AppService, FileService, SelectOptionsService, ListingsService, AccountService],
|
||||
controllers: [AppController, SubscriptionsController],
|
||||
providers: [AppService, FileService],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,18 @@
|
|||
import { Controller, Get, Param } from '@nestjs/common';
|
||||
import { GeoService } from './geo.service.js';
|
||||
|
||||
@Controller('geo')
|
||||
export class GeoController {
|
||||
constructor(private geoService:GeoService){}
|
||||
|
||||
@Get(':prefix')
|
||||
findByPrefix(@Param('prefix') prefix:string): any {
|
||||
return this.geoService.findCitiesStartingWith(prefix);
|
||||
}
|
||||
|
||||
@Get(':prefix/:state')
|
||||
findByPrefixAndState(@Param('prefix') prefix:string,@Param('state') state:string): any {
|
||||
return this.geoService.findCitiesStartingWith(prefix,state);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { GeoController } from './geo.controller.js';
|
||||
import { GeoService } from './geo.service.js';
|
||||
|
||||
@Module({
|
||||
controllers: [GeoController],
|
||||
providers: [GeoService]
|
||||
})
|
||||
export class GeoModule {}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { readFileSync } from 'fs';
|
||||
import path, { join } from 'path';
|
||||
import { City, Geo, State } from 'src/models/server.model.js';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
@Injectable()
|
||||
export class GeoService {
|
||||
geo:Geo;
|
||||
constructor() {
|
||||
this.loadGeo();
|
||||
}
|
||||
private loadGeo(): void {
|
||||
const filePath = join(__dirname,'..', 'assets', 'geo.json');
|
||||
const rawData = readFileSync(filePath, 'utf8');
|
||||
this.geo = JSON.parse(rawData);
|
||||
}
|
||||
|
||||
findCitiesStartingWith( prefix: string, state?:string): { city: string; state: string; state_code: string }[] {
|
||||
const result: { city: string; state: string; state_code: string }[] = [];
|
||||
|
||||
this.geo.states.forEach((state: State) => {
|
||||
state.cities.forEach((city: City) => {
|
||||
if (city.name.toLowerCase().startsWith(prefix.toLowerCase())) {
|
||||
result.push({
|
||||
city: city.name,
|
||||
state: state.name,
|
||||
state_code: state.state_code
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return state ? result.filter(e=>e.state_code.toLowerCase()===state.toLowerCase()) :result;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ListingsController } from './listings.controller.js';
|
||||
import { ListingsService } from './listings.service.js';
|
||||
|
||||
@Module({
|
||||
controllers: [ListingsController],
|
||||
providers: [ListingsService]
|
||||
})
|
||||
export class ListingsModule {}
|
||||
|
|
@ -5,55 +5,51 @@ import {
|
|||
ListingCriteria,
|
||||
ProfessionalsBrokersListing,
|
||||
} from '../models/main.model.js';
|
||||
import { LISTINGS, RedisService } from '../redis/redis.service.js';
|
||||
import { convertStringToNullUndefined } from '../utils.js';
|
||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
@Injectable()
|
||||
export class ListingsService {
|
||||
// private readonly logger = new Logger(ListingsService.name);
|
||||
constructor(private redisService: RedisService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {}
|
||||
constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {}
|
||||
|
||||
async setListing(
|
||||
value: BusinessListing | ProfessionalsBrokersListing | InvestmentsListing,
|
||||
id?: string,
|
||||
) {
|
||||
if (!id) {
|
||||
id = await this.redisService.getId(LISTINGS);
|
||||
value.id = id;
|
||||
this.logger.info(`No ID - creating new one:${id}`)
|
||||
} else {
|
||||
this.logger.info(`ID available:${id}`)
|
||||
}
|
||||
this.redisService.setJson(id, value);
|
||||
// if (!id) {
|
||||
// id = await this.redisService.getId(LISTINGS);
|
||||
// value.id = id;
|
||||
// this.logger.info(`No ID - creating new one:${id}`)
|
||||
// } else {
|
||||
// this.logger.info(`ID available:${id}`)
|
||||
// }
|
||||
//this.redisService.setJson(id, value);
|
||||
}
|
||||
|
||||
async getListingById(id: string) {
|
||||
return await this.redisService.getJson(id, LISTINGS);
|
||||
//return await this.redisService.getJson(id, LISTINGS);
|
||||
}
|
||||
deleteListing(id: string){
|
||||
this.redisService.delete(id);
|
||||
//this.redisService.delete(id);
|
||||
this.logger.info(`delete listing with ID:${id}`)
|
||||
}
|
||||
async getAllListings(start?: number, end?: number) {
|
||||
const searchResult = await this.redisService.search(LISTINGS, '*');
|
||||
const listings = searchResult.slice(1).reduce((acc, item, index, array) => {
|
||||
// Jedes zweite Element (beginnend mit dem ersten) ist ein JSON-String des Listings
|
||||
if (index % 2 === 1) {
|
||||
try {
|
||||
const listing = JSON.parse(item[1]); // Parsen des JSON-Strings
|
||||
acc.push(listing);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Parsen des JSON-Strings: ', error);
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
return listings;
|
||||
// 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 [];
|
||||
}
|
||||
//criteria.type,criteria.location,criteria.minPrice,criteria.maxPrice,criteria.realEstateChecked,criteria.listingsCategory
|
||||
//async find(type:string,location:string,minPrice:string,maxPrice:string,realEstateChecked:boolean,listingsCategory:string): Promise<any> {
|
||||
async find(criteria:ListingCriteria): Promise<any> {
|
||||
let listings = await this.getAllListings();
|
||||
listings=listings.filter(l=>l.listingsCategory===criteria.listingsCategory);
|
||||
|
|
|
|||
|
|
@ -14,12 +14,16 @@ const __dirname = path.dirname(__filename);
|
|||
// transport: 'smtps://user@example.com:topsecret@smtp.example.com',
|
||||
// or
|
||||
transport: {
|
||||
host: 'smtp.gmail.com',
|
||||
secure: true,
|
||||
|
||||
host: 'email-smtp.us-east-2.amazonaws.com',
|
||||
secure: false,
|
||||
port:587,
|
||||
// auth: {
|
||||
// user: 'andreas.knuth@gmail.com',
|
||||
// pass: 'ksnh xjae dqbv xana',
|
||||
// },
|
||||
auth: {
|
||||
user: 'andreas.knuth@gmail.com',
|
||||
pass: 'ksnh xjae dqbv xana',
|
||||
user: 'AKIAU6GDWVAQ2QNFLNWN',
|
||||
pass: 'BDE9nZv/ARbpotim1mIOir52WgIbpSi9cv1oJoH8oEf7',
|
||||
},
|
||||
},
|
||||
defaults: {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,80 @@
|
|||
import { Entity } from "redis-om";
|
||||
import { UserBase } from "./main.model.js";
|
||||
|
||||
export interface MailInfo {
|
||||
sender:Sender;
|
||||
userId:string;
|
||||
}
|
||||
export interface Sender {
|
||||
name:string;
|
||||
email:string;
|
||||
phoneNumber:string;
|
||||
state:string;
|
||||
comments:string;
|
||||
sender: Sender;
|
||||
userId: string;
|
||||
}
|
||||
export interface Sender {
|
||||
name: string;
|
||||
email: string;
|
||||
phoneNumber: string;
|
||||
state: string;
|
||||
comments: string;
|
||||
}
|
||||
export interface Geo {
|
||||
id: number;
|
||||
name: string;
|
||||
iso3: string;
|
||||
iso2: string;
|
||||
numeric_code: string;
|
||||
phone_code: string;
|
||||
capital: string;
|
||||
currency: string;
|
||||
currency_name: string;
|
||||
currency_symbol: string;
|
||||
tld: string;
|
||||
native: string;
|
||||
region: string;
|
||||
region_id: string;
|
||||
subregion: string;
|
||||
subregion_id: string;
|
||||
nationality: string;
|
||||
timezones: Timezone[];
|
||||
translations: Translations;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
emoji: string;
|
||||
emojiU: string;
|
||||
states: State[];
|
||||
}
|
||||
export interface State {
|
||||
id: number;
|
||||
name: string;
|
||||
state_code: string;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
type: string;
|
||||
cities: City[];
|
||||
}
|
||||
export interface City {
|
||||
id: number;
|
||||
name: string;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
}
|
||||
export interface Translations {
|
||||
kr: string;
|
||||
'pt-BR': string;
|
||||
pt: string;
|
||||
nl: string;
|
||||
hr: string;
|
||||
fa: string;
|
||||
de: string;
|
||||
es: string;
|
||||
fr: string;
|
||||
ja: string;
|
||||
it: string;
|
||||
cn: string;
|
||||
tr: string;
|
||||
}
|
||||
export interface Timezone {
|
||||
zoneName: string;
|
||||
gmtOffset: number;
|
||||
gmtOffsetName: string;
|
||||
abbreviation: string;
|
||||
tzName: string;
|
||||
}
|
||||
export interface UserEntity extends UserBase, Entity {
|
||||
licensedIn?: string[];
|
||||
}
|
||||
|
|
@ -6,13 +6,4 @@ import { RedisService } from './redis.service.js';
|
|||
export class RedisController {
|
||||
constructor(private redisService:RedisService){}
|
||||
|
||||
// @Get(':id')
|
||||
// getById(@Param('id') id:string){
|
||||
// return this.redisService.getListingById(id);
|
||||
// }
|
||||
|
||||
// @Post(':id')
|
||||
// updateById(@Body() listing: any){
|
||||
// this.redisService.setListing(listing);
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,30 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
|
||||
@Module({
|
||||
providers: [RedisService],
|
||||
exports: [RedisService],
|
||||
controllers: [RedisController],
|
||||
providers: [
|
||||
{
|
||||
provide: 'REDIS_OPTIONS',
|
||||
useValue: {
|
||||
url: 'redis://localhost:6379'
|
||||
}
|
||||
},
|
||||
{
|
||||
inject: ['REDIS_OPTIONS'],
|
||||
provide: 'REDIS_CLIENT',
|
||||
useFactory: async (options: { url: string }) => {
|
||||
const client = createClient(options);
|
||||
await client.connect();
|
||||
return client;
|
||||
}
|
||||
}],
|
||||
exports:['REDIS_CLIENT']
|
||||
})
|
||||
export class RedisModule {}
|
||||
|
||||
export const REDIS_CLIENT = "REDIS_CLIENT";
|
||||
// redis.service.ts
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { RedisService } from './redis.service.js';
|
||||
import { RedisController } from './redis.controller.js';
|
||||
import { createClient } from 'redis';
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,48 +1,50 @@
|
|||
// redis.service.ts
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Redis } from 'ioredis';
|
||||
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',
|
||||
password: 'bizmatchRedis:5600Wuppertal11', // ACL Benutzername und Passwort
|
||||
//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 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 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;
|
||||
}
|
||||
// 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;
|
||||
// }
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { SelectOptionsController } from './select-options.controller.js';
|
||||
import { SelectOptionsService } from './select-options.service.js';
|
||||
|
||||
@Module({
|
||||
controllers: [SelectOptionsController],
|
||||
providers: [SelectOptionsService]
|
||||
})
|
||||
export class SelectOptionsModule {}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { Body, Controller, Get, Inject, Param, Post, Put } from '@nestjs/common';
|
||||
import { UserService } from './user.service.js';
|
||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||
import { Logger } from 'winston';
|
||||
import { UserEntity } from 'src/models/server.model.js';
|
||||
|
||||
@Controller('user')
|
||||
export class UserController {
|
||||
|
||||
constructor(private userService:UserService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger){}
|
||||
|
||||
@Get(':id')
|
||||
findById(@Param('id') id:string): any {
|
||||
return this.userService.getUserById(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
save(@Body() user: any):Promise<UserEntity>{
|
||||
this.logger.info(`User persisted: `);
|
||||
return this.userService.saveUser(user);
|
||||
}
|
||||
|
||||
// @Put()
|
||||
// update(@Body() user: any):Promise<UserEntity>{
|
||||
// this.logger.info(`update User`);
|
||||
// return this.userService.saveUser(user);
|
||||
// }
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { UserController } from './user.controller.js';
|
||||
import { UserService } from './user.service.js';
|
||||
import { RedisModule } from '../redis/redis.module.js';
|
||||
|
||||
@Module({
|
||||
imports: [RedisModule],
|
||||
controllers: [UserController],
|
||||
providers: [UserService]
|
||||
})
|
||||
export class UserModule {
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { Get, Inject, Injectable, Param } from '@nestjs/common';
|
||||
import { createClient } from 'redis';
|
||||
import { Entity, Repository, Schema } from 'redis-om';
|
||||
import { User } from '../models/main.model.js';
|
||||
import { REDIS_CLIENT } from '../redis/redis.module.js';
|
||||
import { UserEntity } from '../models/server.model.js';
|
||||
// export const redis = createClient({ url: 'redis://localhost:6379' })
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
userRepository:Repository;
|
||||
userSchema = new Schema('user',{
|
||||
// id: string;
|
||||
firstname: { type: 'string' },
|
||||
lastname: { type: 'string' },
|
||||
email: { type: 'string' },
|
||||
phoneNumber: { type: 'string' },
|
||||
companyOverview:{ type: 'string' },
|
||||
companyWebsite:{ type: 'string' },
|
||||
companyLocation:{ type: 'string' },
|
||||
offeredServices:{ type: 'string' },
|
||||
areasServed:{ type: 'string' },
|
||||
names:{ type: 'string[]', path:'$.licensedIn.name' },
|
||||
values:{ type: 'string[]', path:'$.licensedIn.value' }
|
||||
}, {
|
||||
dataStructure: 'JSON'
|
||||
})
|
||||
constructor(@Inject(REDIS_CLIENT) private readonly client: any){
|
||||
// const redis = createClient({ url: 'redis://localhost:6379' })
|
||||
this.userRepository = new Repository(this.userSchema, client)
|
||||
}
|
||||
|
||||
async getUserById( id:string){
|
||||
return await this.userRepository.fetch(id);
|
||||
}
|
||||
async saveUser(user:any):Promise<UserEntity>{
|
||||
return await this.userRepository.save(user.id,user) as UserEntity
|
||||
}
|
||||
// createUser(){
|
||||
|
||||
// }
|
||||
|
||||
// updateById(id:string){
|
||||
|
||||
// }
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
<div class="grid">
|
||||
<div class="col-12 md:col-6">
|
||||
<ul class="list-none p-0 m-0 border-top-1 border-300">
|
||||
@if (listing && (listing.listingsCategory==='business' || listing.listingsCategory==='professionals_brokers')){
|
||||
<!-- @if (listing && (listing.listingsCategory==='business')){
|
||||
<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">Summary</div>
|
||||
<div class="w-full md:w-10">
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
}
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
} -->
|
||||
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||
<div class="text-500 w-full md:w-2 font-medium">Description</div>
|
||||
<div class="text-900 w-full md:w-10 line-height-3">{{listing?.description}}</div>
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
</li>
|
||||
<li class="flex align-items-center py-3 px-2 flex-wrap ">
|
||||
<div class="text-500 w-full md:w-2 font-medium">Located in</div>
|
||||
<div class="text-900 w-full md:w-10">{{selectOptions.getLocation(listing.location)}}</div>
|
||||
<div class="text-900 w-full md:w-10">{{selectOptions.getState(listing.state)}}</div>
|
||||
</li>
|
||||
<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">Asking Price</div>
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
<div class="text-900 w-full md:w-10">{{listing.brokerLicencing}}</div>
|
||||
</li>
|
||||
}
|
||||
@if (listing && (listing.listingsCategory==='professionals_brokers')){
|
||||
<!-- @if (listing && (listing.listingsCategory==='professionals_brokers')){
|
||||
<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-900 w-full md:w-10">{{selectOptions.getLocation(listing.location)}}</div>
|
||||
|
|
@ -84,11 +84,11 @@
|
|||
<div class="text-500 w-full md:w-2 font-medium">Category</div>
|
||||
<div class="text-900 w-full md:w-10">{{listing.category}}</div>
|
||||
</li>
|
||||
}
|
||||
} -->
|
||||
@if (listing && (listing.listingsCategory==='investment')){
|
||||
<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-900 w-full md:w-10">{{selectOptions.getLocation(listing.location)}}</div>
|
||||
<div class="text-900 w-full md:w-10">{{selectOptions.getState(listing.state)}}</div>
|
||||
</li>
|
||||
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||
<div class="text-500 w-full md:w-2 font-medium">EMail</div>
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
[style]="{ width: '100%'}"></p-dropdown>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<p-dropdown [options]="locations" [(ngModel)]="location" optionLabel="criteria.location" optionLabel="name" optionValue="value"
|
||||
[showClear]="true" placeholder="Location" [style]="{ width: '100%'}"></p-dropdown>
|
||||
<p-dropdown [options]="states" [(ngModel)]="state" optionLabel="criteria.location" optionLabel="name" optionValue="value"
|
||||
[showClear]="true" placeholder="State" [style]="{ width: '100%'}"></p-dropdown>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<p-dropdown [options]="selectOptions.prices" [(ngModel)]="criteria.minPrice" optionLabel="name"
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
}
|
||||
@if (listingCategory==='investment'){
|
||||
<div class="col-2">
|
||||
<p-dropdown [options]="locations" [(ngModel)]="criteria.location" optionLabel="name" optionValue="value"
|
||||
<p-dropdown [options]="states" [(ngModel)]="criteria.location" optionLabel="name" optionValue="value"
|
||||
[showClear]="true" placeholder="Location" [style]="{ width: '100%'}"></p-dropdown>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
[style]="{ width: '100%'}"></p-dropdown>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<p-dropdown [options]="locations" [(ngModel)]="criteria.location" optionLabel="name" optionValue="value"
|
||||
<p-dropdown [options]="states" [(ngModel)]="criteria.location" optionLabel="name" optionValue="value"
|
||||
[showClear]="true" placeholder="Location" [style]="{ width: '100%'}"></p-dropdown>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -94,17 +94,17 @@
|
|||
<p class="mt-0 mb-1 text-700 line-height-3">Asking price: {{listing.price | currency}}</p>
|
||||
<p class="mt-0 mb-1 text-700 line-height-3">Sales revenue: {{listing.salesRevenue | currency}}</p>
|
||||
<p class="mt-0 mb-1 text-700 line-height-3">Net profit: {{listing.cashFlow | currency}}</p>
|
||||
<p class="mt-0 mb-1 text-700 line-height-3">Location: {{selectOptions.getLocation(listing.location)}}</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>
|
||||
}
|
||||
@if (listing.listingsCategory==='professionals_brokers'){
|
||||
<!-- <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.getLocation(listing.location)}}</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">Website: {{listing.website}}</p>
|
||||
}
|
||||
@if (listing.listingsCategory==='investment'){
|
||||
<p class="mt-0 mb-1 text-700 line-height-3">Location: {{selectOptions.getLocation(listing.location)}}</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">Website: {{listing.website}}</p>
|
||||
<p class="mt-0 mb-1 text-700 line-height-3">Phone Number: {{listing.phoneNumber}}</p>
|
||||
|
|
|
|||
|
|
@ -34,9 +34,9 @@ export class ListingsComponent {
|
|||
maxPrice: string;
|
||||
minPrice: string;
|
||||
type:string;
|
||||
locations = [];
|
||||
locationsSet = new Set();
|
||||
location:string;
|
||||
states = [];
|
||||
statesSet = new Set();
|
||||
state:string;
|
||||
first: number = 0;
|
||||
rows: number = 12;
|
||||
totalRecords:number = 0;
|
||||
|
|
@ -61,25 +61,25 @@ export class ListingsComponent {
|
|||
}
|
||||
async init(){
|
||||
this.listings=await this.listingsService.getListings(this.criteria);
|
||||
this.setLocations();
|
||||
this.setStates();
|
||||
this.filteredListings=[...this.listings];
|
||||
this.totalRecords=this.listings.length
|
||||
this.filteredListings=[...this.listings].splice(this.first,this.rows);
|
||||
this.cdRef.markForCheck();
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
setLocations(){
|
||||
this.locationsSet=new Set();
|
||||
setStates(){
|
||||
this.statesSet=new Set();
|
||||
this.listings.forEach(l=>{
|
||||
if (l.location){
|
||||
this.locationsSet.add(l.location)
|
||||
if (l.state){
|
||||
this.statesSet.add(l.state)
|
||||
}
|
||||
})
|
||||
this.locations = [...this.locationsSet].map((ls) =>({name:this.selectOptions.getLocation(ls as string),value:ls}))
|
||||
this.states = [...this.statesSet].map((ls) =>({name:this.selectOptions.getState(ls as string),value:ls}))
|
||||
}
|
||||
async search() {
|
||||
this.listings= await this.listingsService.getListings(this.criteria);
|
||||
this.setLocations();
|
||||
this.setStates();
|
||||
this.totalRecords=this.listings.length
|
||||
this.filteredListings =[...this.listings].splice(this.first,this.rows);
|
||||
this.cdRef.markForCheck();
|
||||
|
|
|
|||
|
|
@ -8,41 +8,90 @@
|
|||
<p-divider></p-divider>
|
||||
<div class="flex gap-5 flex-column-reverse md:flex-row">
|
||||
<div class="flex-auto p-fluid">
|
||||
<div class="mb-4">
|
||||
<!-- <div class="mb-4">
|
||||
<label for="email" class="block font-medium text-900 mb-2">Username</label>
|
||||
<input id="email" type="text" [disabled]="true" pInputText [(ngModel)]="user.username">
|
||||
<p class="font-italic text-sm line-height-1">Usernames cannot be changed.</p>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="state" class="block font-medium text-900 mb-2">First Name</label>
|
||||
<input id="state" type="text" pInputText [(ngModel)]="user.firstname">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="state" class="block font-medium text-900 mb-2">Last Name</label>
|
||||
<input id="state" type="text" pInputText [(ngModel)]="user.lastname">
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="mb-4">
|
||||
<label for="state" class="block font-medium text-900 mb-2">E-mail (required)</label>
|
||||
<input id="state" type="text" pInputText [(ngModel)]="user.email">
|
||||
<input id="state" type="text" [disabled]="true" pInputText [(ngModel)]="user.email">
|
||||
<p class="font-italic text-sm line-height-1">You can only modify your email by contacting us at emailchange@bizmatch.net</p>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="firstname" class="block font-medium text-900 mb-2">First Name</label>
|
||||
<input id="firstname" type="text" pInputText [(ngModel)]="user.firstname">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="lastname" class="block font-medium text-900 mb-2">Last Name</label>
|
||||
<input id="lastname" type="text" pInputText [(ngModel)]="user.lastname">
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="mb-4 col-12 md:col-4">
|
||||
<label for="phoneNumber" class="block font-medium text-900 mb-2">Your Phone Number</label>
|
||||
<input id="phoneNumber" type="text" pInputText [(ngModel)]="user.phoneNumber">
|
||||
</div>
|
||||
<div class="mb-4 col-12 md:col-4">
|
||||
<label for="companyWebsite" class="block font-medium text-900 mb-2">Company Website</label>
|
||||
<input id="companyWebsite" type="text" pInputText [(ngModel)]="user.companyWebsite">
|
||||
</div>
|
||||
<div class="mb-4 col-12 md:col-4">
|
||||
<label for="companyLocation" class="block font-medium text-900 mb-2">Company Location</label>
|
||||
<p-autoComplete [(ngModel)]="user.companyLocation" [suggestions]="suggestions" (completeMethod)="search($event)"></p-autoComplete>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="companyOverview" class="block font-medium text-900 mb-2">Company Overview</label>
|
||||
<textarea id="companyOverview" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="user.companyOverview"></textarea>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="areasServed" class="block font-medium text-900 mb-2">Areas We Serve</label>
|
||||
<textarea id="areasServed" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="user.areasServed"></textarea>
|
||||
</div>
|
||||
<div >
|
||||
<label for="companyOverview" class="block font-medium text-900 mb-2">Licensed In</label>
|
||||
@for (licensedIn of user.licensedIn; track licensedIn.value){
|
||||
<div class="grid">
|
||||
<div class="flex col-12 md:col-6">
|
||||
<p-dropdown id="states" [options]="selectOptions?.states" [(ngModel)]="licensedIn.name" optionLabel="name" optionValue="value" [showClear]="true" placeholder="State" [ngStyle]="{ width: '100%'}"></p-dropdown>
|
||||
</div>
|
||||
<div class="flex col-12 md:col-6">
|
||||
<input id="companyWebsite" type="text" pInputText [(ngModel)]="licensedIn.value" placeholder="Licence Number">
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="field mb-5 col-12 md:col-6 flex align-items-center">
|
||||
<p-button class="mr-1" icon="pi pi-plus" severity="success" (click)="addLicence()"></p-button>
|
||||
<p-button icon="pi pi-minus" severity="danger" (click)="removeLicence()" [disabled]="user.licensedIn?.length<2"></p-button>
|
||||
<span class="text-xs"> (Add more licenses or remove existing ones.)</span>
|
||||
<!-- <button pButton pRipple label="Add Licence" class="w-auto" (click)="addLicence()"></button> -->
|
||||
</div>
|
||||
<!-- <div class="mb-4">
|
||||
<label for="state" class="block font-medium text-900 mb-2">New Password</label>
|
||||
<p class="font-italic text-sm line-height-1">If you would like to change the password type a new one. Otherwise leave this blank.</p>
|
||||
<input id="state" type="text" pInputText>
|
||||
<p class="font-italic text-sm line-height-1">Password repetition</p>
|
||||
<input id="state" type="text" pInputText>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div>
|
||||
<button pButton pRipple label="Update Profile" class="w-auto" (click)="updateProfile(user)"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-column align-items-center flex-or">
|
||||
<span class="font-medium text-900 mb-2">Profile Picture</span>
|
||||
<img [src]="imageUrl" (error)="setImageToFallback($event)" class="rounded-image"/>
|
||||
<!-- <p-image src="http://localhost:3000/public/user.png" alt="Image" width="10rem" [preview]="true"></p-image> -->
|
||||
<!-- <button pButton type="button" icon="pi pi-pencil" class="p-button-rounded -mt-4"></button> -->
|
||||
<p-fileUpload mode="basic" chooseLabel="Upload" name="file" [url]="uploadUrl" accept="image/*" [maxFileSize]="maxFileSize" (onUpload)="onUpload($event)" [auto]="true" styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"></p-fileUpload>
|
||||
<div>
|
||||
<div class="flex flex-column align-items-center flex-or mb-8">
|
||||
<span class="font-medium text-900 mb-2">Company Logo</span>
|
||||
<span class="font-medium text-xs mb-2">(is shown in every offer)</span>
|
||||
<img [src]="companyLogoUrl" (error)="setImageToFallback($event)" class="rounded-image"/>
|
||||
<p-fileUpload mode="basic" chooseLabel="Upload" name="file" [url]="uploadUrl" accept="image/*" [maxFileSize]="maxFileSize" (onUpload)="onUploadCompanyLogo($event)" [auto]="true" styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"></p-fileUpload>
|
||||
</div>
|
||||
<p-divider></p-divider>
|
||||
<div class="flex flex-column align-items-center flex-or">
|
||||
<span class="font-medium text-900 mb-2">Your Profile Picture</span>
|
||||
<img [src]="profileUrl" (error)="setImageToFallback($event)" class="rounded-image"/>
|
||||
<p-fileUpload mode="basic" chooseLabel="Upload" name="file" [url]="uploadUrl" accept="image/*" [maxFileSize]="maxFileSize" (onUpload)="onUploadProfilePicture($event)" [auto]="true" styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"></p-fileUpload>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-900 font-semibold text-lg mt-3">Membership Level</div>
|
||||
|
|
|
|||
|
|
@ -7,3 +7,6 @@
|
|||
object-fit: contain;
|
||||
}
|
||||
|
||||
.wfull{
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||
import { ButtonModule } from 'primeng/button';
|
||||
import { CheckboxModule } from 'primeng/checkbox';
|
||||
import { InputTextModule } from 'primeng/inputtext';
|
||||
|
|
@ -23,7 +23,9 @@ import { lastValueFrom } from 'rxjs';
|
|||
import { MessageService } from 'primeng/api';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { FileUploadModule } from 'primeng/fileupload';
|
||||
import { Invoice, Subscription, User } from '../../../../../../common-models/src/main.model';
|
||||
import { AutoCompleteCompleteEvent, Invoice, Subscription, User } from '../../../../../../common-models/src/main.model';
|
||||
import { GeoService } from '../../../services/geo.service';
|
||||
import { ChangeDetectionStrategy } from '@angular/compiler';
|
||||
|
||||
|
||||
@Component({
|
||||
|
|
@ -41,24 +43,58 @@ export class AccountComponent {
|
|||
userSubscriptions:Array<Subscription>=[];
|
||||
uploadUrl:string;
|
||||
maxFileSize=1000000;
|
||||
imageUrl:string;
|
||||
constructor(public userService: UserService, private subscriptionService: SubscriptionsService,private messageService: MessageService) {
|
||||
companyLogoUrl:string;
|
||||
profileUrl:string;
|
||||
constructor(public userService: UserService,
|
||||
private subscriptionService: SubscriptionsService,
|
||||
private messageService: MessageService,
|
||||
private geoService:GeoService,
|
||||
public selectOptions:SelectOptionsService,
|
||||
private cdref:ChangeDetectorRef) {
|
||||
this.user=this.userService.getUser()
|
||||
|
||||
}
|
||||
async ngOnInit(){
|
||||
this.imageUrl = `${environment.apiBaseUrl}/profile_${this.user.id}`
|
||||
this.profileUrl = `${environment.apiBaseUrl}/profile_${this.user.id}`
|
||||
this.companyLogoUrl = `${environment.apiBaseUrl}/profile_${this.user.id}`
|
||||
this.userSubscriptions=await lastValueFrom(this.subscriptionService.getAllSubscriptions());
|
||||
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/account/uploadPhoto/${this.user.id}`;
|
||||
if (!this.user.licensedIn || this.user.licensedIn?.length===0){
|
||||
this.user.licensedIn = [{name:'',value:''}]
|
||||
}
|
||||
this.user=await this.userService.getById(this.user.id);
|
||||
}
|
||||
printInvoice(invoice:Invoice){}
|
||||
updateProfile(user:User){
|
||||
this.messageService.add({ severity: 'warn', summary: 'Information', detail: 'This function is not yet available, please send an email to info@bizmatch.net for changes to your customer data', life: 15000 });
|
||||
|
||||
async updateProfile(user:User){
|
||||
//this.messageService.add({ severity: 'warn', summary: 'Information', detail: 'This function is not yet available, please send an email to info@bizmatch.net for changes to your customer data', life: 15000 });
|
||||
await this.userService.save(this.user);
|
||||
}
|
||||
onUpload(event:any){
|
||||
|
||||
|
||||
onUploadCompanyLogo(event:any){
|
||||
const uniqueSuffix = '?_ts=' + new Date().getTime();
|
||||
this.imageUrl = `${environment.apiBaseUrl}/profile_${this.user.id}${uniqueSuffix}` //`http://IhrServer:Port/${newImagePath}${uniqueSuffix}`;
|
||||
this.companyLogoUrl = `${environment.apiBaseUrl}/company_${this.user.id}${uniqueSuffix}` //`http://IhrServer:Port/${newImagePath}${uniqueSuffix}`;
|
||||
}
|
||||
onUploadProfilePicture(event:any){
|
||||
const uniqueSuffix = '?_ts=' + new Date().getTime();
|
||||
this.profileUrl = `${environment.apiBaseUrl}/profile_${this.user.id}${uniqueSuffix}` //`http://IhrServer:Port/${newImagePath}${uniqueSuffix}`;
|
||||
}
|
||||
setImageToFallback(event: Event) {
|
||||
(event.target as HTMLImageElement).src = `/assets/images/placeholder.png`; // Pfad zum Platzhalterbild
|
||||
}
|
||||
|
||||
|
||||
suggestions: string[] | undefined;
|
||||
|
||||
async search(event: AutoCompleteCompleteEvent) {
|
||||
const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query))
|
||||
this.suggestions = result.map(r=>`${r.city} - ${r.state_code}`).slice(0,5);
|
||||
}
|
||||
addLicence(){
|
||||
this.user.licensedIn.push({name:'',value:''});
|
||||
}
|
||||
removeLicence(){
|
||||
this.user.licensedIn.splice(this.user.licensedIn.length-2,1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,14 +18,6 @@
|
|||
<label for="email" class="block font-medium text-900 mb-2">Title of Listing</label>
|
||||
<input id="email" type="text" pInputText [(ngModel)]="listing.title">
|
||||
</div>
|
||||
@if (listing.listingsCategory==='business' || listing.listingsCategory==='professionals_brokers'){
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<label for="summary" class="block font-medium text-900 mb-2">Summary (Brief description)</label>
|
||||
<textarea id="summary" type="text" pInputTextarea rows="5" [autoResize]="true" [ngModel]="listing.summary | arrayToString:'\n\n'" (ngModelChange)="updateSummary($event)"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<label for="description" class="block font-medium text-900 mb-2">Description</label>
|
||||
|
|
@ -40,21 +32,22 @@
|
|||
[style]="{ width: '100%'}"></p-dropdown>
|
||||
</div>
|
||||
}
|
||||
<div class="mb-4">
|
||||
<label for="listingCategory" class="block font-medium text-900 mb-2">Location</label>
|
||||
<p-dropdown id="listingCategory" [options]="selectOptions?.locations" [(ngModel)]="listing.location" optionLabel="name"
|
||||
optionValue="value" [showClear]="true" placeholder="Location"
|
||||
<div class="grid">
|
||||
<div class="mb-4 col-12 md:col-6">
|
||||
<label for="listingCategory" class="block font-medium text-900 mb-2">State</label>
|
||||
<p-dropdown id="listingCategory" [options]="selectOptions?.states" [(ngModel)]="listing.state" optionLabel="name"
|
||||
optionValue="value" [showClear]="true" placeholder="State"
|
||||
[style]="{ width: '100%'}"></p-dropdown>
|
||||
</div>
|
||||
@if (listing.listingsCategory==='professionals_brokers'){
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<label for="address" class="block font-medium text-900 mb-2">Address</label>
|
||||
<input id="address" type="text" pInputText [(ngModel)]="listing.address">
|
||||
</div>
|
||||
<div class="mb-4 col-12 md:col-6">
|
||||
<label for="listingCategory" class="block font-medium text-900 mb-2">City</label>
|
||||
<!-- <p-dropdown id="listingCategory" [options]="selectOptions?.states" [(ngModel)]="listing.city" optionLabel="name"
|
||||
optionValue="value" [showClear]="true" placeholder="State"
|
||||
[style]="{ width: '100%'}"></p-dropdown> -->
|
||||
<p-autoComplete [(ngModel)]="listing.city" [suggestions]="suggestions" (completeMethod)="search($event)"></p-autoComplete>
|
||||
</div>
|
||||
}
|
||||
@if (listing.listingsCategory==='professionals_brokers' || listing.listingsCategory==='investment'){
|
||||
</div>
|
||||
<!-- @if (listing.listingsCategory==='investment'){
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<label for="email" class="block font-medium text-900 mb-2">Email</label>
|
||||
|
|
@ -67,23 +60,13 @@
|
|||
<input id="address" type="text" pInputText [(ngModel)]="listing.website">
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (listing.listingsCategory==='professionals_brokers'){
|
||||
<div class="mb-4">
|
||||
<label for="category" class="block font-medium text-900 mb-2">Category</label>
|
||||
<p-dropdown id="category" [options]="selectOptions?.categories" [(ngModel)]="listing.category" optionLabel="name"
|
||||
optionValue="value" [showClear]="true" placeholder="Category"
|
||||
[style]="{ width: '100%'}"></p-dropdown>
|
||||
</div>
|
||||
}
|
||||
@if (listing.listingsCategory==='investment'){
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<label for="phoneNumber" class="block font-medium text-900 mb-2">Phone Number</label>
|
||||
<input id="phoneNumber" type="text" pInputText [(ngModel)]="listing.phoneNumber">
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
} -->
|
||||
</div>
|
||||
</div>
|
||||
<p-divider></p-divider>
|
||||
|
|
@ -93,54 +76,76 @@
|
|||
<div class="grid">
|
||||
<div class="mb-4 col-12 md:col-6">
|
||||
<label for="price" class="block font-medium text-900 mb-2">Price</label>
|
||||
<!-- <input id="price" type="text" pInputText [(ngModel)]="listing.price"> -->
|
||||
<p-inputNumber mode="currency" currency="USD" inputId="price" type="text" [(ngModel)]="listing.price"></p-inputNumber>
|
||||
</div>
|
||||
<div class="mb-4 col-12 md:col-6 flex align-items-end justify-content-center">
|
||||
<p-checkbox [binary]="true" [(ngModel)]="listing.realEstateIncluded"></p-checkbox>
|
||||
<span class="ml-2 text-900">Real Estate Included</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="mb-4 col-12 md:col-6">
|
||||
<label for="salesRevenue" class="block font-medium text-900 mb-2">Sales Revenue</label>
|
||||
<!-- <input id="salesRevenue" type="text" pInputText [(ngModel)]="listing.salesRevenue"> -->
|
||||
<p-inputNumber mode="currency" currency="USD" inputId="salesRevenue" type="text" [(ngModel)]="listing.salesRevenue"></p-inputNumber>
|
||||
</div>
|
||||
<div class="mb-4 col-12 md:col-6">
|
||||
<label for="cashFlow" class="block font-medium text-900 mb-2">Cash Flow</label>
|
||||
<!-- <input id="cashFlow" type="text" pInputText [(ngModel)]="listing.cashFlow"> -->
|
||||
<p-inputNumber mode="currency" currency="USD" inputId="cashFlow" type="text" [(ngModel)]="listing.cashFlow"></p-inputNumber>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="mb-4 col-12 md:col-6">
|
||||
<label for="netProfit" class="block font-medium text-900 mb-2">Net Profit</label>
|
||||
<!-- <input id="netProfit" type="text" pInputText [(ngModel)]="listing.netProfit"> -->
|
||||
<p-inputNumber mode="currency" currency="USD" inputId="netProfit" type="text" [(ngModel)]="listing.netProfit"></p-inputNumber>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="mb-4 col-12 md:col-6">
|
||||
<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>
|
||||
</div>
|
||||
<div class="mb-4 col-12 md:col-6">
|
||||
<label for="employees" class="block font-medium text-900 mb-2">Employees</label>
|
||||
<!-- <input id="employees" type="text" pInputText [(ngModel)]="listing.employees"> -->
|
||||
<p-inputNumber mode="employees" mode="decimal" inputId="employees" type="text" [(ngModel)]="listing.employees"></p-inputNumber>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="mb-4 col-12 md:col-4 ">
|
||||
<p-checkbox [binary]="true" [(ngModel)]="listing.realEstateIncluded"></p-checkbox>
|
||||
<span class="ml-2 text-900">Real Estate Included</span>
|
||||
</div>
|
||||
<div class="mb-4 col-12 md:col-4">
|
||||
<p-checkbox [binary]="true" [(ngModel)]="listing.leasedLocation"></p-checkbox>
|
||||
<span class="ml-2 text-900">Leased Location</span>
|
||||
</div>
|
||||
<div class="mb-4 col-12 md:col-4">
|
||||
<p-checkbox [binary]="true" [(ngModel)]="listing.franchiseResale"></p-checkbox>
|
||||
<span class="ml-2 text-900">Franchise Re-Sale</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="inventory" class="block font-medium text-900 mb-2">Inventory</label>
|
||||
<textarea id="inventory" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.inventory"></textarea>
|
||||
<label for="supportAndTraining" class="block font-medium text-900 mb-2">Support & Training</label>
|
||||
<!-- <textarea id="inventory" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.supportAndTraining"></textarea> -->
|
||||
<input id="supportAndTraining" type="text" pInputText [(ngModel)]="listing.supportAndTraining">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="reasonForSale" class="block font-medium text-900 mb-2">Reason for Sale</label>
|
||||
<textarea id="reasonForSale" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.reasonForSale"></textarea>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="brokerLicensing" class="block font-medium text-900 mb-2">Broker Licensing</label>
|
||||
<input id="brokerLicensing" type="text" pInputText [(ngModel)]="listing.brokerLicencing">
|
||||
<div class="grid">
|
||||
<div class="mb-4 col-12 md:col-6">
|
||||
<label for="brokerLicensing" class="block font-medium text-900 mb-2">Broker Licensing</label>
|
||||
<input id="brokerLicensing" type="text" pInputText [(ngModel)]="listing.brokerLicencing">
|
||||
</div>
|
||||
<div class="mb-4 col-12 md:col-6">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="internalListing" class="block font-medium text-900 mb-2">Internal Listing (Will not be shown on the listing, for your records only.)</label>
|
||||
<label for="internalListing" class="block font-medium text-900 mb-2">Internal Notes (Will not be shown on the listing, for your records only.)</label>
|
||||
<input id="internalListing" type="text" pInputText [(ngModel)]="listing.internals">
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="mb-4 col-12 md:col-6 ">
|
||||
<!-- <p-tag value="New"></p-tag> -->
|
||||
<!-- <p-checkbox [binary]="true" [(ngModel)]="listing.draft"></p-checkbox> -->
|
||||
<p-inputSwitch inputId="draft" [(ngModel)]="listing.draft"></p-inputSwitch>
|
||||
<!-- <span class="ml-2 text-900">Share my data with contacts</span> -->
|
||||
<span class="ml-2 text-900 absolute translate-y-5">Draft Mode (Will not be shown as public listing)</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div>
|
||||
@if (mode==='create'){
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
.translate-y-5{
|
||||
transform: translateY(5px);
|
||||
}
|
||||
|
|
@ -25,7 +25,11 @@ import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
|
|||
import { UserService } from '../../../services/user.service';
|
||||
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||
import { MessageService } from 'primeng/api';
|
||||
import { 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';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'create-listing',
|
||||
standalone: true,
|
||||
|
|
@ -43,7 +47,13 @@ export class EditListingComponent {
|
|||
listing:ListingType = createGenericObject<BusinessListing>();
|
||||
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
||||
user:User;
|
||||
constructor(public selectOptions:SelectOptionsService,private router: Router,private activatedRoute: ActivatedRoute,private listingsService:ListingsService,public userService: UserService,private messageService: MessageService){
|
||||
constructor(public selectOptions:SelectOptionsService,
|
||||
private router: Router,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private listingsService:ListingsService,
|
||||
public userService: UserService,
|
||||
private messageService: MessageService,
|
||||
private geoService:GeoService){
|
||||
this.user=this.userService.getUser();
|
||||
// Abonniere Router-Events, um den aktiven Link zu ermitteln
|
||||
this.router.events.subscribe(event => {
|
||||
|
|
@ -62,10 +72,10 @@ export class EditListingComponent {
|
|||
this.listing.listingsCategory='business';
|
||||
}
|
||||
}
|
||||
updateSummary(value: string): void {
|
||||
const lines = value.split('\n');
|
||||
(<BusinessListing>this.listing).summary = lines.filter(l=>l.trim().length>0);
|
||||
}
|
||||
// updateSummary(value: string): void {
|
||||
// const lines = value.split('\n');
|
||||
// (<BusinessListing>this.listing).summary = lines.filter(l=>l.trim().length>0);
|
||||
// }
|
||||
async update(id:string){
|
||||
await this.listingsService.update(this.listing,this.listing.id);
|
||||
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing has been updated', life: 3000 });
|
||||
|
|
@ -74,4 +84,11 @@ export class EditListingComponent {
|
|||
await this.listingsService.create(this.listing);
|
||||
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing has been created', life: 3000 });
|
||||
}
|
||||
|
||||
suggestions: string[] | undefined;
|
||||
|
||||
async search(event: AutoCompleteCompleteEvent) {
|
||||
const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query,this.listing.state))//[...Array(5).keys()].map(item => event.query + '-' + item);
|
||||
this.suggestions = result.map(r=>r.city).slice(0,5);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
<tr>
|
||||
<td class="wide-column line-height-3">{{ listing.title }}</td>
|
||||
<td>{{ selectOptions.getListingsCategory(listing.listingsCategory) }}</td>
|
||||
<td>{{ selectOptions.getLocation(listing.location) }}</td>
|
||||
<td>{{ selectOptions.getState(listing.state) }}</td>
|
||||
<td>
|
||||
<button pButton pRipple icon="pi pi-eye" class="p-button-rounded p-button-success mr-2" [routerLink]="['/details',listing.id]"></button>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
<tr>
|
||||
<td class="wide-column line-height-3">{{ listing.title }}</td>
|
||||
<td>{{ selectOptions.getListingsCategory(listing.listingsCategory) }}</td>
|
||||
<td>{{ selectOptions.getLocation(listing.location) }}</td>
|
||||
<td>{{ selectOptions.getState(listing.location) }}</td>
|
||||
<td>
|
||||
<button pButton pRipple icon="pi pi-pencil" class="p-button-rounded p-button-success mr-2" [routerLink]="['/editListing',listing.id]"></button>
|
||||
<button pButton pRipple icon="pi pi-trash" class="p-button-rounded p-button-warning" (click)="confirm($event,listing)"></button>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { Observable } from 'rxjs';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
export interface GeoResult { city: string; state: string; state_code: string }
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class GeoService {
|
||||
|
||||
private apiBaseUrl = environment.apiBaseUrl;
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
findCitiesStartingWith(prefix:string, state?:string):Observable<GeoResult[]>{
|
||||
const stateString = state?`/${state}`:''
|
||||
return this.http.get<GeoResult[]>(`${this.apiBaseUrl}/bizmatch/geo/${prefix}${stateString}`);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ export class SelectOptionsService {
|
|||
this.prices = allSelectOptions.prices;
|
||||
this.listingCategories = allSelectOptions.listingCategories;
|
||||
this.categories = allSelectOptions.categories;
|
||||
this.locations = allSelectOptions.locations;
|
||||
this.states = allSelectOptions.locations;
|
||||
}
|
||||
public typesOfBusiness: Array<KeyValueStyle>;
|
||||
|
||||
|
|
@ -30,10 +30,10 @@ export class SelectOptionsService {
|
|||
|
||||
public categories: Array<KeyValueStyle>;
|
||||
|
||||
public locations: Array<any>;
|
||||
public states: Array<any>;
|
||||
|
||||
getLocation(value:string):string{
|
||||
return this.locations.find(l=>l.value===value)?.name
|
||||
getState(value:string):string{
|
||||
return this.states.find(l=>l.value===value)?.name
|
||||
}
|
||||
|
||||
getBusiness(value:string):string{
|
||||
|
|
|
|||
|
|
@ -1,20 +1,25 @@
|
|||
import { Injectable, Signal, WritableSignal, computed, effect, signal } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
import { Observable, distinctUntilChanged, filter, from, map } from 'rxjs';
|
||||
import { Observable, distinctUntilChanged, filter, from, lastValueFrom, map } from 'rxjs';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { KeycloakService } from './keycloak.service';
|
||||
import { JwtToken, User } from '../../../../common-models/src/main.model';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserService {
|
||||
|
||||
private apiBaseUrl = environment.apiBaseUrl;
|
||||
// -----------------------------
|
||||
// Keycloak services
|
||||
// -----------------------------
|
||||
private user$ = new Observable<User>();
|
||||
private user:User
|
||||
public $isLoggedIn : Signal<boolean>;
|
||||
constructor(public keycloak:KeycloakService){
|
||||
constructor(public keycloak:KeycloakService,private http: HttpClient){
|
||||
this.user$ = from(this.keycloak.getToken()).pipe(
|
||||
filter(t => !!t),
|
||||
distinctUntilChanged(),
|
||||
|
|
@ -50,15 +55,12 @@ export class UserService {
|
|||
const token = await this.keycloak.getToken();
|
||||
this.user = this.map2User(token);
|
||||
}
|
||||
getUserName(){
|
||||
return this.user?.username
|
||||
}
|
||||
|
||||
|
||||
private map2User(jwt:string):User{
|
||||
const token = jwtDecode<JwtToken>(jwt);
|
||||
return {
|
||||
id:token.user_id,
|
||||
username:token.preferred_username,
|
||||
firstname:token.given_name,
|
||||
lastname:token.family_name,
|
||||
email:token.email
|
||||
|
|
@ -91,4 +93,14 @@ export class UserService {
|
|||
register(url:string){
|
||||
this.keycloak.register({redirectUri:url});
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// Redis services
|
||||
// -----------------------------
|
||||
async save(user:User):Promise<User>{
|
||||
return await lastValueFrom(this.http.post<User>(`${this.apiBaseUrl}/bizmatch/user`,user));
|
||||
}
|
||||
async getById(id:string):Promise<User>{
|
||||
return await lastValueFrom(this.http.get<User>(`${this.apiBaseUrl}/bizmatch/user/${id}`));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,14 +20,15 @@ import { InputNumberModule } from 'primeng/inputnumber';
|
|||
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
||||
import { ConfirmPopupModule } from 'primeng/confirmpopup';
|
||||
import { ToastModule } from 'primeng/toast';
|
||||
|
||||
import { AutoCompleteModule } from 'primeng/autocomplete';
|
||||
import { InputSwitchModule } from 'primeng/inputswitch';
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
CommonModule, StyleClassModule, DividerModule,ButtonModule, TableModule, InputTextModule, DropdownModule, FormsModule, ChipModule,InputTextareaModule, RouterModule,FontAwesomeModule,MenuAccountComponent,InputNumberModule,ConfirmDialogModule,ConfirmPopupModule, ToastModule, CheckboxModule
|
||||
CommonModule, StyleClassModule, DividerModule,ButtonModule, TableModule, InputTextModule, DropdownModule, FormsModule, ChipModule,InputTextareaModule, RouterModule,FontAwesomeModule,MenuAccountComponent,InputNumberModule,ConfirmDialogModule,ConfirmPopupModule, ToastModule, CheckboxModule, AutoCompleteModule,InputSwitchModule
|
||||
],
|
||||
exports:[
|
||||
CommonModule, StyleClassModule, DividerModule,ButtonModule, TableModule, InputTextModule, DropdownModule, FormsModule, ChipModule,InputTextareaModule,RouterModule,FontAwesomeModule,MenuAccountComponent,InputNumberModule,ConfirmDialogModule,ConfirmPopupModule, ToastModule, CheckboxModule
|
||||
CommonModule, StyleClassModule, DividerModule,ButtonModule, TableModule, InputTextModule, DropdownModule, FormsModule, ChipModule,InputTextareaModule,RouterModule,FontAwesomeModule,MenuAccountComponent,InputNumberModule,ConfirmDialogModule,ConfirmPopupModule, ToastModule, CheckboxModule, AutoCompleteModule, TagModule,InputSwitchModule
|
||||
]
|
||||
})
|
||||
export class SharedModule { }
|
||||
|
|
|
|||
|
|
@ -18,24 +18,30 @@ export interface Listing {
|
|||
userId: string;
|
||||
title: string;
|
||||
description: Array<string>;
|
||||
location: string;//enum
|
||||
country: string;
|
||||
city: string,
|
||||
state: string;//enum
|
||||
favoritesForUser:Array<string>;
|
||||
hideImage?:boolean;
|
||||
draft?:boolean;
|
||||
created:Date;
|
||||
updated:Date;
|
||||
}
|
||||
export interface BusinessListing extends Listing {
|
||||
listingsCategory: 'business'; //enum
|
||||
summary: Array<string>;
|
||||
// summary: Array<string>;
|
||||
type: string; //enum
|
||||
price?: number;
|
||||
realEstateIncluded?: boolean;
|
||||
leasedLocation?:boolean;
|
||||
franchiseResale?:boolean;
|
||||
salesRevenue?: number;
|
||||
cashFlow?: number;
|
||||
netProfit?: number;
|
||||
inventory?: string;
|
||||
// netProfit?: number;
|
||||
supportAndTraining?: string;
|
||||
employees?: number;
|
||||
established?: number;
|
||||
internalListingNumber?:number;
|
||||
reasonForSale?: string;
|
||||
brokerLicencing?: string;
|
||||
internals?: string;
|
||||
|
|
@ -68,13 +74,22 @@ export interface ListingCriteria {
|
|||
listingsCategory:'business'|'professionals_brokers'|'investment',
|
||||
category:'professional|broker'
|
||||
}
|
||||
export interface User {
|
||||
export interface UserBase {
|
||||
id: string;
|
||||
username: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
email: string;
|
||||
phoneNumber?: string;
|
||||
companyOverview?:string;
|
||||
companyWebsite?:string;
|
||||
companyLocation?:string;
|
||||
offeredServices?:string;
|
||||
areasServed?:string;
|
||||
}
|
||||
export interface User extends UserBase {
|
||||
licensedIn?:KeyValue[];
|
||||
}
|
||||
|
||||
export interface Subscription {
|
||||
id: string;
|
||||
userId:string
|
||||
|
|
@ -127,3 +142,7 @@ export interface PageEvent {
|
|||
page: number;
|
||||
pageCount: number;
|
||||
}
|
||||
export interface AutoCompleteCompleteEvent {
|
||||
originalEvent: Event;
|
||||
query: string;
|
||||
}
|
||||
Loading…
Reference in New Issue