Compare commits
11 Commits
e784b424b0
...
5230ef1230
| Author | SHA1 | Date | |
|---|---|---|---|
| 5230ef1230 | |||
| d508415de4 | |||
| 6b61c19bd7 | |||
| bb5a408cdc | |||
| 9121ca1a69 | |||
| 4230867608 | |||
| 9e03620be7 | |||
| 7f0f21b598 | |||
| c90d6b72b7 | |||
| c4cdcf4505 | |||
| 7d10080069 |
16
bizmatch-server/.editorconfig
Normal file
16
bizmatch-server/.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Editor configuration, see https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
||||||
45
bizmatch-server/.eslintrc.json
Normal file
45
bizmatch-server/.eslintrc.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"es2021": true,
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"airbnb-base",
|
||||||
|
"airbnb-typescript",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"eslint-config-prettier",
|
||||||
|
"plugin:cypress/recommended"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 12,
|
||||||
|
"sourceType": "module",
|
||||||
|
"project": ["./tsconfig.json"]
|
||||||
|
},
|
||||||
|
"plugins": ["@typescript-eslint"],
|
||||||
|
"rules": {
|
||||||
|
"import/no-unresolved": ["off"],
|
||||||
|
"import/prefer-default-export": ["off"],
|
||||||
|
"no-useless-constructor": "off",
|
||||||
|
"@typescript-eslint/no-useless-constructor": ["error"],
|
||||||
|
"@typescript-eslint/lines-between-class-members": ["off"],
|
||||||
|
"no-param-reassign": ["off"],
|
||||||
|
"max-classes-per-file": ["off"],
|
||||||
|
"no-shadow": ["off"],
|
||||||
|
"class-methods-use-this": ["off"],
|
||||||
|
"react/jsx-filename-extension": ["off"],
|
||||||
|
"import/no-cycle": ["off"],
|
||||||
|
"radix": ["off"],
|
||||||
|
"no-promise-executor-return": ["off"],
|
||||||
|
"@typescript-eslint/naming-convention": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"selector": "enumMember",
|
||||||
|
"format": ["UPPER_CASE", "PascalCase"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"],
|
||||||
|
"spaced-comment": ["off"],
|
||||||
|
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,18 @@
|
|||||||
{
|
{
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"embeddedLanguageFormatting": "auto",
|
||||||
|
"htmlWhitespaceSensitivity": "css",
|
||||||
|
"insertPragma": false,
|
||||||
|
"jsxBracketSameLine": false,
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"printWidth": 220,
|
||||||
|
"proseWrap": "always",
|
||||||
|
"quoteProps": "as-needed",
|
||||||
|
"requirePragma": false,
|
||||||
|
"semi": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all"
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"useTabs": false,
|
||||||
|
"vueIndentScriptAndStyle": false
|
||||||
}
|
}
|
||||||
45
bizmatch-server/.vscode/launch.json
vendored
45
bizmatch-server/.vscode/launch.json
vendored
@@ -17,6 +17,49 @@
|
|||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"stopOnEntry": false,
|
"stopOnEntry": false,
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug Current TS File",
|
||||||
|
"program": "${workspaceFolder}/dist/src/drizzle/${fileBasenameNoExtension}.js",
|
||||||
|
"preLaunchTask": "tsc: build - tsconfig.json",
|
||||||
|
"outFiles": [
|
||||||
|
"${workspaceFolder}/out/**/*.js"
|
||||||
|
],
|
||||||
|
"sourceMaps": true,
|
||||||
|
"smartStep": true,
|
||||||
|
"internalConsoleOptions": "openOnSessionStart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "generateDefs",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
|
"program": "${workspaceFolder}/dist/src/drizzle/generateDefs.js",
|
||||||
|
"outFiles": [
|
||||||
|
"${workspaceFolder}/dist/src/drizzle/**/*.js"
|
||||||
|
],
|
||||||
|
"sourceMaps": true,
|
||||||
|
"smartStep": true,
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "generateTypes",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
|
"program": "${workspaceFolder}/dist/src/drizzle/generateTypes.js",
|
||||||
|
"outFiles": [
|
||||||
|
"${workspaceFolder}/dist/src/drizzle/**/*.js"
|
||||||
|
],
|
||||||
|
"sourceMaps": true,
|
||||||
|
"smartStep": true,
|
||||||
|
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
28
bizmatch-server/.vscode/settings.json
vendored
Normal file
28
bizmatch-server/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"editor.suggestSelection": "first",
|
||||||
|
"vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue",
|
||||||
|
"explorer.confirmDelete": false,
|
||||||
|
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[html]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.defaultFormatter": "vscode.json-language-features"
|
||||||
|
},
|
||||||
|
"[scss]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[jsonc]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.organizeImports": "explicit"
|
||||||
|
},
|
||||||
|
"prettier.printWidth": 240,
|
||||||
|
"git.autofetch": false,
|
||||||
|
"git.autorefresh": true
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
FT.CREATE listingsIndex ON JSON PREFIX 1 listings: SCHEMA $.location AS location TAG SORTABLE $.price AS price NUMERIC SORTABLE $.listingsCategory AS listingsCategory TAG SORTABLE $.type AS type TAG SORTABLE
|
|
||||||
1326
bizmatch-server/broker.json
Normal file
1326
bizmatch-server/broker.json
Normal file
File diff suppressed because it is too large
Load Diff
1371
bizmatch-server/data/broker.json
Normal file
1371
bizmatch-server/data/broker.json
Normal file
File diff suppressed because it is too large
Load Diff
12493
bizmatch-server/data/businesses.json
Normal file
12493
bizmatch-server/data/businesses.json
Normal file
File diff suppressed because it is too large
Load Diff
2858
bizmatch-server/data/commercials.json
Normal file
2858
bizmatch-server/data/commercials.json
Normal file
File diff suppressed because it is too large
Load Diff
11
bizmatch-server/drizzle.config.ts
Normal file
11
bizmatch-server/drizzle.config.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { defineConfig } from 'drizzle-kit'
|
||||||
|
export default defineConfig({
|
||||||
|
schema: "./src/drizzle/schema.ts",
|
||||||
|
out: "./src/drizzle/migrations",
|
||||||
|
driver: 'pg',
|
||||||
|
dbCredentials: {
|
||||||
|
connectionString: process.env.DATABASE_URL,
|
||||||
|
},
|
||||||
|
verbose: true,
|
||||||
|
strict: true,
|
||||||
|
})
|
||||||
@@ -4,7 +4,10 @@
|
|||||||
"sourceRoot": "src",
|
"sourceRoot": "src",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"deleteOutDir": true,
|
"deleteOutDir": true,
|
||||||
"assets": ["assets/**/*","**/*.hbs"],
|
"assets": [
|
||||||
"watchAssets": true
|
"assets/**/*",
|
||||||
|
"**/*.hbs"
|
||||||
|
],
|
||||||
|
"watchAssets": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,12 @@
|
|||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||||
|
"generate": "drizzle-kit generate:pg",
|
||||||
|
"drop": "drizzle-kit drop",
|
||||||
|
"migrate": "tsx src/drizzle/migrate.ts",
|
||||||
|
"import": "tsx src/drizzle/import.ts",
|
||||||
|
"generateTypes": "tsx src/drizzle/generateTypes.ts src/drizzle/schema.ts src/models/db.model.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs-modules/mailer": "^1.10.3",
|
"@nestjs-modules/mailer": "^1.10.3",
|
||||||
@@ -29,6 +34,9 @@
|
|||||||
"@nestjs/passport": "^10.0.3",
|
"@nestjs/passport": "^10.0.3",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@nestjs/serve-static": "^4.0.1",
|
"@nestjs/serve-static": "^4.0.1",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"drizzle-orm": "^0.30.8",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"ky": "^1.2.0",
|
"ky": "^1.2.0",
|
||||||
"nest-winston": "^1.9.4",
|
"nest-winston": "^1.9.4",
|
||||||
@@ -38,15 +46,19 @@
|
|||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
|
"pg": "^8.11.5",
|
||||||
"redis": "^4.6.13",
|
"redis": "^4.6.13",
|
||||||
"redis-om": "^0.4.3",
|
"redis-om": "^0.4.3",
|
||||||
"reflect-metadata": "^0.2.0",
|
"reflect-metadata": "^0.2.0",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"sharp": "^0.33.2",
|
"sharp": "^0.33.2",
|
||||||
|
"tsx": "^4.7.2",
|
||||||
"urlcat": "^3.1.0",
|
"urlcat": "^3.1.0",
|
||||||
"winston": "^3.11.0"
|
"winston": "^3.11.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/parser": "^7.24.4",
|
||||||
|
"@babel/traverse": "^7.24.1",
|
||||||
"@nestjs/cli": "^10.0.0",
|
"@nestjs/cli": "^10.0.0",
|
||||||
"@nestjs/schematics": "^10.0.0",
|
"@nestjs/schematics": "^10.0.0",
|
||||||
"@nestjs/testing": "^10.0.0",
|
"@nestjs/testing": "^10.0.0",
|
||||||
@@ -58,14 +70,20 @@
|
|||||||
"@types/passport-google-oauth20": "^2.0.14",
|
"@types/passport-google-oauth20": "^2.0.14",
|
||||||
"@types/passport-jwt": "^4.0.1",
|
"@types/passport-jwt": "^4.0.1",
|
||||||
"@types/passport-local": "^1.0.38",
|
"@types/passport-local": "^1.0.38",
|
||||||
|
"@types/pg": "^8.11.5",
|
||||||
"@types/supertest": "^6.0.0",
|
"@types/supertest": "^6.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||||
"@typescript-eslint/parser": "^6.0.0",
|
"@typescript-eslint/parser": "^6.0.0",
|
||||||
|
"commander": "^12.0.0",
|
||||||
|
"drizzle-kit": "^0.20.16",
|
||||||
"eslint": "^8.42.0",
|
"eslint": "^8.42.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
|
"kysely-codegen": "^0.15.0",
|
||||||
|
"pg-to-ts": "^4.1.1",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
|
"rimraf": "^5.0.5",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^6.3.3",
|
"supertest": "^6.3.3",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
|
|||||||
@@ -1,60 +1,57 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { MiddlewareConsumer, Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { WinstonModule, utilities as nestWinstonModuleUtilities } from 'nest-winston';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import * as winston from 'winston';
|
||||||
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 { FileService } from './file/file.service.js';
|
|
||||||
import { AuthService } from './auth/auth.service.js';
|
|
||||||
import { AuthController } from './auth/auth.controller.js';
|
|
||||||
import { ConfigModule } from '@nestjs/config';
|
|
||||||
import { SelectOptionsController } from './select-options/select-options.controller.js';
|
|
||||||
import { SelectOptionsService } from './select-options/select-options.service.js';
|
|
||||||
import { SubscriptionsController } from './subscriptions/subscriptions.controller.js';
|
|
||||||
import { RedisModule } from './redis/redis.module.js';
|
|
||||||
import { ListingsService } from './listings/listings.service.js';
|
|
||||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
|
||||||
import path, { join } from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
import { utilities as nestWinstonModuleUtilities, WinstonModule } from 'nest-winston';
|
|
||||||
import * as winston from 'winston';
|
|
||||||
import { MailModule } from './mail/mail.module.js';
|
|
||||||
import { AuthModule } from './auth/auth.module.js';
|
import { AuthModule } from './auth/auth.module.js';
|
||||||
|
import { FileService } from './file/file.service.js';
|
||||||
import { GeoModule } from './geo/geo.module.js';
|
import { GeoModule } from './geo/geo.module.js';
|
||||||
import { UserModule } from './user/user.module.js';
|
|
||||||
import { ListingsModule } from './listings/listings.module.js';
|
|
||||||
import { SelectOptionsModule } from './select-options/select-options.module.js';
|
|
||||||
import { CommercialPropertyListingsController } from './listings/commercial-property-listings.controller.js';
|
|
||||||
import { ImageModule } from './image/image.module.js';
|
import { ImageModule } from './image/image.module.js';
|
||||||
|
import { ListingsModule } from './listings/listings.module.js';
|
||||||
|
import { MailModule } from './mail/mail.module.js';
|
||||||
|
import { RequestDurationMiddleware } from './request-duration/request-duration.middleware.js';
|
||||||
|
import { SelectOptionsModule } from './select-options/select-options.module.js';
|
||||||
|
import { SubscriptionsController } from './subscriptions/subscriptions.controller.js';
|
||||||
|
import { UserModule } from './user/user.module.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule.forRoot(), MailModule, AuthModule,
|
imports: [
|
||||||
ServeStaticModule.forRoot({
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
rootPath: join(__dirname, '..', 'pictures'), // `public` ist das Verzeichnis, wo Ihre statischen Dateien liegen
|
MailModule,
|
||||||
}),
|
AuthModule,
|
||||||
WinstonModule.forRoot({
|
WinstonModule.forRoot({
|
||||||
transports: [
|
transports: [
|
||||||
new winston.transports.Console({
|
new winston.transports.Console({
|
||||||
format: winston.format.combine(
|
format: winston.format.combine(
|
||||||
winston.format.timestamp(),
|
winston.format.timestamp(),
|
||||||
winston.format.ms(),
|
winston.format.ms(),
|
||||||
nestWinstonModuleUtilities.format.nestLike('Bizmatch', {
|
nestWinstonModuleUtilities.format.nestLike('Bizmatch', {
|
||||||
colors: true,
|
colors: true,
|
||||||
prettyPrint: true,
|
prettyPrint: true,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
// other transports...
|
// other transports...
|
||||||
],
|
],
|
||||||
// other options
|
// other options
|
||||||
}),
|
}),
|
||||||
GeoModule,
|
GeoModule,
|
||||||
UserModule,
|
UserModule,
|
||||||
ListingsModule,
|
ListingsModule,
|
||||||
SelectOptionsModule,
|
SelectOptionsModule,
|
||||||
RedisModule,
|
ImageModule,
|
||||||
ImageModule
|
|
||||||
],
|
],
|
||||||
controllers: [AppController, SubscriptionsController],
|
controllers: [AppController, SubscriptionsController],
|
||||||
providers: [AppService, FileService],
|
providers: [AppService, FileService],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {
|
||||||
|
configure(consumer: MiddlewareConsumer) {
|
||||||
|
consumer.apply(RequestDurationMiddleware).forRoutes('*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
27
bizmatch-server/src/drizzle/drizzle.module.ts
Normal file
27
bizmatch-server/src/drizzle/drizzle.module.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
|
import pkg from 'pg';
|
||||||
|
const { Pool } = pkg;
|
||||||
|
import * as schema from './schema.js';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { jsonb, varchar } from 'drizzle-orm/pg-core';
|
||||||
|
import { PG_CONNECTION } from './schema.js';
|
||||||
|
@Module({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: PG_CONNECTION,
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: async (configService: ConfigService) => {
|
||||||
|
const connectionString = configService.get<string>('DATABASE_URL');
|
||||||
|
const pool = new Pool({
|
||||||
|
connectionString,
|
||||||
|
// ssl: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return drizzle(pool, { schema, logger:true });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
exports: [PG_CONNECTION],
|
||||||
|
})
|
||||||
|
export class DrizzleModule {}
|
||||||
143
bizmatch-server/src/drizzle/generateTypes.ts
Normal file
143
bizmatch-server/src/drizzle/generateTypes.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import ts from 'typescript';
|
||||||
|
export const TABLE_BO_MAPPING = {
|
||||||
|
'users':'User',
|
||||||
|
'businesses':'BusinessListing',
|
||||||
|
'commercials':'CommercialPropertyListing'
|
||||||
|
}
|
||||||
|
function generateInterfaceDefinitions(inputFile: string, outputFile: string): void {
|
||||||
|
const sourceFile = ts.createSourceFile(
|
||||||
|
inputFile,
|
||||||
|
fs.readFileSync(inputFile, 'utf8'),
|
||||||
|
ts.ScriptTarget.Latest,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
let interfaceDefinitions = '';
|
||||||
|
|
||||||
|
ts.forEachChild(sourceFile, (node) => {
|
||||||
|
if (ts.isVariableStatement(node)) {
|
||||||
|
const variableDeclaration = node.declarationList.declarations[0];
|
||||||
|
if (variableDeclaration.initializer && ts.isCallExpression(variableDeclaration.initializer)) {
|
||||||
|
const initializer = variableDeclaration.initializer;
|
||||||
|
if (initializer.expression.getText(sourceFile) === 'pgTable') {
|
||||||
|
const tableName = initializer.arguments[0].getText(sourceFile).slice(1, -1); // Entfernen von zusätzlichen Anführungszeichen
|
||||||
|
const tableDefinition = initializer.arguments[1];
|
||||||
|
|
||||||
|
const interfaceName = TABLE_BO_MAPPING[tableName] ? TABLE_BO_MAPPING[tableName] : tableName.charAt(0).toUpperCase() + tableName.slice(1);
|
||||||
|
let interfaceDefinition = `export interface ${interfaceName} {\n`;
|
||||||
|
|
||||||
|
ts.forEachChild(tableDefinition, (propertyNode) => {
|
||||||
|
if (ts.isPropertyAssignment(propertyNode)) {
|
||||||
|
const propertyName = propertyNode.name.getText(sourceFile);
|
||||||
|
const propertyDefinition = propertyNode.initializer;
|
||||||
|
|
||||||
|
if (ts.isCallExpression(propertyDefinition)) {
|
||||||
|
const propertyType = getPropertyType(propertyDefinition, sourceFile);
|
||||||
|
const isOptional = !hasNonOptionalModifier(propertyDefinition.expression);
|
||||||
|
interfaceDefinition += ` ${propertyName}${isOptional ? '?' : ''}: ${propertyType};\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interfaceDefinition += '}\n\n';
|
||||||
|
interfaceDefinitions += interfaceDefinition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(outputFile, interfaceDefinitions);
|
||||||
|
console.log(`Interface definitions generated successfully. Output file: ${outputFile}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPropertyType(propertyDefinition: ts.CallExpression, sourceFile: ts.SourceFile): string {
|
||||||
|
const typeFunction = getTypeFunctionName(propertyDefinition.expression, sourceFile);
|
||||||
|
|
||||||
|
let propertyType = '';
|
||||||
|
|
||||||
|
switch (typeFunction) {
|
||||||
|
case 'varchar':
|
||||||
|
case 'char':
|
||||||
|
case 'text':
|
||||||
|
case 'uuid':
|
||||||
|
propertyType = 'string';
|
||||||
|
break;
|
||||||
|
case 'integer':
|
||||||
|
case 'numeric':
|
||||||
|
case 'real':
|
||||||
|
case 'doublePrecision':
|
||||||
|
propertyType = 'number';
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
propertyType = 'boolean';
|
||||||
|
break;
|
||||||
|
case 'timestamp':
|
||||||
|
propertyType = 'Date';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
propertyType = 'any';
|
||||||
|
}
|
||||||
|
|
||||||
|
const isArray = hasArrayModifier(propertyDefinition.expression);
|
||||||
|
return isArray ? `${propertyType}[]` : propertyType;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeFunctionName(expression: ts.Expression, sourceFile: ts.SourceFile): string {
|
||||||
|
// Prüfen, ob die aktuelle Expression ein Identifier ist (SyntaxKind.Identifier hat den Wert 80)
|
||||||
|
if (expression.kind === ts.SyntaxKind.Identifier) {
|
||||||
|
return expression.getText(sourceFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn die Expression eine CallExpression oder eine PropertyAccessExpression ist,
|
||||||
|
// gehe zur nächsten Expression-Ebene
|
||||||
|
if (ts.isCallExpression(expression) || ts.isPropertyAccessExpression(expression)) {
|
||||||
|
return getTypeFunctionName(expression.expression, sourceFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Falls ein nicht unterstützter Expression-Typ vorliegt, gibt 'unknown' zurück
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasArrayModifier(expression: ts.Expression): boolean {
|
||||||
|
// Prüfe, ob die aktuelle Expression eine CallExpression ist und der Funktionsname 'array' ist
|
||||||
|
if (ts.isPropertyAccessExpression(expression) && expression.name.getText() === 'array') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn die Expression eine weitere CallExpression oder PropertyAccessExpression ist,
|
||||||
|
// prüfe rekursiv die nächste Ebene
|
||||||
|
if (ts.isCallExpression(expression) || ts.isPropertyAccessExpression(expression)) {
|
||||||
|
return hasArrayModifier(expression.expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn keine weitere Ebene oder kein Array-Modifier gefunden wurde, gib false zurück
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasNonOptionalModifier(expression: ts.Expression): boolean {
|
||||||
|
// Prüfe, ob die aktuelle Expression eine CallExpression ist und der Funktionsname 'notNull' ist
|
||||||
|
if (ts.isPropertyAccessExpression(expression) && (expression.name.getText() === 'notNull' || expression.name.getText() === 'primaryKey')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn die Expression eine weitere CallExpression oder PropertyAccessExpression ist,
|
||||||
|
// prüfe rekursiv die nächste Ebene
|
||||||
|
if (ts.isCallExpression(expression) || ts.isPropertyAccessExpression(expression)) {
|
||||||
|
return hasNonOptionalModifier(expression.expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn keine weitere Ebene oder kein NotNull-Modifier gefunden wurde, gib false zurück
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hauptprogramm
|
||||||
|
const inputFile = process.argv[2]|| './src/drizzle/schema.ts';
|
||||||
|
const outputFile = process.argv[3] || './model.ts';
|
||||||
|
|
||||||
|
if (!inputFile) {
|
||||||
|
console.error('Please provide an input file.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateInterfaceDefinitions(inputFile, outputFile);
|
||||||
153
bizmatch-server/src/drizzle/import.ts
Normal file
153
bizmatch-server/src/drizzle/import.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import 'dotenv/config';
|
||||||
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
|
import { existsSync, readFileSync, readdirSync, statSync, unlinkSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
import pkg from 'pg';
|
||||||
|
import { rimraf } from 'rimraf';
|
||||||
|
import sharp from 'sharp';
|
||||||
|
import { BusinessListing, CommercialPropertyListing, User } from 'src/models/db.model.js';
|
||||||
|
import * as schema from './schema.js';
|
||||||
|
const { Pool } = pkg;
|
||||||
|
|
||||||
|
const connectionString = process.env.DATABASE_URL;
|
||||||
|
// const pool = new Pool({connectionString})
|
||||||
|
const client = new Pool({ connectionString });
|
||||||
|
const db = drizzle(client, { schema, logger: true });
|
||||||
|
|
||||||
|
//Delete Content
|
||||||
|
await db.delete(schema.commercials);
|
||||||
|
await db.delete(schema.businesses);
|
||||||
|
await db.delete(schema.users);
|
||||||
|
|
||||||
|
//Broker
|
||||||
|
let filePath = `./data/broker.json`;
|
||||||
|
let data: string = readFileSync(filePath, 'utf8');
|
||||||
|
const userData: User[] = JSON.parse(data); // Erwartet ein Array von Objekten
|
||||||
|
const generatedUserData = [];
|
||||||
|
console.log(userData.length);
|
||||||
|
let i = 0,
|
||||||
|
male = 0,
|
||||||
|
female = 0;
|
||||||
|
const targetPathProfile = `./pictures/profile`;
|
||||||
|
deleteFilesOfDir(targetPathProfile);
|
||||||
|
const targetPathLogo = `./pictures/logo`;
|
||||||
|
deleteFilesOfDir(targetPathLogo);
|
||||||
|
for (const user of userData) {
|
||||||
|
delete user.id;
|
||||||
|
user.licensedIn = user.licensedIn.map(l => `${l['name']}|${l['value']}`);
|
||||||
|
user.hasCompanyLogo = true;
|
||||||
|
user.hasProfile = true;
|
||||||
|
const u = await db.insert(schema.users).values(user).returning({ insertedId: schema.users.id, gender: schema.users.gender });
|
||||||
|
generatedUserData.push(u[0].insertedId);
|
||||||
|
i++;
|
||||||
|
if (u[0].gender === 'male') {
|
||||||
|
male++;
|
||||||
|
const data = readFileSync(`./pictures/profile_base/Mann_${male}.jpg`);
|
||||||
|
await storeProfilePicture(data, u[0].insertedId);
|
||||||
|
} else {
|
||||||
|
female++;
|
||||||
|
const data = readFileSync(`./pictures/profile_base/Frau_${male}.jpg`);
|
||||||
|
await storeProfilePicture(data, u[0].insertedId);
|
||||||
|
}
|
||||||
|
const data = readFileSync(`./pictures/logos_base/${i}.jpg`);
|
||||||
|
await storeCompanyLogo(data, u[0].insertedId);
|
||||||
|
}
|
||||||
|
//Business Listings
|
||||||
|
filePath = `./data/businesses.json`;
|
||||||
|
data = readFileSync(filePath, 'utf8');
|
||||||
|
const businessJsonData = JSON.parse(data) as BusinessListing[]; // Erwartet ein Array von Objekten
|
||||||
|
|
||||||
|
for (const business of businessJsonData) {
|
||||||
|
delete business.id;
|
||||||
|
business.created = new Date(business.created);
|
||||||
|
business.userId = getRandomItem(generatedUserData);
|
||||||
|
await db.insert(schema.businesses).values(business);
|
||||||
|
}
|
||||||
|
//Corporate Listings
|
||||||
|
filePath = `./data/commercials.json`;
|
||||||
|
data = readFileSync(filePath, 'utf8');
|
||||||
|
const commercialJsonData = JSON.parse(data) as CommercialPropertyListing[]; // Erwartet ein Array von Objekten
|
||||||
|
for (const commercial of commercialJsonData) {
|
||||||
|
const id = commercial.id;
|
||||||
|
delete commercial.id;
|
||||||
|
|
||||||
|
commercial.imageOrder = getFilenames(id);
|
||||||
|
commercial.imagePath = id;
|
||||||
|
commercial.created = getRandomDateWithinLastYear();
|
||||||
|
commercial.userId = getRandomItem(generatedUserData);
|
||||||
|
await db.insert(schema.commercials).values(commercial);
|
||||||
|
}
|
||||||
|
|
||||||
|
//End
|
||||||
|
await client.end();
|
||||||
|
|
||||||
|
function getRandomItem<T>(arr: T[]): T {
|
||||||
|
if (arr.length === 0) {
|
||||||
|
throw new Error('The array is empty.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const randomIndex = Math.floor(Math.random() * arr.length);
|
||||||
|
return arr[randomIndex];
|
||||||
|
}
|
||||||
|
function getFilenames(id: string): string[] {
|
||||||
|
try {
|
||||||
|
let filePath = `./pictures/property/${id}`;
|
||||||
|
return readdirSync(filePath);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getRandomDateWithinLastYear(): Date {
|
||||||
|
const currentDate = new Date();
|
||||||
|
const lastYear = new Date(currentDate.getFullYear() - 1, currentDate.getMonth(), currentDate.getDate());
|
||||||
|
|
||||||
|
const timeDiff = currentDate.getTime() - lastYear.getTime();
|
||||||
|
const randomTimeDiff = Math.random() * timeDiff;
|
||||||
|
const randomDate = new Date(lastYear.getTime() + randomTimeDiff);
|
||||||
|
|
||||||
|
return randomDate;
|
||||||
|
}
|
||||||
|
async function storeProfilePicture(buffer: Buffer, userId: string) {
|
||||||
|
let quality = 50;
|
||||||
|
const output = await sharp(buffer)
|
||||||
|
.resize({ width: 300 })
|
||||||
|
.avif({ quality }) // Verwende AVIF
|
||||||
|
//.webp({ quality }) // Verwende Webp
|
||||||
|
.toBuffer();
|
||||||
|
await sharp(output).toFile(`./pictures/profile/${userId}.avif`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function storeCompanyLogo(buffer: Buffer, userId: string) {
|
||||||
|
let quality = 50;
|
||||||
|
const output = await sharp(buffer)
|
||||||
|
.resize({ width: 300 })
|
||||||
|
.avif({ quality }) // Verwende AVIF
|
||||||
|
//.webp({ quality }) // Verwende Webp
|
||||||
|
.toBuffer();
|
||||||
|
await sharp(output).toFile(`./pictures/logo/${userId}.avif`); // Ersetze Dateierweiterung
|
||||||
|
// await fs.outputFile(`./pictures/logo/${userId}`, file.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteFilesOfDir(directoryPath) {
|
||||||
|
// Überprüfen, ob das Verzeichnis existiert
|
||||||
|
if (existsSync(directoryPath)) {
|
||||||
|
// Den Inhalt des Verzeichnisses synchron löschen
|
||||||
|
try {
|
||||||
|
readdirSync(directoryPath).forEach(file => {
|
||||||
|
const filePath = join(directoryPath, file);
|
||||||
|
// Wenn es sich um ein Verzeichnis handelt, rekursiv löschen
|
||||||
|
if (statSync(filePath).isDirectory()) {
|
||||||
|
rimraf.sync(filePath);
|
||||||
|
} else {
|
||||||
|
// Wenn es sich um eine Datei handelt, direkt löschen
|
||||||
|
unlinkSync(filePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('Der Inhalt des Verzeichnisses wurde erfolgreich gelöscht.');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Fehler beim Löschen des Verzeichnisses:', err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Das Verzeichnis existiert nicht.');
|
||||||
|
}
|
||||||
|
}
|
||||||
13
bizmatch-server/src/drizzle/migrate.ts
Normal file
13
bizmatch-server/src/drizzle/migrate.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import 'dotenv/config';
|
||||||
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
|
import pkg from 'pg';
|
||||||
|
const { Pool } = pkg;
|
||||||
|
import * as schema from './schema.js';
|
||||||
|
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
||||||
|
const connectionString = process.env.DATABASE_URL
|
||||||
|
const pool = new Pool({connectionString})
|
||||||
|
const db = drizzle(pool, { schema });
|
||||||
|
// This will run migrations on the database, skipping the ones already applied
|
||||||
|
await migrate(db, { migrationsFolder: './src/drizzle/migrations' });
|
||||||
|
// Don't forget to close the connection, otherwise the script will hang
|
||||||
|
await pool.end();
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS "businesses" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"userId" uuid,
|
||||||
|
"type" integer,
|
||||||
|
"title" varchar(255),
|
||||||
|
"description" text,
|
||||||
|
"city" varchar(255),
|
||||||
|
"state" char(2),
|
||||||
|
"price" double precision,
|
||||||
|
"favoritesForUser" varchar(30)[],
|
||||||
|
"draft" boolean,
|
||||||
|
"listingsCategory" varchar(255),
|
||||||
|
"realEstateIncluded" boolean,
|
||||||
|
"leasedLocation" boolean,
|
||||||
|
"franchiseResale" boolean,
|
||||||
|
"salesRevenue" double precision,
|
||||||
|
"cashFlow" double precision,
|
||||||
|
"supportAndTraining" text,
|
||||||
|
"employees" integer,
|
||||||
|
"established" integer,
|
||||||
|
"internalListingNumber" integer,
|
||||||
|
"reasonForSale" varchar(255),
|
||||||
|
"brokerLicencing" varchar(255),
|
||||||
|
"internals" text,
|
||||||
|
"created" timestamp,
|
||||||
|
"updated" timestamp,
|
||||||
|
"visits" integer,
|
||||||
|
"lastVisit" timestamp
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "commercials" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"userId" uuid,
|
||||||
|
"type" integer,
|
||||||
|
"title" varchar(255),
|
||||||
|
"description" text,
|
||||||
|
"city" varchar(255),
|
||||||
|
"state" char(2),
|
||||||
|
"price" double precision,
|
||||||
|
"favoritesForUser" varchar(30)[],
|
||||||
|
"hideImage" boolean,
|
||||||
|
"draft" boolean,
|
||||||
|
"zipCode" integer,
|
||||||
|
"county" varchar(255),
|
||||||
|
"email" varchar(255),
|
||||||
|
"website" varchar(255),
|
||||||
|
"phoneNumber" varchar(255),
|
||||||
|
"imageOrder" varchar(30)[],
|
||||||
|
"imagePath" varchar(50),
|
||||||
|
"created" timestamp,
|
||||||
|
"updated" timestamp,
|
||||||
|
"visits" integer,
|
||||||
|
"lastVisit" timestamp
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE IF NOT EXISTS "users" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"firstname" varchar(255) NOT NULL,
|
||||||
|
"lastname" varchar(255) NOT NULL,
|
||||||
|
"email" varchar(255) NOT NULL,
|
||||||
|
"phoneNumber" varchar(255),
|
||||||
|
"description" text,
|
||||||
|
"companyName" varchar(255),
|
||||||
|
"companyOverview" text,
|
||||||
|
"companyWebsite" varchar(255),
|
||||||
|
"companyLocation" varchar(255),
|
||||||
|
"offeredServices" text,
|
||||||
|
"areasServed" varchar(100)[],
|
||||||
|
"hasProfile" boolean,
|
||||||
|
"hasCompanyLogo" boolean,
|
||||||
|
"licensedIn" varchar(50)[]
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "businesses" ADD CONSTRAINT "businesses_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "commercials" ADD CONSTRAINT "commercials_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "commercials" ALTER COLUMN "imageOrder" SET DATA TYPE varchar(200)[];
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE "gender" AS ENUM('male', 'female');
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "users" ADD COLUMN "gender" "gender";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "commercials" ADD COLUMN "listingsCategory" varchar(255);
|
||||||
460
bizmatch-server/src/drizzle/migrations/meta/0000_snapshot.json
Normal file
460
bizmatch-server/src/drizzle/migrations/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,460 @@
|
|||||||
|
{
|
||||||
|
"id": "f6d421f9-2394-4a1c-9268-9e46285f0a41",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"tables": {
|
||||||
|
"businesses": {
|
||||||
|
"name": "businesses",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"name": "city",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"name": "state",
|
||||||
|
"type": "char(2)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"name": "price",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"favoritesForUser": {
|
||||||
|
"name": "favoritesForUser",
|
||||||
|
"type": "varchar(30)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"draft": {
|
||||||
|
"name": "draft",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"listingsCategory": {
|
||||||
|
"name": "listingsCategory",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"realEstateIncluded": {
|
||||||
|
"name": "realEstateIncluded",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"leasedLocation": {
|
||||||
|
"name": "leasedLocation",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"franchiseResale": {
|
||||||
|
"name": "franchiseResale",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"salesRevenue": {
|
||||||
|
"name": "salesRevenue",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"cashFlow": {
|
||||||
|
"name": "cashFlow",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"supportAndTraining": {
|
||||||
|
"name": "supportAndTraining",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"employees": {
|
||||||
|
"name": "employees",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"established": {
|
||||||
|
"name": "established",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"internalListingNumber": {
|
||||||
|
"name": "internalListingNumber",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"reasonForSale": {
|
||||||
|
"name": "reasonForSale",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"brokerLicencing": {
|
||||||
|
"name": "brokerLicencing",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"internals": {
|
||||||
|
"name": "internals",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"name": "created",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"updated": {
|
||||||
|
"name": "updated",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"visits": {
|
||||||
|
"name": "visits",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"lastVisit": {
|
||||||
|
"name": "lastVisit",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"businesses_userId_users_id_fk": {
|
||||||
|
"name": "businesses_userId_users_id_fk",
|
||||||
|
"tableFrom": "businesses",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"commercials": {
|
||||||
|
"name": "commercials",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"name": "city",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"name": "state",
|
||||||
|
"type": "char(2)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"name": "price",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"favoritesForUser": {
|
||||||
|
"name": "favoritesForUser",
|
||||||
|
"type": "varchar(30)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"hideImage": {
|
||||||
|
"name": "hideImage",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"draft": {
|
||||||
|
"name": "draft",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"zipCode": {
|
||||||
|
"name": "zipCode",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"county": {
|
||||||
|
"name": "county",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"website": {
|
||||||
|
"name": "website",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"phoneNumber": {
|
||||||
|
"name": "phoneNumber",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"imageOrder": {
|
||||||
|
"name": "imageOrder",
|
||||||
|
"type": "varchar(30)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"imagePath": {
|
||||||
|
"name": "imagePath",
|
||||||
|
"type": "varchar(50)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"name": "created",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"updated": {
|
||||||
|
"name": "updated",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"visits": {
|
||||||
|
"name": "visits",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"lastVisit": {
|
||||||
|
"name": "lastVisit",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"commercials_userId_users_id_fk": {
|
||||||
|
"name": "commercials_userId_users_id_fk",
|
||||||
|
"tableFrom": "commercials",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"firstname": {
|
||||||
|
"name": "firstname",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"lastname": {
|
||||||
|
"name": "lastname",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"phoneNumber": {
|
||||||
|
"name": "phoneNumber",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyName": {
|
||||||
|
"name": "companyName",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyOverview": {
|
||||||
|
"name": "companyOverview",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyWebsite": {
|
||||||
|
"name": "companyWebsite",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyLocation": {
|
||||||
|
"name": "companyLocation",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"offeredServices": {
|
||||||
|
"name": "offeredServices",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"areasServed": {
|
||||||
|
"name": "areasServed",
|
||||||
|
"type": "varchar(100)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"hasProfile": {
|
||||||
|
"name": "hasProfile",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"hasCompanyLogo": {
|
||||||
|
"name": "hasCompanyLogo",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"licensedIn": {
|
||||||
|
"name": "licensedIn",
|
||||||
|
"type": "varchar(50)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
460
bizmatch-server/src/drizzle/migrations/meta/0001_snapshot.json
Normal file
460
bizmatch-server/src/drizzle/migrations/meta/0001_snapshot.json
Normal file
@@ -0,0 +1,460 @@
|
|||||||
|
{
|
||||||
|
"id": "3e4b8c5f-4474-4877-abec-38283408ee34",
|
||||||
|
"prevId": "f6d421f9-2394-4a1c-9268-9e46285f0a41",
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"tables": {
|
||||||
|
"businesses": {
|
||||||
|
"name": "businesses",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"name": "city",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"name": "state",
|
||||||
|
"type": "char(2)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"name": "price",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"favoritesForUser": {
|
||||||
|
"name": "favoritesForUser",
|
||||||
|
"type": "varchar(30)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"draft": {
|
||||||
|
"name": "draft",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"listingsCategory": {
|
||||||
|
"name": "listingsCategory",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"realEstateIncluded": {
|
||||||
|
"name": "realEstateIncluded",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"leasedLocation": {
|
||||||
|
"name": "leasedLocation",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"franchiseResale": {
|
||||||
|
"name": "franchiseResale",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"salesRevenue": {
|
||||||
|
"name": "salesRevenue",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"cashFlow": {
|
||||||
|
"name": "cashFlow",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"supportAndTraining": {
|
||||||
|
"name": "supportAndTraining",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"employees": {
|
||||||
|
"name": "employees",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"established": {
|
||||||
|
"name": "established",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"internalListingNumber": {
|
||||||
|
"name": "internalListingNumber",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"reasonForSale": {
|
||||||
|
"name": "reasonForSale",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"brokerLicencing": {
|
||||||
|
"name": "brokerLicencing",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"internals": {
|
||||||
|
"name": "internals",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"name": "created",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"updated": {
|
||||||
|
"name": "updated",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"visits": {
|
||||||
|
"name": "visits",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"lastVisit": {
|
||||||
|
"name": "lastVisit",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"businesses_userId_users_id_fk": {
|
||||||
|
"name": "businesses_userId_users_id_fk",
|
||||||
|
"tableFrom": "businesses",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"commercials": {
|
||||||
|
"name": "commercials",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"name": "city",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"name": "state",
|
||||||
|
"type": "char(2)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"name": "price",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"favoritesForUser": {
|
||||||
|
"name": "favoritesForUser",
|
||||||
|
"type": "varchar(30)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"hideImage": {
|
||||||
|
"name": "hideImage",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"draft": {
|
||||||
|
"name": "draft",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"zipCode": {
|
||||||
|
"name": "zipCode",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"county": {
|
||||||
|
"name": "county",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"website": {
|
||||||
|
"name": "website",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"phoneNumber": {
|
||||||
|
"name": "phoneNumber",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"imageOrder": {
|
||||||
|
"name": "imageOrder",
|
||||||
|
"type": "varchar(200)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"imagePath": {
|
||||||
|
"name": "imagePath",
|
||||||
|
"type": "varchar(50)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"name": "created",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"updated": {
|
||||||
|
"name": "updated",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"visits": {
|
||||||
|
"name": "visits",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"lastVisit": {
|
||||||
|
"name": "lastVisit",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"commercials_userId_users_id_fk": {
|
||||||
|
"name": "commercials_userId_users_id_fk",
|
||||||
|
"tableFrom": "commercials",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"firstname": {
|
||||||
|
"name": "firstname",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"lastname": {
|
||||||
|
"name": "lastname",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"phoneNumber": {
|
||||||
|
"name": "phoneNumber",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyName": {
|
||||||
|
"name": "companyName",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyOverview": {
|
||||||
|
"name": "companyOverview",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyWebsite": {
|
||||||
|
"name": "companyWebsite",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyLocation": {
|
||||||
|
"name": "companyLocation",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"offeredServices": {
|
||||||
|
"name": "offeredServices",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"areasServed": {
|
||||||
|
"name": "areasServed",
|
||||||
|
"type": "varchar(100)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"hasProfile": {
|
||||||
|
"name": "hasProfile",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"hasCompanyLogo": {
|
||||||
|
"name": "hasCompanyLogo",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"licensedIn": {
|
||||||
|
"name": "licensedIn",
|
||||||
|
"type": "varchar(50)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
474
bizmatch-server/src/drizzle/migrations/meta/0002_snapshot.json
Normal file
474
bizmatch-server/src/drizzle/migrations/meta/0002_snapshot.json
Normal file
@@ -0,0 +1,474 @@
|
|||||||
|
{
|
||||||
|
"id": "ad48c6eb-2d04-442f-9242-b6765553c7c4",
|
||||||
|
"prevId": "3e4b8c5f-4474-4877-abec-38283408ee34",
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"tables": {
|
||||||
|
"businesses": {
|
||||||
|
"name": "businesses",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"name": "city",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"name": "state",
|
||||||
|
"type": "char(2)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"name": "price",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"favoritesForUser": {
|
||||||
|
"name": "favoritesForUser",
|
||||||
|
"type": "varchar(30)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"draft": {
|
||||||
|
"name": "draft",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"listingsCategory": {
|
||||||
|
"name": "listingsCategory",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"realEstateIncluded": {
|
||||||
|
"name": "realEstateIncluded",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"leasedLocation": {
|
||||||
|
"name": "leasedLocation",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"franchiseResale": {
|
||||||
|
"name": "franchiseResale",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"salesRevenue": {
|
||||||
|
"name": "salesRevenue",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"cashFlow": {
|
||||||
|
"name": "cashFlow",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"supportAndTraining": {
|
||||||
|
"name": "supportAndTraining",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"employees": {
|
||||||
|
"name": "employees",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"established": {
|
||||||
|
"name": "established",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"internalListingNumber": {
|
||||||
|
"name": "internalListingNumber",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"reasonForSale": {
|
||||||
|
"name": "reasonForSale",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"brokerLicencing": {
|
||||||
|
"name": "brokerLicencing",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"internals": {
|
||||||
|
"name": "internals",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"name": "created",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"updated": {
|
||||||
|
"name": "updated",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"visits": {
|
||||||
|
"name": "visits",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"lastVisit": {
|
||||||
|
"name": "lastVisit",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"businesses_userId_users_id_fk": {
|
||||||
|
"name": "businesses_userId_users_id_fk",
|
||||||
|
"tableFrom": "businesses",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"commercials": {
|
||||||
|
"name": "commercials",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"name": "city",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"name": "state",
|
||||||
|
"type": "char(2)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"name": "price",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"favoritesForUser": {
|
||||||
|
"name": "favoritesForUser",
|
||||||
|
"type": "varchar(30)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"hideImage": {
|
||||||
|
"name": "hideImage",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"draft": {
|
||||||
|
"name": "draft",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"zipCode": {
|
||||||
|
"name": "zipCode",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"county": {
|
||||||
|
"name": "county",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"website": {
|
||||||
|
"name": "website",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"phoneNumber": {
|
||||||
|
"name": "phoneNumber",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"imageOrder": {
|
||||||
|
"name": "imageOrder",
|
||||||
|
"type": "varchar(200)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"imagePath": {
|
||||||
|
"name": "imagePath",
|
||||||
|
"type": "varchar(50)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"name": "created",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"updated": {
|
||||||
|
"name": "updated",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"visits": {
|
||||||
|
"name": "visits",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"lastVisit": {
|
||||||
|
"name": "lastVisit",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"commercials_userId_users_id_fk": {
|
||||||
|
"name": "commercials_userId_users_id_fk",
|
||||||
|
"tableFrom": "commercials",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"firstname": {
|
||||||
|
"name": "firstname",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"lastname": {
|
||||||
|
"name": "lastname",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"phoneNumber": {
|
||||||
|
"name": "phoneNumber",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyName": {
|
||||||
|
"name": "companyName",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyOverview": {
|
||||||
|
"name": "companyOverview",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyWebsite": {
|
||||||
|
"name": "companyWebsite",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyLocation": {
|
||||||
|
"name": "companyLocation",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"offeredServices": {
|
||||||
|
"name": "offeredServices",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"areasServed": {
|
||||||
|
"name": "areasServed",
|
||||||
|
"type": "varchar(100)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"hasProfile": {
|
||||||
|
"name": "hasProfile",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"hasCompanyLogo": {
|
||||||
|
"name": "hasCompanyLogo",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"licensedIn": {
|
||||||
|
"name": "licensedIn",
|
||||||
|
"type": "varchar(50)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"gender": {
|
||||||
|
"name": "gender",
|
||||||
|
"type": "gender",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {
|
||||||
|
"gender": {
|
||||||
|
"name": "gender",
|
||||||
|
"values": {
|
||||||
|
"male": "male",
|
||||||
|
"female": "female"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemas": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
480
bizmatch-server/src/drizzle/migrations/meta/0003_snapshot.json
Normal file
480
bizmatch-server/src/drizzle/migrations/meta/0003_snapshot.json
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
{
|
||||||
|
"id": "da786c6a-fd5f-4629-bd5e-3ecd42ab1f2c",
|
||||||
|
"prevId": "ad48c6eb-2d04-442f-9242-b6765553c7c4",
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"tables": {
|
||||||
|
"businesses": {
|
||||||
|
"name": "businesses",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"name": "city",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"name": "state",
|
||||||
|
"type": "char(2)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"name": "price",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"favoritesForUser": {
|
||||||
|
"name": "favoritesForUser",
|
||||||
|
"type": "varchar(30)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"draft": {
|
||||||
|
"name": "draft",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"listingsCategory": {
|
||||||
|
"name": "listingsCategory",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"realEstateIncluded": {
|
||||||
|
"name": "realEstateIncluded",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"leasedLocation": {
|
||||||
|
"name": "leasedLocation",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"franchiseResale": {
|
||||||
|
"name": "franchiseResale",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"salesRevenue": {
|
||||||
|
"name": "salesRevenue",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"cashFlow": {
|
||||||
|
"name": "cashFlow",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"supportAndTraining": {
|
||||||
|
"name": "supportAndTraining",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"employees": {
|
||||||
|
"name": "employees",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"established": {
|
||||||
|
"name": "established",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"internalListingNumber": {
|
||||||
|
"name": "internalListingNumber",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"reasonForSale": {
|
||||||
|
"name": "reasonForSale",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"brokerLicencing": {
|
||||||
|
"name": "brokerLicencing",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"internals": {
|
||||||
|
"name": "internals",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"name": "created",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"updated": {
|
||||||
|
"name": "updated",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"visits": {
|
||||||
|
"name": "visits",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"lastVisit": {
|
||||||
|
"name": "lastVisit",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"businesses_userId_users_id_fk": {
|
||||||
|
"name": "businesses_userId_users_id_fk",
|
||||||
|
"tableFrom": "businesses",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"commercials": {
|
||||||
|
"name": "commercials",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"name": "city",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"name": "state",
|
||||||
|
"type": "char(2)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"name": "price",
|
||||||
|
"type": "double precision",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"favoritesForUser": {
|
||||||
|
"name": "favoritesForUser",
|
||||||
|
"type": "varchar(30)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"listingsCategory": {
|
||||||
|
"name": "listingsCategory",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"hideImage": {
|
||||||
|
"name": "hideImage",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"draft": {
|
||||||
|
"name": "draft",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"zipCode": {
|
||||||
|
"name": "zipCode",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"county": {
|
||||||
|
"name": "county",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"website": {
|
||||||
|
"name": "website",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"phoneNumber": {
|
||||||
|
"name": "phoneNumber",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"imageOrder": {
|
||||||
|
"name": "imageOrder",
|
||||||
|
"type": "varchar(200)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"imagePath": {
|
||||||
|
"name": "imagePath",
|
||||||
|
"type": "varchar(50)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"name": "created",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"updated": {
|
||||||
|
"name": "updated",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"visits": {
|
||||||
|
"name": "visits",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"lastVisit": {
|
||||||
|
"name": "lastVisit",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"commercials_userId_users_id_fk": {
|
||||||
|
"name": "commercials_userId_users_id_fk",
|
||||||
|
"tableFrom": "commercials",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"firstname": {
|
||||||
|
"name": "firstname",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"lastname": {
|
||||||
|
"name": "lastname",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"phoneNumber": {
|
||||||
|
"name": "phoneNumber",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyName": {
|
||||||
|
"name": "companyName",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyOverview": {
|
||||||
|
"name": "companyOverview",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyWebsite": {
|
||||||
|
"name": "companyWebsite",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"companyLocation": {
|
||||||
|
"name": "companyLocation",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"offeredServices": {
|
||||||
|
"name": "offeredServices",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"areasServed": {
|
||||||
|
"name": "areasServed",
|
||||||
|
"type": "varchar(100)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"hasProfile": {
|
||||||
|
"name": "hasProfile",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"hasCompanyLogo": {
|
||||||
|
"name": "hasCompanyLogo",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"licensedIn": {
|
||||||
|
"name": "licensedIn",
|
||||||
|
"type": "varchar(50)[]",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"gender": {
|
||||||
|
"name": "gender",
|
||||||
|
"type": "gender",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {
|
||||||
|
"gender": {
|
||||||
|
"name": "gender",
|
||||||
|
"values": {
|
||||||
|
"male": "male",
|
||||||
|
"female": "female"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemas": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
bizmatch-server/src/drizzle/migrations/meta/_journal.json
Normal file
34
bizmatch-server/src/drizzle/migrations/meta/_journal.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1714913766996,
|
||||||
|
"tag": "0000_third_spacker_dave",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1714981666488,
|
||||||
|
"tag": "0001_rapid_daimon_hellstrom",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 2,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1714982539265,
|
||||||
|
"tag": "0002_black_zaladane",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1715254754561,
|
||||||
|
"tag": "0003_tough_hobgoblin",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
78
bizmatch-server/src/drizzle/schema.ts
Normal file
78
bizmatch-server/src/drizzle/schema.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { boolean, char, doublePrecision, integer, pgEnum, pgTable, text, timestamp, uuid, varchar } from 'drizzle-orm/pg-core';
|
||||||
|
|
||||||
|
export const PG_CONNECTION = 'PG_CONNECTION';
|
||||||
|
export const genderEnum = pgEnum('gender', ['male', 'female']);
|
||||||
|
export const users = pgTable('users', {
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
firstname: varchar('firstname', { length: 255 }).notNull(),
|
||||||
|
lastname: varchar('lastname', { length: 255 }).notNull(),
|
||||||
|
email: varchar('email', { length: 255 }).notNull(),
|
||||||
|
phoneNumber: varchar('phoneNumber', { length: 255 }),
|
||||||
|
description: text('description'),
|
||||||
|
companyName: varchar('companyName', { length: 255 }),
|
||||||
|
companyOverview: text('companyOverview'),
|
||||||
|
companyWebsite: varchar('companyWebsite', { length: 255 }),
|
||||||
|
companyLocation: varchar('companyLocation', { length: 255 }),
|
||||||
|
offeredServices: text('offeredServices'),
|
||||||
|
areasServed: varchar('areasServed', { length: 100 }).array(),
|
||||||
|
hasProfile: boolean('hasProfile'),
|
||||||
|
hasCompanyLogo: boolean('hasCompanyLogo'),
|
||||||
|
licensedIn: varchar('licensedIn', { length: 50 }).array(),
|
||||||
|
gender: genderEnum('gender'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const businesses = pgTable('businesses', {
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
userId: uuid('userId').references(() => users.id),
|
||||||
|
type: integer('type'),
|
||||||
|
title: varchar('title', { length: 255 }),
|
||||||
|
description: text('description'),
|
||||||
|
city: varchar('city', { length: 255 }),
|
||||||
|
state: char('state', { length: 2 }),
|
||||||
|
price: doublePrecision('price'),
|
||||||
|
favoritesForUser: varchar('favoritesForUser', { length: 30 }).array(),
|
||||||
|
draft: boolean('draft'),
|
||||||
|
listingsCategory: varchar('listingsCategory', { length: 255 }),
|
||||||
|
realEstateIncluded: boolean('realEstateIncluded'),
|
||||||
|
leasedLocation: boolean('leasedLocation'),
|
||||||
|
franchiseResale: boolean('franchiseResale'),
|
||||||
|
salesRevenue: doublePrecision('salesRevenue'),
|
||||||
|
cashFlow: doublePrecision('cashFlow'),
|
||||||
|
supportAndTraining: text('supportAndTraining'),
|
||||||
|
employees: integer('employees'),
|
||||||
|
established: integer('established'),
|
||||||
|
internalListingNumber: integer('internalListingNumber'),
|
||||||
|
reasonForSale: varchar('reasonForSale', { length: 255 }),
|
||||||
|
brokerLicencing: varchar('brokerLicencing', { length: 255 }),
|
||||||
|
internals: text('internals'),
|
||||||
|
created: timestamp('created'),
|
||||||
|
updated: timestamp('updated'),
|
||||||
|
visits: integer('visits'),
|
||||||
|
lastVisit: timestamp('lastVisit'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const commercials = pgTable('commercials', {
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
userId: uuid('userId').references(() => users.id),
|
||||||
|
type: integer('type'),
|
||||||
|
title: varchar('title', { length: 255 }),
|
||||||
|
description: text('description'),
|
||||||
|
city: varchar('city', { length: 255 }),
|
||||||
|
state: char('state', { length: 2 }),
|
||||||
|
price: doublePrecision('price'),
|
||||||
|
favoritesForUser: varchar('favoritesForUser', { length: 30 }).array(),
|
||||||
|
listingsCategory: varchar('listingsCategory', { length: 255 }),
|
||||||
|
hideImage: boolean('hideImage'),
|
||||||
|
draft: boolean('draft'),
|
||||||
|
zipCode: integer('zipCode'),
|
||||||
|
county: varchar('county', { length: 255 }),
|
||||||
|
email: varchar('email', { length: 255 }),
|
||||||
|
website: varchar('website', { length: 255 }),
|
||||||
|
phoneNumber: varchar('phoneNumber', { length: 255 }),
|
||||||
|
imageOrder: varchar('imageOrder', { length: 200 }).array(),
|
||||||
|
imagePath: varchar('imagePath', { length: 50 }),
|
||||||
|
created: timestamp('created'),
|
||||||
|
updated: timestamp('updated'),
|
||||||
|
visits: integer('visits'),
|
||||||
|
lastVisit: timestamp('lastVisit'),
|
||||||
|
});
|
||||||
@@ -22,7 +22,7 @@ export class FileService {
|
|||||||
fs.ensureDirSync(`./pictures/property`);
|
fs.ensureDirSync(`./pictures/property`);
|
||||||
}
|
}
|
||||||
private loadSubscriptions(): void {
|
private loadSubscriptions(): void {
|
||||||
const filePath = join(__dirname, '..', 'assets', 'subscriptions.json');
|
const filePath = join(__dirname, '../..', 'assets', 'subscriptions.json');
|
||||||
const rawData = readFileSync(filePath, 'utf8');
|
const rawData = readFileSync(filePath, 'utf8');
|
||||||
this.subscriptions = JSON.parse(rawData);
|
this.subscriptions = JSON.parse(rawData);
|
||||||
}
|
}
|
||||||
@@ -53,17 +53,16 @@ export class FileService {
|
|||||||
// await fs.outputFile(`./pictures/logo/${userId}`, file.buffer);
|
// await fs.outputFile(`./pictures/logo/${userId}`, file.buffer);
|
||||||
}
|
}
|
||||||
hasCompanyLogo(userId: string){
|
hasCompanyLogo(userId: string){
|
||||||
return fs.existsSync(`./pictures/logo/${userId}.avif`)
|
return fs.existsSync(`./pictures/logo/${userId}.avif`)?true:false
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPropertyImages(listingId: string): Promise<ImageProperty[]> {
|
async getPropertyImages(listingId: string): Promise<string[]> {
|
||||||
const result: ImageProperty[] = []
|
const result: string[] = []
|
||||||
const directory = `./pictures/property/${listingId}`
|
const directory = `./pictures/property/${listingId}`
|
||||||
if (fs.existsSync(directory)) {
|
if (fs.existsSync(directory)) {
|
||||||
const files = await fs.readdir(directory);
|
const files = await fs.readdir(directory);
|
||||||
files.forEach(f => {
|
files.forEach(f => {
|
||||||
const image: ImageProperty = { name: f, id: '', code: '' };
|
result.push(f)
|
||||||
result.push(image)
|
|
||||||
})
|
})
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export class GeoService {
|
|||||||
this.loadGeo();
|
this.loadGeo();
|
||||||
}
|
}
|
||||||
private loadGeo(): void {
|
private loadGeo(): void {
|
||||||
const filePath = join(__dirname,'..', 'assets', 'geo.json');
|
const filePath = join(__dirname,'../..', 'assets', 'geo.json');
|
||||||
const rawData = readFileSync(filePath, 'utf8');
|
const rawData = readFileSync(filePath, 'utf8');
|
||||||
this.geo = JSON.parse(rawData);
|
this.geo = JSON.parse(rawData);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +1,74 @@
|
|||||||
import { Body, Controller, Delete, Get, Inject, Param, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
|
import { Controller, Delete, Get, Inject, Param, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
|
||||||
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
|
||||||
import { FileService } from '../file/file.service.js';
|
import { FileService } from '../file/file.service.js';
|
||||||
import { SelectOptionsService } from '../select-options/select-options.service.js';
|
|
||||||
import { ListingsService } from '../listings/listings.service.js';
|
import { ListingsService } from '../listings/listings.service.js';
|
||||||
import { CommercialPropertyListing } from 'src/models/main.model.js';
|
import { SelectOptionsService } from '../select-options/select-options.service.js';
|
||||||
import { Entity, EntityData } from 'redis-om';
|
|
||||||
|
import { commercials } from 'src/drizzle/schema.js';
|
||||||
|
import { CommercialPropertyListing } from 'src/models/db.model.js';
|
||||||
|
|
||||||
@Controller('image')
|
@Controller('image')
|
||||||
export class ImageController {
|
export class ImageController {
|
||||||
|
constructor(
|
||||||
constructor(private fileService:FileService,
|
private fileService: FileService,
|
||||||
private listingService:ListingsService,
|
private listingService: ListingsService,
|
||||||
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||||
private selectOptions:SelectOptionsService) {
|
private selectOptions: SelectOptionsService,
|
||||||
}
|
) {}
|
||||||
|
|
||||||
@Post('uploadPropertyPicture/:id')
|
|
||||||
@UseInterceptors(FileInterceptor('file'),)
|
|
||||||
async uploadPropertyPicture(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
|
|
||||||
const imagename = await this.fileService.storePropertyPicture(file,id);
|
|
||||||
await this.listingService.addImage(id,imagename);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('uploadProfile/:id')
|
@Post('uploadPropertyPicture/:id')
|
||||||
@UseInterceptors(FileInterceptor('file'),)
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
async uploadProfile(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
|
async uploadPropertyPicture(@UploadedFile() file: Express.Multer.File, @Param('id') id: string) {
|
||||||
await this.fileService.storeProfilePicture(file,id);
|
const imagename = await this.fileService.storePropertyPicture(file, id);
|
||||||
}
|
await this.listingService.addImage(id, imagename);
|
||||||
|
}
|
||||||
@Post('uploadCompanyLogo/:id')
|
|
||||||
@UseInterceptors(FileInterceptor('file'),)
|
@Post('uploadProfile/:id')
|
||||||
async uploadCompanyLogo(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
await this.fileService.storeCompanyLogo(file,id);
|
async uploadProfile(@UploadedFile() file: Express.Multer.File, @Param('id') id: string) {
|
||||||
}
|
await this.fileService.storeProfilePicture(file, id);
|
||||||
|
}
|
||||||
@Get(':id')
|
|
||||||
async getPropertyImagesById(@Param('id') id:string): Promise<any> {
|
@Post('uploadCompanyLogo/:id')
|
||||||
const result = await this.listingService.getCommercialPropertyListingById(id);
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
const listing = result as CommercialPropertyListing;
|
async uploadCompanyLogo(@UploadedFile() file: Express.Multer.File, @Param('id') id: string) {
|
||||||
if (listing.imageOrder){
|
await this.fileService.storeCompanyLogo(file, id);
|
||||||
return listing.imageOrder
|
}
|
||||||
} else {
|
|
||||||
const imageOrder = await this.fileService.getPropertyImages(id);
|
@Get(':id')
|
||||||
listing.imageOrder=imageOrder;
|
async getPropertyImagesById(@Param('id') id: string): Promise<any> {
|
||||||
this.listingService.saveListing(listing);
|
const result = await this.listingService.findById(id, commercials);
|
||||||
return imageOrder;
|
const listing = result as CommercialPropertyListing;
|
||||||
}
|
if (listing.imageOrder) {
|
||||||
}
|
return listing.imageOrder;
|
||||||
@Get('profileImages/:userids')
|
} else {
|
||||||
async getProfileImagesForUsers(@Param('userids') userids:string): Promise<any> {
|
const imageOrder = await this.fileService.getPropertyImages(id);
|
||||||
return await this.fileService.getProfileImagesForUsers(userids);
|
listing.imageOrder = imageOrder;
|
||||||
}
|
this.listingService.updateListing(listing.id, listing, commercials);
|
||||||
@Get('companyLogos/:userids')
|
return imageOrder;
|
||||||
async getCompanyLogosForUsers(@Param('userids') userids:string): Promise<any> {
|
|
||||||
return await this.fileService.getCompanyLogosForUsers(userids);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('propertyPicture/:listingid/:imagename')
|
|
||||||
async deletePropertyImagesById(@Param('listingid') listingid:string,@Param('imagename') imagename:string): Promise<any> {
|
|
||||||
this.fileService.deleteImage(`pictures/property/${listingid}/${imagename}`);
|
|
||||||
await this.listingService.deleteImage(listingid,imagename);
|
|
||||||
}
|
|
||||||
@Delete('logo/:userid/')
|
|
||||||
async deleteLogoImagesById(@Param('id') id:string): Promise<any> {
|
|
||||||
this.fileService.deleteImage(`pictures/property//${id}`)
|
|
||||||
}
|
|
||||||
@Delete('profile/:userid/')
|
|
||||||
async deleteProfileImagesById(@Param('id') id:string): Promise<any> {
|
|
||||||
this.fileService.deleteImage(`pictures/property//${id}`)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@Get('profileImages/:userids')
|
||||||
|
async getProfileImagesForUsers(@Param('userids') userids: string): Promise<any> {
|
||||||
|
return await this.fileService.getProfileImagesForUsers(userids);
|
||||||
|
}
|
||||||
|
@Get('companyLogos/:userids')
|
||||||
|
async getCompanyLogosForUsers(@Param('userids') userids: string): Promise<any> {
|
||||||
|
return await this.fileService.getCompanyLogosForUsers(userids);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('propertyPicture/:listingid/:imagename')
|
||||||
|
async deletePropertyImagesById(@Param('listingid') listingid: string, @Param('imagename') imagename: string): Promise<any> {
|
||||||
|
this.fileService.deleteImage(`pictures/property/${listingid}/${imagename}`);
|
||||||
|
}
|
||||||
|
@Delete('logo/:userid/')
|
||||||
|
async deleteLogoImagesById(@Param('id') id: string): Promise<any> {
|
||||||
|
this.fileService.deleteImage(`pictures/property//${id}`);
|
||||||
|
}
|
||||||
|
@Delete('profile/:userid/')
|
||||||
|
async deleteProfileImagesById(@Param('id') id: string): Promise<any> {
|
||||||
|
this.fileService.deleteImage(`pictures/property//${id}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,47 @@
|
|||||||
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 { convertStringToNullUndefined } from '../utils.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';
|
||||||
|
import { businesses } from '../drizzle/schema.js';
|
||||||
|
import { ListingCriteria } from '../models/main.model.js';
|
||||||
|
import { ListingsService } from './listings.service.js';
|
||||||
|
|
||||||
@Controller('listings/business')
|
@Controller('listings/business')
|
||||||
export class BusinessListingsController {
|
export class BusinessListingsController {
|
||||||
|
constructor(
|
||||||
constructor(private readonly listingsService:ListingsService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
|
private readonly listingsService: ListingsService,
|
||||||
}
|
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
@Get()
|
|
||||||
findAll(): any {
|
|
||||||
return this.listingsService.getAllBusinessListings();
|
|
||||||
}
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
findById(@Param('id') id:string): any {
|
findById(@Param('id') id: string): any {
|
||||||
return this.listingsService.getBusinessListingById(id);
|
return this.listingsService.findById(id, businesses);
|
||||||
}
|
}
|
||||||
@Get('user/:userid')
|
@Get('user/:userid')
|
||||||
findByUserId(@Param('userid') userid:string): any {
|
findByUserId(@Param('userid') userid: string): any {
|
||||||
return this.listingsService.getBusinessListingByUserId(userid);
|
return this.listingsService.findByUserId(userid, businesses);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('search')
|
@Post('search')
|
||||||
find(@Body() criteria: any): any {
|
find(@Body() criteria: ListingCriteria): any {
|
||||||
return this.listingsService.findBusinessListings(criteria);
|
return this.listingsService.findListingsByCriteria(criteria, businesses);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param listing creates a new listing
|
|
||||||
*/
|
|
||||||
@Post()
|
@Post()
|
||||||
save(@Body() listing: any){
|
create(@Body() listing: any) {
|
||||||
this.logger.info(`Save Listing`);
|
this.logger.info(`Save Listing`);
|
||||||
this.listingsService.saveListing(listing)
|
return this.listingsService.createListing(listing, businesses);
|
||||||
|
}
|
||||||
|
@Put()
|
||||||
|
update(@Body() listing: any) {
|
||||||
|
this.logger.info(`Save Listing`);
|
||||||
|
return this.listingsService.updateListing(listing.id, listing, businesses);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param id deletes a listing
|
|
||||||
*/
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
deleteById(@Param('id') id:string){
|
deleteById(@Param('id') id: string) {
|
||||||
this.listingsService.deleteBusinessListing(id)
|
this.listingsService.deleteListing(id, businesses);
|
||||||
}
|
}
|
||||||
|
@Get('states/all')
|
||||||
@Delete('deleteAll')
|
getStates(): any {
|
||||||
deleteAll(){
|
return this.listingsService.getStates(businesses);
|
||||||
this.listingsService.deleteAllBusinessListings()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +1,52 @@
|
|||||||
import { Body, Controller, Delete, Get, Inject, Param, Post, Put, UploadedFile, UseInterceptors } from '@nestjs/common';
|
import { Body, Controller, Delete, Get, Inject, Param, Post, Put } from '@nestjs/common';
|
||||||
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';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { commercials } from '../drizzle/schema.js';
|
||||||
import { FileService } from '../file/file.service.js';
|
import { FileService } from '../file/file.service.js';
|
||||||
import { CommercialPropertyListing, ImageProperty } from 'src/models/main.model.js';
|
import { ListingCriteria } from '../models/main.model.js';
|
||||||
|
import { ListingsService } from './listings.service.js';
|
||||||
|
|
||||||
@Controller('listings/commercialProperty')
|
@Controller('listings/commercialProperty')
|
||||||
export class CommercialPropertyListingsController {
|
export class CommercialPropertyListingsController {
|
||||||
|
constructor(
|
||||||
constructor(private readonly listingsService:ListingsService,private fileService:FileService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
|
private readonly listingsService: ListingsService,
|
||||||
}
|
private fileService: FileService,
|
||||||
|
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||||
|
) {}
|
||||||
@Get(':id')
|
|
||||||
findById(@Param('id') id:string): any {
|
|
||||||
return this.listingsService.getCommercialPropertyListingById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('search')
|
@Get(':id')
|
||||||
find(@Body() criteria: any): any {
|
findById(@Param('id') id: string): any {
|
||||||
return this.listingsService.findCommercialPropertyListings(criteria);
|
return this.listingsService.findById(id, commercials);
|
||||||
}
|
}
|
||||||
|
@Get('user/:userid')
|
||||||
@Put('imageOrder/:id')
|
findByUserId(@Param('userid') userid: string): any {
|
||||||
async changeImageOrder(@Param('id') id:string,@Body() imageOrder: ImageProperty[]) {
|
return this.listingsService.findByUserId(userid, commercials);
|
||||||
this.listingsService.updateImageOrder(id, imageOrder)
|
}
|
||||||
}
|
@Post('search')
|
||||||
/**
|
async find(@Body() criteria: ListingCriteria): Promise<any> {
|
||||||
* @param listing creates a new listing
|
return await this.listingsService.findListingsByCriteria(criteria, commercials);
|
||||||
*/
|
}
|
||||||
@Post()
|
@Get('states/all')
|
||||||
save(@Body() listing: any){
|
getStates(): any {
|
||||||
this.logger.info(`Save Listing`);
|
return this.listingsService.getStates(commercials);
|
||||||
this.listingsService.saveListing(listing)
|
}
|
||||||
}
|
@Post()
|
||||||
|
async create(@Body() listing: any) {
|
||||||
/**
|
this.logger.info(`Save Listing`);
|
||||||
* @param id deletes a listing
|
return await this.listingsService.createListing(listing, commercials);
|
||||||
*/
|
}
|
||||||
@Delete(':id')
|
@Put()
|
||||||
deleteById(@Param('id') id:string){
|
async update(@Body() listing: any) {
|
||||||
this.listingsService.deleteCommercialPropertyListing(id)
|
this.logger.info(`Save Listing`);
|
||||||
}
|
return await this.listingsService.updateListing(listing.id, listing, commercials);
|
||||||
@Delete('deleteAll')
|
}
|
||||||
deleteAll(){
|
@Delete(':id')
|
||||||
this.listingsService.deleteAllcommercialListings()
|
deleteById(@Param('id') id: string) {
|
||||||
}
|
this.listingsService.deleteListing(id, commercials);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('imageOrder/:id')
|
||||||
|
async changeImageOrder(@Param('id') id: string, @Body() imageOrder: string[]) {
|
||||||
|
this.listingsService.updateImageOrder(id, imageOrder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { BusinessListingsController } from './business-listings.controller.js';
|
import { DrizzleModule } from '../drizzle/drizzle.module.js';
|
||||||
import { ListingsService } from './listings.service.js';
|
|
||||||
import { CommercialPropertyListingsController } from './commercial-property-listings.controller.js';
|
|
||||||
import { RedisModule } from '../redis/redis.module.js';
|
|
||||||
import { FileService } from '../file/file.service.js';
|
import { FileService } from '../file/file.service.js';
|
||||||
import { UnknownListingsController } from './unknown-listings.controller.js';
|
|
||||||
import { UserModule } from '../user/user.module.js';
|
|
||||||
import { BrokerListingsController } from './broker-listings.controller.js';
|
|
||||||
import { UserService } from '../user/user.service.js';
|
import { UserService } from '../user/user.service.js';
|
||||||
|
import { BrokerListingsController } from './broker-listings.controller.js';
|
||||||
|
import { BusinessListingsController } from './business-listings.controller.js';
|
||||||
|
import { CommercialPropertyListingsController } from './commercial-property-listings.controller.js';
|
||||||
|
import { ListingsService } from './listings.service.js';
|
||||||
|
import { UnknownListingsController } from './unknown-listings.controller.js';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [RedisModule],
|
imports: [DrizzleModule],
|
||||||
controllers: [BusinessListingsController, CommercialPropertyListingsController,UnknownListingsController,BrokerListingsController],
|
controllers: [BusinessListingsController, CommercialPropertyListingsController, UnknownListingsController, BrokerListingsController],
|
||||||
providers: [ListingsService,FileService,UserService],
|
providers: [ListingsService, FileService, UserService],
|
||||||
exports: [ListingsService],
|
exports: [ListingsService],
|
||||||
})
|
})
|
||||||
export class ListingsModule {}
|
export class ListingsModule {}
|
||||||
|
|||||||
@@ -1,186 +1,125 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import {
|
import { and, eq, gte, ilike, lte, sql } from 'drizzle-orm';
|
||||||
BusinessListing,
|
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||||
CommercialPropertyListing,
|
|
||||||
ListingCriteria,
|
|
||||||
ListingType,
|
|
||||||
ImageProperty,
|
|
||||||
ListingCategory
|
|
||||||
} from '../models/main.model.js';
|
|
||||||
import { convertStringToNullUndefined } from '../utils.js';
|
|
||||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
|
import { BusinessListing, CommercialPropertyListing } from 'src/models/db.model.js';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import { EntityData, EntityId, Repository, Schema, SchemaDefinition } from 'redis-om';
|
import * as schema from '../drizzle/schema.js';
|
||||||
import { REDIS_CLIENT } from '../redis/redis.module.js';
|
import { PG_CONNECTION, businesses, commercials } from '../drizzle/schema.js';
|
||||||
|
import { ListingCriteria } from '../models/main.model.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ListingsService {
|
export class ListingsService {
|
||||||
schemaNameBusiness:ListingCategory={name:'business'}
|
constructor(
|
||||||
schemaNameCommercial:ListingCategory={name:'commercialProperty'}
|
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||||
businessListingRepository:Repository;
|
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,
|
||||||
commercialPropertyListingRepository:Repository;
|
) {}
|
||||||
baseListingSchemaDef : SchemaDefinition = {
|
private getConditions(criteria: ListingCriteria, table: typeof businesses | typeof commercials): any[] {
|
||||||
id: { type: 'string' },
|
const conditions = [];
|
||||||
userId: { type: 'string' },
|
if (criteria.type) {
|
||||||
listingsCategory: { type: 'string' },
|
conditions.push(eq(table.type, criteria.type));
|
||||||
title: { type: 'string' },
|
|
||||||
description: { type: 'string' },
|
|
||||||
country: { type: 'string' },
|
|
||||||
state:{ type: 'string' },
|
|
||||||
city:{ type: 'string' },
|
|
||||||
zipCode: { type: 'number' },
|
|
||||||
type: { type: 'string' },
|
|
||||||
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(this.schemaNameBusiness.name,this.businessListingSchemaDef, {
|
|
||||||
dataStructure: 'JSON'
|
|
||||||
})
|
|
||||||
commercialPropertyListingSchema = new Schema(this.schemaNameCommercial.name,this.commercialPropertyListingSchemaDef, {
|
|
||||||
dataStructure: 'JSON'
|
|
||||||
})
|
|
||||||
constructor(@Inject(REDIS_CLIENT) private readonly redis: any, @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger){
|
|
||||||
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;
|
if (criteria.state) {
|
||||||
|
conditions.push(eq(table.state, criteria.state));
|
||||||
|
}
|
||||||
|
if (criteria.minPrice) {
|
||||||
|
conditions.push(gte(table.price, criteria.minPrice));
|
||||||
|
}
|
||||||
|
if (criteria.maxPrice) {
|
||||||
|
conditions.push(lte(table.price, criteria.maxPrice));
|
||||||
|
}
|
||||||
|
if (criteria.realEstateChecked) {
|
||||||
|
conditions.push(eq(businesses.realEstateIncluded, true));
|
||||||
|
}
|
||||||
|
if (criteria.title) {
|
||||||
|
conditions.push(ilike(table.title, `%${criteria.title}%`));
|
||||||
|
}
|
||||||
|
return conditions;
|
||||||
}
|
}
|
||||||
async getCommercialPropertyListingById(id: string): Promise<CommercialPropertyListing>{
|
// ##############################################################
|
||||||
return await this.commercialPropertyListingRepository.fetch(id) as unknown as CommercialPropertyListing;
|
// Listings general
|
||||||
|
// ##############################################################
|
||||||
|
async findListingsByCriteria(criteria: ListingCriteria, table: typeof businesses | typeof commercials): Promise<{ data: Record<string, any>[]; total: number }> {
|
||||||
|
const start = criteria.start ? criteria.start : 0;
|
||||||
|
const length = criteria.length ? criteria.length : 12;
|
||||||
|
return await this.findListings(table, criteria, start, length);
|
||||||
}
|
}
|
||||||
async getBusinessListingById(id: string) {
|
private async findListings(table: typeof businesses | typeof commercials, criteria: ListingCriteria, start = 0, length = 12): Promise<any> {
|
||||||
return await this.businessListingRepository.fetch(id)
|
const conditions = this.getConditions(criteria, table);
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
this.conn
|
||||||
|
.select()
|
||||||
|
.from(table)
|
||||||
|
.where(and(...conditions))
|
||||||
|
.offset(start)
|
||||||
|
.limit(length),
|
||||||
|
this.conn
|
||||||
|
.select({ count: sql`count(*)` })
|
||||||
|
.from(table)
|
||||||
|
.where(and(...conditions))
|
||||||
|
.then(result => Number(result[0].count)),
|
||||||
|
]);
|
||||||
|
return { total, data };
|
||||||
}
|
}
|
||||||
async getBusinessListingByUserId(userid:string){
|
async findById(id: string, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> {
|
||||||
return await this.businessListingRepository.search().where('userId').equals(userid).return.all()
|
const result = await this.conn
|
||||||
}
|
.select()
|
||||||
async deleteBusinessListing(id: string){
|
.from(table)
|
||||||
return await this.businessListingRepository.remove(id);
|
.where(sql`${table.id} = ${id}`);
|
||||||
}
|
return result[0] as BusinessListing | CommercialPropertyListing;
|
||||||
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 findBusinessListings(criteria:ListingCriteria): Promise<any> {
|
|
||||||
// let listings = await this.getAllBusinessListings();
|
|
||||||
// return this.find(criteria,listings);
|
|
||||||
this.logger.info(`start findBusinessListings: ${JSON.stringify(criteria)}`);
|
|
||||||
const result = await this.redis.ft.search('business:index','*',{LIMIT:{from:0,size:50}});
|
|
||||||
this.logger.info(`start findBusinessListings: ${JSON.stringify(criteria)}`);
|
|
||||||
return result.documents;
|
|
||||||
}
|
|
||||||
async findCommercialPropertyListings(criteria:ListingCriteria): Promise<any> {
|
|
||||||
let listings = await this.getAllCommercialListings();
|
|
||||||
return this.find(criteria,listings);
|
|
||||||
}
|
|
||||||
async deleteAllBusinessListings(){
|
|
||||||
const ids = await this.getIdsForRepo(this.schemaNameBusiness.name);
|
|
||||||
this.businessListingRepository.remove(ids);
|
|
||||||
}
|
|
||||||
async deleteAllcommercialListings(){
|
|
||||||
const ids = await this.getIdsForRepo(this.schemaNameCommercial.name);
|
|
||||||
this.commercialPropertyListingRepository.remove(ids);
|
|
||||||
}
|
|
||||||
async getIdsForRepo(repoName:string, maxcount=100000){
|
|
||||||
let cursor = 0;
|
|
||||||
let ids = [];
|
|
||||||
do {
|
|
||||||
const reply = await this.redis.scan(cursor, {
|
|
||||||
MATCH: `${repoName}:*`,
|
|
||||||
COUNT: maxcount
|
|
||||||
});
|
|
||||||
cursor = reply.cursor;
|
|
||||||
// Extrahiere die ID aus jedem Schlüssel und füge sie zur Liste hinzu
|
|
||||||
ids = ids.concat(reply.keys.map(key => key.split(':')[1]).filter(id=>id!='index'));
|
|
||||||
} while (cursor !== 0);
|
|
||||||
return ids;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async find(criteria:ListingCriteria, listings: any[]): Promise<any> {
|
async findByUserId(userId: string, table: typeof businesses | typeof commercials): Promise<BusinessListing[] | CommercialPropertyListing[]> {
|
||||||
listings=listings.filter(l=>l.listingsCategory===criteria.listingsCategory);
|
return (await this.conn.select().from(table).where(eq(table.userId, userId))) as BusinessListing[] | CommercialPropertyListing[];
|
||||||
if (convertStringToNullUndefined(criteria.type)){
|
|
||||||
console.log(criteria.type);
|
|
||||||
listings=listings.filter(l=>l.type===criteria.type);
|
|
||||||
}
|
|
||||||
if (convertStringToNullUndefined(criteria.state)){
|
|
||||||
console.log(criteria.state);
|
|
||||||
listings=listings.filter(l=>l.state===criteria.state);
|
|
||||||
}
|
|
||||||
if (convertStringToNullUndefined(criteria.minPrice)){
|
|
||||||
console.log(criteria.minPrice);
|
|
||||||
listings=listings.filter(l=>l.price>=Number(criteria.minPrice));
|
|
||||||
}
|
|
||||||
if (convertStringToNullUndefined(criteria.maxPrice)){
|
|
||||||
console.log(criteria.maxPrice);
|
|
||||||
listings=listings.filter(l=>l.price<=Number(criteria.maxPrice));
|
|
||||||
}
|
|
||||||
if (convertStringToNullUndefined(criteria.realEstateChecked)){
|
|
||||||
console.log(criteria.realEstateChecked);
|
|
||||||
listings=listings.filter(l=>l.realEstateIncluded);
|
|
||||||
}
|
|
||||||
if (convertStringToNullUndefined(criteria.category)){
|
|
||||||
console.log(criteria.category);
|
|
||||||
listings=listings.filter(l=>l.category===criteria.category);
|
|
||||||
}
|
|
||||||
return listings
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateImageOrder(id:string,imageOrder: ImageProperty[]){
|
async createListing(data: BusinessListing | CommercialPropertyListing, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> {
|
||||||
const listing = await this.getCommercialPropertyListingById(id) as unknown as CommercialPropertyListing
|
data.created = new Date();
|
||||||
listing.imageOrder=imageOrder;
|
data.updated = new Date();
|
||||||
this.saveListing(listing);
|
data.visits = 0;
|
||||||
|
data.lastVisit = null;
|
||||||
|
const [createdListing] = await this.conn.insert(table).values(data).returning();
|
||||||
|
return createdListing as BusinessListing | CommercialPropertyListing;
|
||||||
}
|
}
|
||||||
async deleteImage(listingid:string,name:string,){
|
|
||||||
const listing = await this.getCommercialPropertyListingById(listingid) as unknown as CommercialPropertyListing
|
async updateListing(id: string, data: BusinessListing | CommercialPropertyListing, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> {
|
||||||
const index = listing.imageOrder.findIndex(im=>im.name===name);
|
data.updated = new Date();
|
||||||
if (index>-1){
|
data.created = new Date(data.created);
|
||||||
listing.imageOrder.splice(index,1);
|
const [updateListing] = await this.conn.update(table).set(data).where(eq(table.id, id)).returning();
|
||||||
this.saveListing(listing);
|
return updateListing as BusinessListing | CommercialPropertyListing;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteListing(id: string, table: typeof businesses | typeof commercials): Promise<void> {
|
||||||
|
await this.conn.delete(table).where(eq(table.id, id));
|
||||||
|
}
|
||||||
|
async getStates(table: typeof businesses | typeof commercials): Promise<any[]> {
|
||||||
|
return await this.conn
|
||||||
|
.select({ state: table.state, count: sql<number>`count(${table.id})`.mapWith(Number) })
|
||||||
|
.from(table)
|
||||||
|
.groupBy(sql`${table.state}`)
|
||||||
|
.orderBy(sql`count desc`);
|
||||||
|
}
|
||||||
|
// ##############################################################
|
||||||
|
// Images for commercial Properties
|
||||||
|
// ##############################################################
|
||||||
|
|
||||||
|
async updateImageOrder(id: string, imageOrder: string[]) {
|
||||||
|
const listing = (await this.findById(id, commercials)) as unknown as CommercialPropertyListing;
|
||||||
|
listing.imageOrder = imageOrder;
|
||||||
|
await this.updateListing(listing.id, listing, commercials);
|
||||||
|
}
|
||||||
|
async deleteImage(id: string, name: string) {
|
||||||
|
const listing = (await this.findById(id, commercials)) as unknown as CommercialPropertyListing;
|
||||||
|
const index = listing.imageOrder.findIndex(im => im === name);
|
||||||
|
if (index > -1) {
|
||||||
|
listing.imageOrder.splice(index, 1);
|
||||||
|
await this.updateListing(listing.id, listing, commercials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async addImage(id:string,imagename: string){
|
async addImage(id: string, imagename: string) {
|
||||||
const listing = await this.getCommercialPropertyListingById(id) as unknown as CommercialPropertyListing
|
const listing = (await this.findById(id, commercials)) as unknown as CommercialPropertyListing;
|
||||||
listing.imageOrder.push({name:imagename,code:'',id:''});
|
listing.imageOrder.push(imagename);
|
||||||
this.saveListing(listing);
|
listing.imagePath = listing.id;
|
||||||
|
await this.updateListing(listing.id, listing, commercials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { convertStringToNullUndefined } from '../utils.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';
|
||||||
|
import { businesses, commercials } from 'src/drizzle/schema.js';
|
||||||
|
|
||||||
|
|
||||||
@Controller('listings/undefined')
|
@Controller('listings/undefined')
|
||||||
export class UnknownListingsController {
|
export class UnknownListingsController {
|
||||||
@@ -11,19 +13,15 @@ export class UnknownListingsController {
|
|||||||
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(':id')
|
@Get(':id')
|
||||||
async findById(@Param('id') id:string): Promise<any> {
|
async findById(@Param('id') id:string): Promise<any> {
|
||||||
const result = await this.listingsService.getBusinessListingById(id);
|
const result = await this.listingsService.findById(id,businesses);
|
||||||
if (result.id){
|
if (result){
|
||||||
return result
|
return result
|
||||||
} else {
|
} else {
|
||||||
return await this.listingsService.getCommercialPropertyListingById(id);
|
return await this.listingsService.findById(id,commercials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Get('repo/:repo')
|
|
||||||
async getAllByRepo(@Param('repo') repo:string): Promise<any> {
|
|
||||||
return await this.listingsService.getIdsForRepo(repo);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
|
import { Body, Controller, Post } from '@nestjs/common';
|
||||||
|
import { User } from 'src/models/db.model.js';
|
||||||
|
import { MailInfo } from 'src/models/main.model.js';
|
||||||
import { MailService } from './mail.service.js';
|
import { MailService } from './mail.service.js';
|
||||||
import { KeycloakUser, MailInfo } from 'src/models/main.model.js';
|
|
||||||
|
|
||||||
|
|
||||||
@Controller('mail')
|
@Controller('mail')
|
||||||
export class MailController {
|
export class MailController {
|
||||||
constructor(private mailService:MailService){
|
constructor(private mailService: MailService) {}
|
||||||
|
@Post()
|
||||||
}
|
sendEMail(@Body() mailInfo: MailInfo): Promise<User> {
|
||||||
@Post()
|
return this.mailService.sendInquiry(mailInfo);
|
||||||
sendEMail(@Body() mailInfo: MailInfo): Promise< KeycloakUser> {
|
}
|
||||||
return this.mailService.sendInquiry(mailInfo);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { MailService } from './mail.service.js';
|
|
||||||
import { MailController } from './mail.controller.js';
|
|
||||||
import { MailerModule } from '@nestjs-modules/mailer';
|
import { MailerModule } from '@nestjs-modules/mailer';
|
||||||
import path, { join } from 'path';
|
|
||||||
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter.js';
|
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter.js';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import path, { join } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { AuthModule } from '../auth/auth.module.js';
|
import { DrizzleModule } from '../drizzle/drizzle.module.js';
|
||||||
|
import { FileService } from '../file/file.service.js';
|
||||||
|
import { UserModule } from '../user/user.module.js';
|
||||||
|
import { UserService } from '../user/user.service.js';
|
||||||
|
import { MailController } from './mail.controller.js';
|
||||||
|
import { MailService } from './mail.service.js';
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
const user = process.env.amazon_user;
|
||||||
|
const password = process.env.amazon_password;
|
||||||
@Module({
|
@Module({
|
||||||
imports: [AuthModule,
|
imports: [
|
||||||
|
DrizzleModule,
|
||||||
|
UserModule,
|
||||||
MailerModule.forRoot({
|
MailerModule.forRoot({
|
||||||
// transport: 'smtps://user@example.com:topsecret@smtp.example.com',
|
|
||||||
// or
|
|
||||||
transport: {
|
transport: {
|
||||||
host: 'email-smtp.us-east-2.amazonaws.com',
|
host: 'email-smtp.us-east-2.amazonaws.com',
|
||||||
secure: false,
|
secure: false,
|
||||||
port:587,
|
port: 587,
|
||||||
// auth: {
|
|
||||||
// user: 'andreas.knuth@gmail.com',
|
|
||||||
// pass: 'ksnh xjae dqbv xana',
|
|
||||||
// },
|
|
||||||
auth: {
|
auth: {
|
||||||
user: 'AKIAU6GDWVAQ2QNFLNWN',
|
user: 'AKIAU6GDWVAQ2QNFLNWN',
|
||||||
pass: 'BDE9nZv/ARbpotim1mIOir52WgIbpSi9cv1oJoH8oEf7',
|
pass: 'BDE9nZv/ARbpotim1mIOir52WgIbpSi9cv1oJoH8oEf7',
|
||||||
@@ -38,7 +39,7 @@ const __dirname = path.dirname(__filename);
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
providers: [MailService],
|
providers: [MailService, UserService, FileService],
|
||||||
controllers: [MailController]
|
controllers: [MailController],
|
||||||
})
|
})
|
||||||
export class MailModule {}
|
export class MailModule {}
|
||||||
|
|||||||
@@ -1,26 +1,41 @@
|
|||||||
import { MailerService } from '@nestjs-modules/mailer';
|
import { MailerService } from '@nestjs-modules/mailer';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { AuthService } from '../auth/auth.service.js';
|
import path, { join } from 'path';
|
||||||
import { KeycloakUser, MailInfo, User } from '../models/main.model.js';
|
import { fileURLToPath } from 'url';
|
||||||
|
import { User } from '../models/db.model.js';
|
||||||
|
import { MailInfo } from '../models/main.model.js';
|
||||||
|
import { UserService } from '../user/user.service.js';
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MailService {
|
export class MailService {
|
||||||
constructor(private mailerService: MailerService, private authService:AuthService) {}
|
constructor(
|
||||||
|
private mailerService: MailerService,
|
||||||
async sendInquiry(mailInfo: MailInfo):Promise<KeycloakUser> {
|
private userService: UserService,
|
||||||
const user = await this.authService.getUser(mailInfo.userId) as KeycloakUser;
|
) {}
|
||||||
console.log(JSON.stringify(user));
|
|
||||||
await this.mailerService.sendMail({
|
async sendInquiry(mailInfo: MailInfo): Promise<User> {
|
||||||
to: user.email,
|
//const user = await this.authService.getUser(mailInfo.userId) as KeycloakUser;
|
||||||
from: '"Bizmatch Team" <info@bizmatch.net>', // override default from
|
const user = await this.userService.getUserByMail(mailInfo.email);
|
||||||
subject: `Inquiry from ${mailInfo.sender.name}`,
|
console.log(JSON.stringify(user));
|
||||||
template: './inquiry', // `.hbs` extension is appended automatically
|
await this.mailerService.sendMail({
|
||||||
context: { // ✏️ filling curly brackets with content
|
to: user.email,
|
||||||
name: user.firstName,
|
from: '"Bizmatch Team" <info@bizmatch.net>', // override default from
|
||||||
inquiry:mailInfo.sender.comments
|
subject: `Inquiry from ${mailInfo.sender.name}`,
|
||||||
},
|
//template: './inquiry', // `.hbs` extension is appended automatically
|
||||||
});
|
template: join(__dirname, '../..', 'mail/templates/inquiry.hbs'),
|
||||||
return user
|
context: {
|
||||||
}
|
// ✏️ filling curly brackets with content
|
||||||
|
name: user.firstname,
|
||||||
|
inquiry: mailInfo.sender.comments,
|
||||||
|
internalListingNumber: mailInfo.listing.internalListingNumber,
|
||||||
|
title: mailInfo.listing.title,
|
||||||
|
iname: mailInfo.sender.name,
|
||||||
|
phone: mailInfo.sender.phoneNumber,
|
||||||
|
email: mailInfo.sender.email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module.js';
|
import { AppModule } from './app.module.js';
|
||||||
|
import * as express from 'express';
|
||||||
|
import path, { join } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
app.setGlobalPrefix('bizmatch');
|
app.setGlobalPrefix('bizmatch');
|
||||||
|
|||||||
73
bizmatch-server/src/models/db.model.ts
Normal file
73
bizmatch-server/src/models/db.model.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
export interface User {
|
||||||
|
id?: string;
|
||||||
|
firstname: string;
|
||||||
|
lastname: string;
|
||||||
|
email: string;
|
||||||
|
phoneNumber?: string;
|
||||||
|
description?: string;
|
||||||
|
companyName?: string;
|
||||||
|
companyOverview?: string;
|
||||||
|
companyWebsite?: string;
|
||||||
|
companyLocation?: string;
|
||||||
|
offeredServices?: string;
|
||||||
|
areasServed?: string[];
|
||||||
|
hasProfile?: boolean;
|
||||||
|
hasCompanyLogo?: boolean;
|
||||||
|
licensedIn?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BusinessListing {
|
||||||
|
id: string;
|
||||||
|
userId?: string;
|
||||||
|
type?: number;
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
city?: string;
|
||||||
|
state?: string;
|
||||||
|
price?: number;
|
||||||
|
favoritesForUser?: string[];
|
||||||
|
draft?: boolean;
|
||||||
|
realEstateIncluded?: boolean;
|
||||||
|
leasedLocation?: boolean;
|
||||||
|
franchiseResale?: boolean;
|
||||||
|
salesRevenue?: number;
|
||||||
|
cashFlow?: number;
|
||||||
|
supportAndTraining?: string;
|
||||||
|
employees?: number;
|
||||||
|
established?: number;
|
||||||
|
internalListingNumber?: number;
|
||||||
|
reasonForSale?: string;
|
||||||
|
brokerLicencing?: string;
|
||||||
|
internals?: string;
|
||||||
|
created?: Date;
|
||||||
|
updated?: Date;
|
||||||
|
visits?: number;
|
||||||
|
lastVisit?: Date;
|
||||||
|
listingsCategory?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommercialPropertyListing {
|
||||||
|
id: string;
|
||||||
|
userId?: string;
|
||||||
|
type?: number;
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
city?: string;
|
||||||
|
state?: string;
|
||||||
|
price?: number;
|
||||||
|
favoritesForUser?: string[];
|
||||||
|
hideImage?: boolean;
|
||||||
|
draft?: boolean;
|
||||||
|
zipCode?: number;
|
||||||
|
county?: string;
|
||||||
|
email?: string;
|
||||||
|
website?: string;
|
||||||
|
phoneNumber?: string;
|
||||||
|
imageOrder?: string[];
|
||||||
|
imagePath?: string;
|
||||||
|
created?: Date;
|
||||||
|
updated?: Date;
|
||||||
|
visits?: number;
|
||||||
|
lastVisit?: Date;
|
||||||
|
listingsCategory?: string;
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../common-models/src/main.model.ts
|
|
||||||
166
bizmatch-server/src/models/main.model.ts
Normal file
166
bizmatch-server/src/models/main.model.ts
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import { BusinessListing, CommercialPropertyListing, User } from './db.model';
|
||||||
|
|
||||||
|
export interface StatesResult {
|
||||||
|
state: string;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KeyValue {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
export interface KeyValueRatio {
|
||||||
|
label: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
export interface KeyValueStyle {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
icon: string;
|
||||||
|
bgColorClass: string;
|
||||||
|
textColorClass: string;
|
||||||
|
}
|
||||||
|
export type SelectOption<T = number> = {
|
||||||
|
value: T;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
export type ImageType = {
|
||||||
|
name: 'propertyPicture' | 'companyLogo' | 'profile';
|
||||||
|
upload: string;
|
||||||
|
delete: string;
|
||||||
|
};
|
||||||
|
export type ListingCategory = {
|
||||||
|
name: 'business' | 'commercialProperty';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ListingType = BusinessListing | CommercialPropertyListing;
|
||||||
|
|
||||||
|
export type ResponseBusinessListingArray = {
|
||||||
|
data: BusinessListing[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
export type ResponseBusinessListing = {
|
||||||
|
data: BusinessListing;
|
||||||
|
};
|
||||||
|
export type ResponseCommercialPropertyListingArray = {
|
||||||
|
data: CommercialPropertyListing[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
export type ResponseCommercialPropertyListing = {
|
||||||
|
data: CommercialPropertyListing;
|
||||||
|
};
|
||||||
|
export type ResponseUsersArray = {
|
||||||
|
data: User[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
export interface ListingCriteria {
|
||||||
|
start: number;
|
||||||
|
length: number;
|
||||||
|
page: number;
|
||||||
|
pageCount: number;
|
||||||
|
type: number;
|
||||||
|
state: string;
|
||||||
|
minPrice: number;
|
||||||
|
maxPrice: number;
|
||||||
|
realEstateChecked: boolean;
|
||||||
|
title: string;
|
||||||
|
category: 'professional|broker';
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KeycloakUser {
|
||||||
|
id: string;
|
||||||
|
createdTimestamp: number;
|
||||||
|
username: string;
|
||||||
|
enabled: boolean;
|
||||||
|
totp: boolean;
|
||||||
|
emailVerified: boolean;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
email: string;
|
||||||
|
disableableCredentialTypes: any[];
|
||||||
|
requiredActions: any[];
|
||||||
|
notBefore: number;
|
||||||
|
access: Access;
|
||||||
|
}
|
||||||
|
export interface Access {
|
||||||
|
manageGroupMembership: boolean;
|
||||||
|
view: boolean;
|
||||||
|
mapRoles: boolean;
|
||||||
|
impersonate: boolean;
|
||||||
|
manage: boolean;
|
||||||
|
}
|
||||||
|
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 interface AutoCompleteCompleteEvent {
|
||||||
|
originalEvent: Event;
|
||||||
|
query: string;
|
||||||
|
}
|
||||||
|
export interface MailInfo {
|
||||||
|
sender: Sender;
|
||||||
|
userId: string;
|
||||||
|
email: string;
|
||||||
|
listing?: BusinessListing;
|
||||||
|
}
|
||||||
|
export interface Sender {
|
||||||
|
name?: string;
|
||||||
|
email?: string;
|
||||||
|
phoneNumber?: string;
|
||||||
|
state?: string;
|
||||||
|
comments?: string;
|
||||||
|
}
|
||||||
|
export interface ImageProperty {
|
||||||
|
id: string;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
import { Entity } from "redis-om";
|
import { Entity } from "redis-om";
|
||||||
import { UserBase } from "./main.model.js";
|
|
||||||
|
|
||||||
|
|
||||||
export interface Geo {
|
export interface Geo {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -65,6 +62,3 @@ export interface Timezone {
|
|||||||
abbreviation: string;
|
abbreviation: string;
|
||||||
tzName: string;
|
tzName: string;
|
||||||
}
|
}
|
||||||
export interface UserEntity extends UserBase, Entity {
|
|
||||||
licensedIn?: string[];
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// redis.module.ts
|
|
||||||
import { Module } from '@nestjs/common';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
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 { createClient } from 'redis';
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Injectable, NestMiddleware, Logger } from '@nestjs/common';
|
||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RequestDurationMiddleware implements NestMiddleware {
|
||||||
|
private readonly logger = new Logger(RequestDurationMiddleware.name);
|
||||||
|
|
||||||
|
use(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const start = Date.now();
|
||||||
|
res.on('finish', () => {
|
||||||
|
const duration = Date.now() - start;
|
||||||
|
this.logger.log(`${req.method} ${req.url} - ${duration}ms`);
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,113 +3,105 @@ import { ImageType, KeyValue, KeyValueStyle } from '../models/main.model.js';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SelectOptionsService {
|
export class SelectOptionsService {
|
||||||
|
constructor() {}
|
||||||
constructor() { }
|
public typesOfBusiness: Array<KeyValueStyle> = [
|
||||||
public typesOfBusiness: Array<KeyValueStyle> = [
|
{ name: 'Automotive', value: '1', icon: 'fa-solid fa-car', bgColorClass: 'bg-green-100', textColorClass: 'text-green-600' },
|
||||||
{ name: 'Automotive', value: '1', icon:'fa-solid fa-car',bgColorClass:'bg-green-100',textColorClass:'text-green-600' },
|
{ name: 'Industrial Services', value: '2', icon: 'fa-solid fa-industry', bgColorClass: 'bg-yellow-100', textColorClass: 'text-yellow-600' },
|
||||||
{ name: 'Industrial Services', value: '2', icon:'fa-solid fa-industry',bgColorClass:'bg-yellow-100',textColorClass:'text-yellow-600'},
|
{ name: 'Real Estate', value: '3', icon: 'pi pi-building', bgColorClass: 'bg-blue-100', textColorClass: 'text-blue-600' },
|
||||||
{ name: 'Real Estate', value: '3' , icon:'pi pi-building',bgColorClass:'bg-blue-100',textColorClass:'text-blue-600'},
|
{ name: 'Uncategorized', value: '4', icon: 'pi pi-question', bgColorClass: 'bg-cyan-100', textColorClass: 'text-cyan-600' },
|
||||||
{ name: 'Uncategorized', value: '4' , icon:'pi pi-question',bgColorClass:'bg-cyan-100',textColorClass:'text-cyan-600'},
|
{ name: 'Retail', value: '5', icon: 'fa-solid fa-money-bill-wave', bgColorClass: 'bg-pink-100', textColorClass: 'text-pink-600' },
|
||||||
{ name: 'Retail', value: '5' , icon:'fa-solid fa-money-bill-wave',bgColorClass:'bg-pink-100',textColorClass:'text-pink-600'},
|
{ name: 'Oilfield SVE and MFG.', value: '6', icon: 'fa-solid fa-oil-well', bgColorClass: 'bg-indigo-100', textColorClass: 'text-indigo-600' },
|
||||||
{ name: 'Oilfield SVE and MFG.', value: '6' , icon:'fa-solid fa-oil-well',bgColorClass:'bg-indigo-100',textColorClass:'text-indigo-600'},
|
{ name: 'Service', value: '7', icon: 'fa-solid fa-umbrella', bgColorClass: 'bg-teal-100', textColorClass: 'text-teal-600' },
|
||||||
{ name: 'Service', value: '7' , icon:'fa-solid fa-umbrella',bgColorClass:'bg-teal-100',textColorClass:'text-teal-600'},
|
{ name: 'Advertising', value: '8', icon: 'fa-solid fa-rectangle-ad', bgColorClass: 'bg-orange-100', textColorClass: 'text-orange-600' },
|
||||||
{ name: 'Advertising', value: '8' , icon:'fa-solid fa-rectangle-ad',bgColorClass:'bg-orange-100',textColorClass:'text-orange-600'},
|
{ name: 'Agriculture', value: '9', icon: 'fa-solid fa-wheat-awn', bgColorClass: 'bg-bluegray-100', textColorClass: 'text-bluegray-600' },
|
||||||
{ name: 'Agriculture', value: '9' , icon:'fa-solid fa-wheat-awn',bgColorClass:'bg-bluegray-100',textColorClass:'text-bluegray-600'},
|
{ name: 'Franchise', value: '10', icon: 'pi pi-star', bgColorClass: 'bg-purple-100', textColorClass: 'text-purple-600' },
|
||||||
{ name: 'Franchise', value: '10' , icon:'pi pi-star',bgColorClass:'bg-purple-100',textColorClass:'text-purple-600'},
|
{ name: 'Professional', value: '11', icon: 'fa-solid fa-user-gear', bgColorClass: 'bg-gray-100', textColorClass: 'text-gray-600' },
|
||||||
{ name: 'Professional', value: '11' , icon:'fa-solid fa-user-gear',bgColorClass:'bg-gray-100',textColorClass:'text-gray-600'},
|
{ name: 'Manufacturing', value: '12', icon: 'fa-solid fa-industry', bgColorClass: 'bg-red-100', textColorClass: 'text-red-600' },
|
||||||
{ name: 'Manufacturing', value: '12' , icon:'fa-solid fa-industry',bgColorClass:'bg-red-100',textColorClass:'text-red-600'},
|
{ name: 'Food and Restaurant', value: '13', icon: 'fa-solid fa-utensils', bgColorClass: 'bg-primary-100', textColorClass: 'text-primary-600' },
|
||||||
{ name: 'Food and Restaurant', value: '13' , icon:'fa-solid fa-utensils',bgColorClass:'bg-primary-100',textColorClass:'text-primary-600'},
|
];
|
||||||
];
|
public typesOfCommercialProperty: Array<KeyValueStyle> = [
|
||||||
public typesOfCommercialProperty: Array<KeyValueStyle> = [
|
{ name: 'Retail', value: '100', icon: 'fa-solid fa-money-bill-wave', bgColorClass: 'bg-pink-100', textColorClass: 'text-pink-600' },
|
||||||
{ name: 'Retail', value: '100' , icon:'fa-solid fa-money-bill-wave',bgColorClass:'bg-pink-100',textColorClass:'text-pink-600'},
|
{ name: 'Land', value: '101', icon: 'pi pi-building', bgColorClass: 'bg-blue-100', textColorClass: 'text-blue-600' },
|
||||||
{ name: 'Land', value: '101' , icon:'pi pi-building',bgColorClass:'bg-blue-100',textColorClass:'text-blue-600'},
|
{ name: 'Industrial', value: '102', icon: 'fa-solid fa-industry', bgColorClass: 'bg-yellow-100', textColorClass: 'text-yellow-600' },
|
||||||
{ name: 'Industrial', value: '102', icon:'fa-solid fa-industry',bgColorClass:'bg-yellow-100',textColorClass:'text-yellow-600'},
|
{ name: 'Office', value: '103', icon: 'fa-solid fa-umbrella', bgColorClass: 'bg-teal-100', textColorClass: 'text-teal-600' },
|
||||||
{ name: 'Office', value: '103' , icon:'fa-solid fa-umbrella',bgColorClass:'bg-teal-100',textColorClass:'text-teal-600'},
|
{ name: 'Mixed Use', value: '104', icon: 'fa-solid fa-rectangle-ad', bgColorClass: 'bg-orange-100', textColorClass: 'text-orange-600' },
|
||||||
{ name: 'Mixed Use', value: '104' , icon:'fa-solid fa-rectangle-ad',bgColorClass:'bg-orange-100',textColorClass:'text-orange-600'},
|
{ name: 'Multifamily', value: '105', icon: 'pi pi-star', bgColorClass: 'bg-purple-100', textColorClass: 'text-purple-600' },
|
||||||
{ name: 'Multifamily', value: '105' , icon:'pi pi-star',bgColorClass:'bg-purple-100',textColorClass:'text-purple-600'},
|
{ name: 'Uncategorized', value: '106', icon: 'pi pi-question', bgColorClass: 'bg-cyan-100', textColorClass: 'text-cyan-600' },
|
||||||
{ name: 'Uncategorized', value: '106' , icon:'pi pi-question',bgColorClass:'bg-cyan-100',textColorClass:'text-cyan-600'},
|
];
|
||||||
];
|
public prices: Array<KeyValue> = [
|
||||||
public prices: Array<KeyValue> = [
|
{ name: '$100K', value: '100000' },
|
||||||
{ name: '$100K', value: '100000' },
|
{ name: '$250K', value: '250000' },
|
||||||
{ name: '$250K', value: '250000' },
|
{ name: '$500K', value: '500000' },
|
||||||
{ name: '$500K', value: '500000' },
|
{ name: '$1M', value: '1000000' },
|
||||||
{ name: '$1M', value: '1000000' },
|
{ name: '$5M', value: '5000000' },
|
||||||
{ name: '$5M', value: '5000000' },
|
];
|
||||||
];
|
public listingCategories: Array<KeyValue> = [
|
||||||
public listingCategories: Array<KeyValue> = [
|
{ name: 'Business', value: 'business' },
|
||||||
{ name: 'Business', value: 'business' },
|
{ name: 'Commercial Property', value: 'commercialProperty' },
|
||||||
{ 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' },
|
{ name: 'Professional', value: 'professional', icon: 'pi-globe', bgColorClass: 'bg-yellow-100', textColorClass: 'text-yellow-600' },
|
||||||
{ name: 'Professional', value: 'professional', icon:'pi-globe',bgColorClass:'bg-yellow-100',textColorClass:'text-yellow-600' },
|
];
|
||||||
]
|
public imageTypes: ImageType[] = [
|
||||||
public imageTypes:ImageType[] = [
|
{ name: 'propertyPicture', upload: 'uploadPropertyPicture', delete: 'propertyPicture' },
|
||||||
{name:'propertyPicture',upload:'uploadPropertyPicture',delete:'propertyPicture'},
|
{ name: 'companyLogo', upload: 'uploadCompanyLogo', delete: 'logo' },
|
||||||
{name:'companyLogo',upload:'uploadCompanyLogo',delete:'logo'},
|
{ name: 'profile', upload: 'uploadProfile', delete: 'profile' },
|
||||||
{name:'profile',upload:'uploadProfile',delete:'profile'},
|
];
|
||||||
]
|
private usStates = [
|
||||||
private usStates = [
|
{ name: 'ALABAMA', abbreviation: 'AL' },
|
||||||
{ name: 'ALABAMA', abbreviation: 'AL'},
|
{ name: 'ALASKA', abbreviation: 'AK' },
|
||||||
{ name: 'ALASKA', abbreviation: 'AK'},
|
{ name: 'ARIZONA', abbreviation: 'AZ' },
|
||||||
{ name: 'AMERICAN SAMOA', abbreviation: 'AS'},
|
{ name: 'ARKANSAS', abbreviation: 'AR' },
|
||||||
{ name: 'ARIZONA', abbreviation: 'AZ'},
|
{ name: 'CALIFORNIA', abbreviation: 'CA' },
|
||||||
{ name: 'ARKANSAS', abbreviation: 'AR'},
|
{ name: 'COLORADO', abbreviation: 'CO' },
|
||||||
{ name: 'CALIFORNIA', abbreviation: 'CA'},
|
{ name: 'CONNECTICUT', abbreviation: 'CT' },
|
||||||
{ name: 'COLORADO', abbreviation: 'CO'},
|
{ name: 'DELAWARE', abbreviation: 'DE' },
|
||||||
{ name: 'CONNECTICUT', abbreviation: 'CT'},
|
{ name: 'DISTRICT OF COLUMBIA', abbreviation: 'DC' },
|
||||||
{ name: 'DELAWARE', abbreviation: 'DE'},
|
{ name: 'FLORIDA', abbreviation: 'FL' },
|
||||||
{ name: 'DISTRICT OF COLUMBIA', abbreviation: 'DC'},
|
{ name: 'GEORGIA', abbreviation: 'GA' },
|
||||||
{ name: 'FEDERATED STATES OF MICRONESIA', abbreviation: 'FM'},
|
{ name: 'GUAM', abbreviation: 'GU' },
|
||||||
{ name: 'FLORIDA', abbreviation: 'FL'},
|
{ name: 'HAWAII', abbreviation: 'HI' },
|
||||||
{ name: 'GEORGIA', abbreviation: 'GA'},
|
{ name: 'IDAHO', abbreviation: 'ID' },
|
||||||
{ name: 'GUAM', abbreviation: 'GU'},
|
{ name: 'ILLINOIS', abbreviation: 'IL' },
|
||||||
{ name: 'HAWAII', abbreviation: 'HI'},
|
{ name: 'INDIANA', abbreviation: 'IN' },
|
||||||
{ name: 'IDAHO', abbreviation: 'ID'},
|
{ name: 'IOWA', abbreviation: 'IA' },
|
||||||
{ name: 'ILLINOIS', abbreviation: 'IL'},
|
{ name: 'KANSAS', abbreviation: 'KS' },
|
||||||
{ name: 'INDIANA', abbreviation: 'IN'},
|
{ name: 'KENTUCKY', abbreviation: 'KY' },
|
||||||
{ name: 'IOWA', abbreviation: 'IA'},
|
{ name: 'LOUISIANA', abbreviation: 'LA' },
|
||||||
{ name: 'KANSAS', abbreviation: 'KS'},
|
{ name: 'MAINE', abbreviation: 'ME' },
|
||||||
{ name: 'KENTUCKY', abbreviation: 'KY'},
|
{ name: 'MARYLAND', abbreviation: 'MD' },
|
||||||
{ name: 'LOUISIANA', abbreviation: 'LA'},
|
{ name: 'MASSACHUSETTS', abbreviation: 'MA' },
|
||||||
{ name: 'MAINE', abbreviation: 'ME'},
|
{ name: 'MICHIGAN', abbreviation: 'MI' },
|
||||||
{ name: 'MARSHALL ISLANDS', abbreviation: 'MH'},
|
{ name: 'MINNESOTA', abbreviation: 'MN' },
|
||||||
{ name: 'MARYLAND', abbreviation: 'MD'},
|
{ name: 'MISSISSIPPI', abbreviation: 'MS' },
|
||||||
{ name: 'MASSACHUSETTS', abbreviation: 'MA'},
|
{ name: 'MISSOURI', abbreviation: 'MO' },
|
||||||
{ name: 'MICHIGAN', abbreviation: 'MI'},
|
{ name: 'MONTANA', abbreviation: 'MT' },
|
||||||
{ name: 'MINNESOTA', abbreviation: 'MN'},
|
{ name: 'NEBRASKA', abbreviation: 'NE' },
|
||||||
{ name: 'MISSISSIPPI', abbreviation: 'MS'},
|
{ name: 'NEVADA', abbreviation: 'NV' },
|
||||||
{ name: 'MISSOURI', abbreviation: 'MO'},
|
{ name: 'NEW HAMPSHIRE', abbreviation: 'NH' },
|
||||||
{ name: 'MONTANA', abbreviation: 'MT'},
|
{ name: 'NEW JERSEY', abbreviation: 'NJ' },
|
||||||
{ name: 'NEBRASKA', abbreviation: 'NE'},
|
{ name: 'NEW MEXICO', abbreviation: 'NM' },
|
||||||
{ name: 'NEVADA', abbreviation: 'NV'},
|
{ name: 'NEW YORK', abbreviation: 'NY' },
|
||||||
{ name: 'NEW HAMPSHIRE', abbreviation: 'NH'},
|
{ name: 'NORTH CAROLINA', abbreviation: 'NC' },
|
||||||
{ name: 'NEW JERSEY', abbreviation: 'NJ'},
|
{ name: 'NORTH DAKOTA', abbreviation: 'ND' },
|
||||||
{ name: 'NEW MEXICO', abbreviation: 'NM'},
|
{ name: 'OHIO', abbreviation: 'OH' },
|
||||||
{ name: 'NEW YORK', abbreviation: 'NY'},
|
{ name: 'OKLAHOMA', abbreviation: 'OK' },
|
||||||
{ name: 'NORTH CAROLINA', abbreviation: 'NC'},
|
{ name: 'OREGON', abbreviation: 'OR' },
|
||||||
{ name: 'NORTH DAKOTA', abbreviation: 'ND'},
|
{ name: 'PALAU', abbreviation: 'PW' },
|
||||||
{ name: 'NORTHERN MARIANA ISLANDS', abbreviation: 'MP'},
|
{ name: 'PENNSYLVANIA', abbreviation: 'PA' },
|
||||||
{ name: 'OHIO', abbreviation: 'OH'},
|
{ name: 'RHODE ISLAND', abbreviation: 'RI' },
|
||||||
{ name: 'OKLAHOMA', abbreviation: 'OK'},
|
{ name: 'SOUTH CAROLINA', abbreviation: 'SC' },
|
||||||
{ name: 'OREGON', abbreviation: 'OR'},
|
{ name: 'SOUTH DAKOTA', abbreviation: 'SD' },
|
||||||
{ name: 'PALAU', abbreviation: 'PW'},
|
{ name: 'TENNESSEE', abbreviation: 'TN' },
|
||||||
{ name: 'PENNSYLVANIA', abbreviation: 'PA'},
|
{ name: 'TEXAS', abbreviation: 'TX' },
|
||||||
{ name: 'PUERTO RICO', abbreviation: 'PR'},
|
{ name: 'UTAH', abbreviation: 'UT' },
|
||||||
{ name: 'RHODE ISLAND', abbreviation: 'RI'},
|
{ name: 'VERMONT', abbreviation: 'VT' },
|
||||||
{ name: 'SOUTH CAROLINA', abbreviation: 'SC'},
|
{ name: 'VIRGINIA', abbreviation: 'VA' },
|
||||||
{ name: 'SOUTH DAKOTA', abbreviation: 'SD'},
|
{ name: 'WASHINGTON', abbreviation: 'WA' },
|
||||||
{ name: 'TENNESSEE', abbreviation: 'TN'},
|
{ name: 'WEST VIRGINIA', abbreviation: 'WV' },
|
||||||
{ name: 'TEXAS', abbreviation: 'TX'},
|
{ name: 'WISCONSIN', abbreviation: 'WI' },
|
||||||
{ name: 'UTAH', abbreviation: 'UT'},
|
{ name: 'WYOMING', abbreviation: 'WY' },
|
||||||
{ name: 'VERMONT', abbreviation: 'VT'},
|
];
|
||||||
{ name: 'VIRGIN ISLANDS', abbreviation: 'VI'},
|
public locations: Array<any> = [...this.usStates.map(state => ({ name: state.name, value: state.abbreviation }))].concat({ name: 'CANADA', value: 'CA' });
|
||||||
{ name: 'VIRGINIA', abbreviation: 'VA'},
|
|
||||||
{ name: 'WASHINGTON', abbreviation: 'WA'},
|
|
||||||
{ name: 'WEST VIRGINIA', abbreviation: 'WV'},
|
|
||||||
{ name: 'WISCONSIN', abbreviation: 'WI'},
|
|
||||||
{ name: 'WYOMING', abbreviation: 'WY' }
|
|
||||||
]
|
|
||||||
public locations:Array<any> = [...this.usStates.map(state=>({name:state.name, value:state.abbreviation}))].concat({name:'CANADA',value:"CA"});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import { Body, Controller, Get, Inject, Param, Post, Put } from '@nestjs/common';
|
import { Body, Controller, Get, Inject, Param, Post, Put, Query, Req } from '@nestjs/common';
|
||||||
import { UserService } from './user.service.js';
|
import { UserService } from './user.service.js';
|
||||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import { UserEntity } from 'src/models/server.model.js';
|
import { User } from 'src/models/db.model.js';
|
||||||
|
|
||||||
@Controller('user')
|
@Controller('user')
|
||||||
export class UserController {
|
export class UserController {
|
||||||
|
|
||||||
constructor(private userService: UserService, @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {}
|
constructor(private userService: UserService, @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
findByMail(@Query('mail') mail: string): any {
|
||||||
|
this.logger.info(`Searching for user with EMail: ${mail}`);
|
||||||
|
const user = this.userService.getUserByMail(mail);
|
||||||
|
this.logger.info(`Found user: ${JSON.stringify(user)}`);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
findById(@Param('id') id: string): any {
|
findById(@Param('id') id: string): any {
|
||||||
this.logger.info(`Searching for user with ID: ${id}`);
|
this.logger.info(`Searching for user with ID: ${id}`);
|
||||||
@@ -16,9 +23,8 @@ export class UserController {
|
|||||||
this.logger.info(`Found user: ${JSON.stringify(user)}`);
|
this.logger.info(`Found user: ${JSON.stringify(user)}`);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
save(@Body() user: any): Promise<UserEntity> {
|
save(@Body() user: any): Promise<User> {
|
||||||
this.logger.info(`Saving user: ${JSON.stringify(user)}`);
|
this.logger.info(`Saving user: ${JSON.stringify(user)}`);
|
||||||
const savedUser = this.userService.saveUser(user);
|
const savedUser = this.userService.saveUser(user);
|
||||||
this.logger.info(`User persisted: ${JSON.stringify(savedUser)}`);
|
this.logger.info(`User persisted: ${JSON.stringify(savedUser)}`);
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { DrizzleModule } from '../drizzle/drizzle.module.js';
|
||||||
|
import { FileService } from '../file/file.service.js';
|
||||||
import { UserController } from './user.controller.js';
|
import { UserController } from './user.controller.js';
|
||||||
import { UserService } from './user.service.js';
|
import { UserService } from './user.service.js';
|
||||||
import { RedisModule } from '../redis/redis.module.js';
|
|
||||||
import { FileService } from '../file/file.service.js';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [RedisModule],
|
imports: [DrizzleModule],
|
||||||
controllers: [UserController],
|
controllers: [UserController],
|
||||||
providers: [UserService,FileService]
|
providers: [UserService, FileService],
|
||||||
})
|
})
|
||||||
export class UserModule {
|
export class UserModule {}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,45 +1,77 @@
|
|||||||
import { Get, Inject, Injectable, Param } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { createClient } from 'redis';
|
import { and, eq, ilike, or, sql } from 'drizzle-orm';
|
||||||
import { Entity, Repository, Schema } from 'redis-om';
|
import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver.js';
|
||||||
import { ListingCriteria, User } from '../models/main.model.js';
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
import { REDIS_CLIENT } from '../redis/redis.module.js';
|
import { PG_CONNECTION } from 'src/drizzle/schema.js';
|
||||||
import { UserEntity } from '../models/server.model.js';
|
import { User } from 'src/models/db.model.js';
|
||||||
|
import { Logger } from 'winston';
|
||||||
|
import * as schema from '../drizzle/schema.js';
|
||||||
import { FileService } from '../file/file.service.js';
|
import { FileService } from '../file/file.service.js';
|
||||||
|
import { ListingCriteria } from '../models/main.model.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
userRepository:Repository;
|
constructor(
|
||||||
userSchema = new Schema('user',{
|
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||||
id: { type: 'string' },
|
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,
|
||||||
firstname: { type: 'string' },
|
private fileService: FileService,
|
||||||
lastname: { type: 'string' },
|
) {}
|
||||||
email: { type: 'string' },
|
private getConditions(criteria: ListingCriteria): any[] {
|
||||||
phoneNumber: { type: 'string' },
|
const conditions = [];
|
||||||
companyOverview:{ type: 'string' },
|
if (criteria.state) {
|
||||||
companyWebsite:{ type: 'string' },
|
conditions.push(sql`EXISTS (SELECT 1 FROM unnest(users."areasServed") AS area WHERE area LIKE '%' || ${criteria.state} || '%')`);
|
||||||
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 redis: any,private fileService:FileService){
|
|
||||||
this.userRepository = new Repository(this.userSchema, redis)
|
|
||||||
this.userRepository.createIndex();
|
|
||||||
}
|
}
|
||||||
async getUserById( id:string){
|
if (criteria.name) {
|
||||||
const user = await this.userRepository.fetch(id) as UserEntity;
|
conditions.push(or(ilike(schema.users.firstname, `%${criteria.name}%`), ilike(schema.users.lastname, `%${criteria.name}%`)));
|
||||||
user.hasCompanyLogo=this.fileService.hasCompanyLogo(id);
|
|
||||||
user.hasProfile=this.fileService.hasProfile(id);
|
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
async saveUser(user:any):Promise<UserEntity>{
|
return conditions;
|
||||||
return await this.userRepository.save(user.id,user) as UserEntity
|
}
|
||||||
|
async getUserByMail(email: string) {
|
||||||
|
const users = (await this.conn
|
||||||
|
.select()
|
||||||
|
.from(schema.users)
|
||||||
|
.where(sql`email = ${email}`)) as User[];
|
||||||
|
const user = users[0];
|
||||||
|
user.hasCompanyLogo = this.fileService.hasCompanyLogo(user.id);
|
||||||
|
user.hasProfile = this.fileService.hasProfile(user.id);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
async getUserById(id: string) {
|
||||||
|
const users = (await this.conn
|
||||||
|
.select()
|
||||||
|
.from(schema.users)
|
||||||
|
.where(sql`id = ${id}`)) as User[];
|
||||||
|
const user = users[0];
|
||||||
|
user.hasCompanyLogo = this.fileService.hasCompanyLogo(id);
|
||||||
|
user.hasProfile = this.fileService.hasProfile(id);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
async saveUser(user: any): Promise<User> {
|
||||||
|
if (user.id) {
|
||||||
|
const [updateUser] = await this.conn.update(schema.users).set(user).where(eq(schema.users.id, user.id)).returning();
|
||||||
|
return updateUser as User;
|
||||||
|
} else {
|
||||||
|
const [newUser] = await this.conn.insert(schema.users).values(user).returning();
|
||||||
|
return newUser as User;
|
||||||
}
|
}
|
||||||
async findUser(criteria:ListingCriteria){
|
}
|
||||||
return await this.userRepository.search().return.all();
|
async findUser(criteria: ListingCriteria) {
|
||||||
}
|
const start = criteria.start ? criteria.start : 0;
|
||||||
|
const length = criteria.length ? criteria.length : 12;
|
||||||
}
|
const conditions = this.getConditions(criteria);
|
||||||
|
const [data, total] = await Promise.all([
|
||||||
|
this.conn
|
||||||
|
.select()
|
||||||
|
.from(schema.users)
|
||||||
|
.where(and(...conditions))
|
||||||
|
.offset(start)
|
||||||
|
.limit(length),
|
||||||
|
this.conn
|
||||||
|
.select({ count: sql`count(*)` })
|
||||||
|
.from(schema.users)
|
||||||
|
.where(and(...conditions))
|
||||||
|
.then(result => Number(result[0].count)),
|
||||||
|
]);
|
||||||
|
return { total, data };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2021",
|
"target": "ES2021",
|
||||||
"module": "Node16",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Node16",
|
"moduleResolution": "Node",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
@@ -16,7 +16,8 @@
|
|||||||
"strictNullChecks": false,
|
"strictNullChecks": false,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"strictBindCallApply": false,
|
"strictBindCallApply": false,
|
||||||
"forceConsistentCasingInFileNames": false,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noFallthroughCasesInSwitch": false,
|
"noFallthroughCasesInSwitch": false,
|
||||||
|
"esModuleInterop":true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
bizmatch/.prettierrc.json
Normal file
18
bizmatch/.prettierrc.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"embeddedLanguageFormatting": "auto",
|
||||||
|
"htmlWhitespaceSensitivity": "css",
|
||||||
|
"insertPragma": false,
|
||||||
|
"jsxBracketSameLine": false,
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"printWidth": 220,
|
||||||
|
"proseWrap": "always",
|
||||||
|
"quoteProps": "as-needed",
|
||||||
|
"requirePragma": false,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"useTabs": false,
|
||||||
|
"vueIndentScriptAndStyle": false
|
||||||
|
}
|
||||||
28
bizmatch/.vscode/settings.json
vendored
Normal file
28
bizmatch/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"editor.suggestSelection": "first",
|
||||||
|
"vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue",
|
||||||
|
"explorer.confirmDelete": false,
|
||||||
|
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[html]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.defaultFormatter": "vscode.json-language-features"
|
||||||
|
},
|
||||||
|
"[scss]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[jsonc]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.organizeImports": "explicit"
|
||||||
|
},
|
||||||
|
"prettier.printWidth": 240,
|
||||||
|
"git.autofetch": false,
|
||||||
|
"git.autorefresh": true
|
||||||
|
}
|
||||||
32
bizmatch/certs/cert.pem
Normal file
32
bizmatch/certs/cert.pem
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFgTCCA2mgAwIBAgIUa+QJdPmuRDNbuf/nzb2J+6ii5nwwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwUDELMAkGA1UEBhMCREUxDDAKBgNVBAgMA05SVzEQMA4GA1UEBwwHRVJLUkFU
|
||||||
|
SDEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTI0MDUxMDEy
|
||||||
|
NTQxNVoXDTI1MDUxMDEyNTQxNVowUDELMAkGA1UEBhMCREUxDDAKBgNVBAgMA05S
|
||||||
|
VzEQMA4GA1UEBwwHRVJLUkFUSDEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
|
||||||
|
dHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxiTSQDCC/i3n
|
||||||
|
X6bMKpl0baUgjbzYDc7ZrvIYfj/t25sdv0E/07ysbXNuldzCX6Rnva/1wVZS30zy
|
||||||
|
vQm8cVM074oP9qy7wKeIU15nEwRe03P5zipix1WXGWXHY+ShZ2MHy/iDQ1XzpO3p
|
||||||
|
xXs2vxuJZSUoz1M0c+/pTWBx2D790l5qNkt2sbk5NaHfPQDuw+y2cXDqmJqcCi9I
|
||||||
|
rYbaQGhXeb/IRu8pW4UwasVsq7DxGlDX8k8Dva5O0Ixf+muqQELuMdeTtR/PoFxw
|
||||||
|
2F+qOUlS2ujuyQkLOvVZOTalxRfWMuexaQzLlQO91MDehTrOFuMUBCKhYztgZKe2
|
||||||
|
k9z0fTJmtLyxMPTQuZCv1Gnrw6hcVxjiFQ8YP2ni+ekb86dIA3llH8r+4xEGygfB
|
||||||
|
QxHiBH9uO8Q9MFpfU2CPE7GxQoB17fu4KqaK0ucVnNM+rJcsNom9svixb5C4CkS/
|
||||||
|
S1/KQVDi8mrYwQIOP+Y4YLuNvSvUlitZXq8h0ogVqNMl2+R0CYX4lk/mkOEeCeGW
|
||||||
|
yG4ek2GQxZNLAnoMoLb+kHnVhPaV0SWW052wvXZzOrIMrlkSZK6yYim3JPsD8hc/
|
||||||
|
284lNEFL3DknICPsVFd64LjwPxA0J5AqyhQAvpXyFVHUUA5+h2EATrBh/Fp9cw84
|
||||||
|
AkEeVArMWOlx5cg7nAdgQaD5XUaBp7kCAwEAAaNTMFEwHQYDVR0OBBYEFMSO9FoT
|
||||||
|
nqjHpniyExGf53tV/TAhMB8GA1UdIwQYMBaAFMSO9FoTnqjHpniyExGf53tV/TAh
|
||||||
|
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBADbsIroXoCIe8adn
|
||||||
|
zh/WNZUUXLDW3JU2QyyYKhnZgqE0Wqh5QLgNwd5ZfH3Iaqhf0xFH9jUeEAWWA17d
|
||||||
|
hVy4rWsC200DRZ5BYOaffqdflpDE3yAk+8p3kAWjogaCX1wvBuLZ9BHWpuqQ72ec
|
||||||
|
zYM2ag2wWfBpicXl4BaSnsx1xErxKvxgixgGy6BhcErmfnYtJDnU2Cl+MSMXb758
|
||||||
|
7hAl9JiJAH8OuaAjvbkhSVTZQFjDgCGHHQ8mR8IksWdGGe/LN/yoWc4lk7lv7vmu
|
||||||
|
YBfP/SNZvxBzZlw1fXcdz1Wirljy7yz3+s59Knkc2jysysFC4LkZlUy0unmGoPy0
|
||||||
|
D1XdXyDMy05eoUaeRnM0rfzUxYfXMA3sQlsWw7he6fD8YylVedXxd3mcfK0jle0y
|
||||||
|
VkDyreZ9+mc/4vmjW0KpOfFGvhhAS9L1D8K3bKpky3HoHSqK1Nb8Ymh/WkhOpHwg
|
||||||
|
unUyIKdRHvGeWkUXQaLbRKI6w2BQwT7oKDOD60cJG26U3XcYarevz9qHsZX865tj
|
||||||
|
4xZrp+IUr8OkYBnRrmx2TZ70goRXI77nHVzHmY+xHhjvPJOZOcUAvEHU+5VY3ucN
|
||||||
|
0noEqiYzb77LcqVbbL3cywDLiyfdx9/x8TU1iYPA+IMwhYb/tLBFzFWmR7znw6On
|
||||||
|
D775XK/EVryozX/6GmtG+XGZs+57
|
||||||
|
-----END CERTIFICATE-----
|
||||||
52
bizmatch/certs/key.pem
Normal file
52
bizmatch/certs/key.pem
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQDGJNJAMIL+Ledf
|
||||||
|
pswqmXRtpSCNvNgNztmu8hh+P+3bmx2/QT/TvKxtc26V3MJfpGe9r/XBVlLfTPK9
|
||||||
|
CbxxUzTvig/2rLvAp4hTXmcTBF7Tc/nOKmLHVZcZZcdj5KFnYwfL+INDVfOk7enF
|
||||||
|
eza/G4llJSjPUzRz7+lNYHHYPv3SXmo2S3axuTk1od89AO7D7LZxcOqYmpwKL0it
|
||||||
|
htpAaFd5v8hG7ylbhTBqxWyrsPEaUNfyTwO9rk7QjF/6a6pAQu4x15O1H8+gXHDY
|
||||||
|
X6o5SVLa6O7JCQs69Vk5NqXFF9Yy57FpDMuVA73UwN6FOs4W4xQEIqFjO2Bkp7aT
|
||||||
|
3PR9Mma0vLEw9NC5kK/UaevDqFxXGOIVDxg/aeL56Rvzp0gDeWUfyv7jEQbKB8FD
|
||||||
|
EeIEf247xD0wWl9TYI8TsbFCgHXt+7gqporS5xWc0z6slyw2ib2y+LFvkLgKRL9L
|
||||||
|
X8pBUOLyatjBAg4/5jhgu429K9SWK1leryHSiBWo0yXb5HQJhfiWT+aQ4R4J4ZbI
|
||||||
|
bh6TYZDFk0sCegygtv6QedWE9pXRJZbTnbC9dnM6sgyuWRJkrrJiKbck+wPyFz/b
|
||||||
|
ziU0QUvcOScgI+xUV3rguPA/EDQnkCrKFAC+lfIVUdRQDn6HYQBOsGH8Wn1zDzgC
|
||||||
|
QR5UCsxY6XHlyDucB2BBoPldRoGnuQIDAQABAoICAAazixrZqSyAj/E3unL8Yqgs
|
||||||
|
rAevKd15r/oPPQ3UCq7hNaXYxphaKri+7TALWdWTQWD0eQrTaRUdTJ5hHGr2xfUO
|
||||||
|
BdExcV4oLF+pczH89VoQc5Pp8hJMzkHxI8e4nU7aVhKrcoEOAKIE2+Guc6EOBN0T
|
||||||
|
Xyh352++XvUbfG40XzBEujHg5oBHQ+yQ73RoOisNL/RxPbXwkLN1eu9Hfs0r2j2H
|
||||||
|
Y3Yms47hV8xcpfq+jsD1mAAddQJuyUKbZMma55SpztWHtXqsO0Dwr25Z+e9bD/7Q
|
||||||
|
XvcUo7kYQC7DruKWFkv9cw4a/S2qhTqTVVNLNFoozu3+39dz1CRDWdTxZaFwWXHX
|
||||||
|
KWc/YRmi5gxjadF3F87Ur+DYBLUJT/Iz5iUfUOQ/OON4ARDUM0UNqC1DemNxxI53
|
||||||
|
eiCw21GYwIA/59w7cWHOHJZImXKBMVJ3ko7hjI1eo4LWPvDfQHlG+5Fo9vUNPk9c
|
||||||
|
GiIreWMIycc5yUm9y1zxTa5zJdOBS+dQD0T230++o9OPytAiz2wT4ChXBfm02WUs
|
||||||
|
0UaU3uGSDmsvt4WI9SjlvSQi12EVekYc86lmlpgtMxc6zBpwJaFSrZa+N4ax9A5P
|
||||||
|
hOOeO6jy5XjEHTcIW+Yhrb0PCEr/U2xSn+zfhclHGTuwQLTdB30SB1pPECtA1j2+
|
||||||
|
rdcFx9+1asoIlQxO+k2pAoIBAQDsQQesFELq96/+7rTSgN1lm19AqKvm102WiOHT
|
||||||
|
CgJhltH19PyOnO23MKQKoYabTsSH2kOpwaA3CKPTk1ecU0P6mptN1xxEN7NgkRKC
|
||||||
|
Wr/pilXKTJg98A/o0zbxZkeYmyT3x6XwWVU1w4msR5lQ7bH4eR6MaQDghAKTRrAS
|
||||||
|
XMRFM4WQBfWr6DLTtz6Am5o+dA5qr8iIdjqGn8CgzPPqFQ/1ItWRkR2eiaGBUUhY
|
||||||
|
2Z8sL+3WW8h0u5qULqIrpY4EjLfl68FFbYgoxSLskQMpX4H/fCc8s9XPn527Ckiv
|
||||||
|
UuoRP7wsdcpt3H3SGDB8ZePH2JAbeu7nauAYeoSuzmqLwsF9AoIBAQDWtF96PpHK
|
||||||
|
FGQ8Epy79FDuJ95qaMFrfHRhx60erb2hHdwUYpBgGXHzN2icdP6Jg90ZyLdU97xH
|
||||||
|
QSNrTLxeUYcCZf0pucPYMFvNdMhTXiYLVpfwkho7Wl4YmuXEOG5ZW4pp0+sgFF/X
|
||||||
|
V8p2hu5QART6JwVsyBrO2T7EoGVDppbbhzF6tXnuLDV/JiP6/QcEYMxitQ06s6B2
|
||||||
|
8MXIqqNbidaCoALmeDgzderSKmiSGHWcAO6mef+xh0qZMfpOjwVLRTQyheiHJaub
|
||||||
|
DkBbtNu91kPJoyyn5+dCbuuK+tOii3FSANBAFH19esZJfcuZWo9x9dqZsT0HZ0lE
|
||||||
|
tlUDXhGBrVPtAoIBAHfD86a5Ur8YtyCOVB5Oc23Z2OzHVPWd+dgxJgG9Fj3wnhmI
|
||||||
|
iyukxCFUyCQXhExhHuIbtKdu39BmUd6k2AoIb/Kvw8EvJkYy0n1GrdJlPNqgZSM7
|
||||||
|
twXXF8mYoUa46dyj8ZamoCl6r+akbLtoRIGxLcJfbCwT4vzuDvwoHoQAgQLvvmqn
|
||||||
|
isYN3Q5U25uIxiWY4eIVoJwFC2BJxfX+UDw/VyqW8RttLE29SaFr2jgogjd9SJ2d
|
||||||
|
Q75hiFhMV6u2rosB5wvoer6+awL4BN9WF/s2Tol8n8t3AxHQwb4a1YQDjWMXI0aK
|
||||||
|
pAcTerkxyAqYAGPEFjHIHSo1lMrz+SVAwOR+42UCggEAUbVxRId9WidqggYfSdRP
|
||||||
|
3GKl3V8ihPJnJDMmai96pE9Fyyg7g6cLW6ExmaFYoSLiyQY+5wIk0AU1IoeghFCI
|
||||||
|
jdwcfX2pz6OPvF/+QOPqnJQG3NHtU7svZjPEz2kebblNsrqol5vJYZ2SeosdNKtE
|
||||||
|
vXKOOPjqYuAAaDoWb6l9bexEY0ufLIn8jfgI52LWAc+I2OPINhfYMIuu6ZAu/Q42
|
||||||
|
6Z1VnToRQVxV0ke7ZiYS1Bzytb5mFbzEIgsIFE+PlzauB7A4bv5iEW9aBMyOd++L
|
||||||
|
+rezre6ubvThhRGx6wEgTjHrDwf9Pfy0a5GJI0J4pskGuUjfTer70j+FmPN6vBwn
|
||||||
|
fQKCAQANeREfOILt9Unwpbo9Vj48BMfVYvJN7Gk4K6LGWN0rE0jxtpAzBiI3BqI0
|
||||||
|
+oj1gy+6Nn9n4hbeqDSyVB5uivCxmFIXpPO1s8Xu+EpEm90Po/551wWBvePOe8YK
|
||||||
|
vJK+UqUXDDcG+CUKsY8quOrNFIbSu4vOB81lgELh/cfhF/C5yOCsJx5pk5TJFwl8
|
||||||
|
3mAlV6KKTcacqEB/kKg+3sY1sv31EdsvpwOcEmXRXhI6hv4yENk0+cEFpEgJ7gkH
|
||||||
|
gzJ5IYYSEAhfy9lPDOhwTG3VC8Fr/z6gld6V6hym9cv2emd6ifjnP4rivsGg8d77
|
||||||
|
qs7lw2IbVhzRkVryySXsCXn2O1iu
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
95
bizmatch/dbschema.ts
Normal file
95
bizmatch/dbschema.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AUTO-GENERATED FILE - DO NOT EDIT!
|
||||||
|
*
|
||||||
|
* This file was automatically generated by pg-to-ts v.4.1.1
|
||||||
|
* $ pg-to-ts generate -c postgresql://username:password@localhost:5432/bizmatch -t businesses -s public
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
export type Json = unknown;
|
||||||
|
|
||||||
|
// Table businesses
|
||||||
|
export interface Businesses {
|
||||||
|
id: string;
|
||||||
|
userId: string | null;
|
||||||
|
type: number | null;
|
||||||
|
title: string | null;
|
||||||
|
description: string | null;
|
||||||
|
city: string | null;
|
||||||
|
state: string | null;
|
||||||
|
price: number | null;
|
||||||
|
favoritesForUser: string[] | null;
|
||||||
|
draft: boolean | null;
|
||||||
|
listingsCategory: string | null;
|
||||||
|
realEstateIncluded: boolean | null;
|
||||||
|
leasedLocation: boolean | null;
|
||||||
|
franchiseResale: boolean | null;
|
||||||
|
salesRevenue: number | null;
|
||||||
|
cashFlow: number | null;
|
||||||
|
supportAndTraining: string | null;
|
||||||
|
employees: number | null;
|
||||||
|
established: number | null;
|
||||||
|
internalListingNumber: number | null;
|
||||||
|
reasonForSale: string | null;
|
||||||
|
brokerLicencing: string | null;
|
||||||
|
internals: string | null;
|
||||||
|
created: Date | null;
|
||||||
|
updated: Date | null;
|
||||||
|
visits: number | null;
|
||||||
|
lastVisit: Date | null;
|
||||||
|
}
|
||||||
|
export interface BusinessesInput {
|
||||||
|
id?: string;
|
||||||
|
userId?: string | null;
|
||||||
|
type?: number | null;
|
||||||
|
title?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
city?: string | null;
|
||||||
|
state?: string | null;
|
||||||
|
price?: number | null;
|
||||||
|
favoritesForUser?: string[] | null;
|
||||||
|
draft?: boolean | null;
|
||||||
|
listingsCategory?: string | null;
|
||||||
|
realEstateIncluded?: boolean | null;
|
||||||
|
leasedLocation?: boolean | null;
|
||||||
|
franchiseResale?: boolean | null;
|
||||||
|
salesRevenue?: number | null;
|
||||||
|
cashFlow?: number | null;
|
||||||
|
supportAndTraining?: string | null;
|
||||||
|
employees?: number | null;
|
||||||
|
established?: number | null;
|
||||||
|
internalListingNumber?: number | null;
|
||||||
|
reasonForSale?: string | null;
|
||||||
|
brokerLicencing?: string | null;
|
||||||
|
internals?: string | null;
|
||||||
|
created?: Date | null;
|
||||||
|
updated?: Date | null;
|
||||||
|
visits?: number | null;
|
||||||
|
lastVisit?: Date | null;
|
||||||
|
}
|
||||||
|
const businesses = {
|
||||||
|
tableName: 'businesses',
|
||||||
|
columns: ['id', 'userId', 'type', 'title', 'description', 'city', 'state', 'price', 'favoritesForUser', 'draft', 'listingsCategory', 'realEstateIncluded', 'leasedLocation', 'franchiseResale', 'salesRevenue', 'cashFlow', 'supportAndTraining', 'employees', 'established', 'internalListingNumber', 'reasonForSale', 'brokerLicencing', 'internals', 'created', 'updated', 'visits', 'lastVisit'],
|
||||||
|
requiredForInsert: [],
|
||||||
|
primaryKey: 'id',
|
||||||
|
foreignKeys: { userId: { table: 'users', column: 'id', $type: null as unknown /* users */ }, },
|
||||||
|
$type: null as unknown as Businesses,
|
||||||
|
$input: null as unknown as BusinessesInput
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
|
||||||
|
export interface TableTypes {
|
||||||
|
businesses: {
|
||||||
|
select: Businesses;
|
||||||
|
input: BusinessesInput;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tables = {
|
||||||
|
businesses,
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "ng serve & http-server ../bizmatch-server",
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"build.dev": "ng build --configuration dev",
|
"build.dev": "ng build --configuration dev",
|
||||||
"watch": "ng build --watch --configuration development",
|
"watch": "ng build --watch --configuration development",
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/jasmine": "~5.1.4",
|
"@types/jasmine": "~5.1.4",
|
||||||
"@types/node": "^20.11.20",
|
"@types/node": "^20.11.20",
|
||||||
|
"http-server": "^14.1.1",
|
||||||
"jasmine-core": "~5.1.2",
|
"jasmine-core": "~5.1.2",
|
||||||
"karma": "~6.4.2",
|
"karma": "~6.4.2",
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
{
|
{
|
||||||
"/api": {
|
"/api": {
|
||||||
"target": "http://localhost:3000",
|
"target": "http://localhost:3000",
|
||||||
"secure": false
|
"secure": false
|
||||||
}
|
},
|
||||||
|
"/pictures": {
|
||||||
|
"target": "http://localhost:8080",
|
||||||
|
"secure": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ import { KeycloakEventType } from './models/keycloak-event';
|
|||||||
import { createGenericObject } from './utils/utils';
|
import { createGenericObject } from './utils/utils';
|
||||||
import onChange from 'on-change';
|
import onChange from 'on-change';
|
||||||
import { UserService } from './services/user.service';
|
import { UserService } from './services/user.service';
|
||||||
import {ListingCriteria, User} from '../../../common-models/src/main.model'
|
import {ListingCriteria} from '../../../bizmatch-server/src/models/main.model'
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -24,7 +24,6 @@ import {ListingCriteria, User} from '../../../common-models/src/main.model'
|
|||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'bizmatch';
|
title = 'bizmatch';
|
||||||
actualRoute ='';
|
actualRoute ='';
|
||||||
user:User;
|
|
||||||
listingCriteria:ListingCriteria = onChange(createGenericObject<ListingCriteria>(),(path, value, previousValue, applyData)=>{
|
listingCriteria:ListingCriteria = onChange(createGenericObject<ListingCriteria>(),(path, value, previousValue, applyData)=>{
|
||||||
sessionStorage.setItem('criteria',JSON.stringify(value));
|
sessionStorage.setItem('criteria',JSON.stringify(value));
|
||||||
});
|
});
|
||||||
@@ -41,7 +40,6 @@ export class AppComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
ngOnInit(){
|
ngOnInit(){
|
||||||
this.user = this.userService.getUser();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,84 +1,119 @@
|
|||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
import { ListingsComponent } from './pages/listings/listings.component';
|
|
||||||
import { HomeComponent } from './pages/home/home.component';
|
|
||||||
import { DetailsListingComponent } from './pages/details/details-listing/details-listing.component';
|
|
||||||
import { AccountComponent } from './pages/subscription/account/account.component';
|
|
||||||
import { EditListingComponent } from './pages/subscription/edit-listing/edit-listing.component';
|
|
||||||
import { MyListingComponent } from './pages/subscription/my-listing/my-listing.component';
|
|
||||||
import { FavoritesComponent } from './pages/subscription/favorites/favorites.component';
|
|
||||||
import { EmailUsComponent } from './pages/subscription/email-us/email-us.component';
|
|
||||||
import { authGuard } from './guards/auth.guard';
|
|
||||||
import { PricingComponent } from './pages/pricing/pricing.component';
|
|
||||||
import { LogoutComponent } from './components/logout/logout.component';
|
import { LogoutComponent } from './components/logout/logout.component';
|
||||||
|
import { authGuard } from './guards/auth.guard';
|
||||||
|
import { DetailsBusinessListingComponent } from './pages/details/details-business-listing/details-business-listing.component';
|
||||||
|
import { DetailsCommercialPropertyListingComponent } from './pages/details/details-commercial-property-listing/details-commercial-property-listing.component';
|
||||||
import { DetailsUserComponent } from './pages/details/details-user/details-user.component';
|
import { DetailsUserComponent } from './pages/details/details-user/details-user.component';
|
||||||
|
import { HomeComponent } from './pages/home/home.component';
|
||||||
|
import { BrokerListingsComponent } from './pages/listings/broker-listings/broker-listings.component';
|
||||||
|
import { BusinessListingsComponent } from './pages/listings/business-listings/business-listings.component';
|
||||||
|
import { CommercialPropertyListingsComponent } from './pages/listings/commercial-property-listings/commercial-property-listings.component';
|
||||||
|
import { PricingComponent } from './pages/pricing/pricing.component';
|
||||||
|
import { AccountComponent } from './pages/subscription/account/account.component';
|
||||||
|
import { EditBusinessListingComponent } from './pages/subscription/edit-business-listing/edit-business-listing.component';
|
||||||
|
import { EditCommercialPropertyListingComponent } from './pages/subscription/edit-commercial-property-listing/edit-commercial-property-listing.component';
|
||||||
|
import { EmailUsComponent } from './pages/subscription/email-us/email-us.component';
|
||||||
|
import { FavoritesComponent } from './pages/subscription/favorites/favorites.component';
|
||||||
|
import { MyListingComponent } from './pages/subscription/my-listing/my-listing.component';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'listings/:type',
|
path: 'businessListings',
|
||||||
component: ListingsComponent,
|
component: BusinessListingsComponent,
|
||||||
},
|
runGuardsAndResolvers: 'always',
|
||||||
// Umleitung von /listing zu /listing/business
|
},
|
||||||
{
|
{
|
||||||
path: 'listings',
|
path: 'commercialPropertyListings',
|
||||||
pathMatch: 'full',
|
component: CommercialPropertyListingsComponent,
|
||||||
redirectTo: 'listings/business',
|
runGuardsAndResolvers: 'always',
|
||||||
runGuardsAndResolvers:'always'
|
},
|
||||||
},
|
{
|
||||||
{
|
path: 'brokerListings',
|
||||||
path: 'home',
|
component: BrokerListingsComponent,
|
||||||
component: HomeComponent,
|
runGuardsAndResolvers: 'always',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'details-listing/:type/:id',
|
path: 'home',
|
||||||
component: DetailsListingComponent,
|
component: HomeComponent,
|
||||||
},
|
},
|
||||||
{
|
// #########
|
||||||
path: 'details-listing/:type/:id',
|
// Listings Details
|
||||||
component: DetailsListingComponent,
|
{
|
||||||
},
|
path: 'details-business-listing/:id',
|
||||||
{
|
component: DetailsBusinessListingComponent,
|
||||||
path: 'details-user/:id',
|
},
|
||||||
component: DetailsUserComponent,
|
{
|
||||||
},
|
path: 'details-commercial-property-listing/:id',
|
||||||
{
|
component: DetailsCommercialPropertyListingComponent,
|
||||||
path: 'account/:id',
|
},
|
||||||
component: AccountComponent,
|
// #########
|
||||||
canActivate: [authGuard],
|
// User Details
|
||||||
},
|
{
|
||||||
{
|
path: 'details-user/:id',
|
||||||
path: 'editListing/:id',
|
component: DetailsUserComponent,
|
||||||
component: EditListingComponent,
|
},
|
||||||
canActivate: [authGuard],
|
// #########
|
||||||
},
|
// User edit
|
||||||
{
|
{
|
||||||
path: 'createListing',
|
path: 'account',
|
||||||
component: EditListingComponent,
|
component: AccountComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [authGuard],
|
||||||
},
|
},
|
||||||
{
|
// #########
|
||||||
path: 'myListings',
|
// Create, Update Listings
|
||||||
component: MyListingComponent,
|
{
|
||||||
canActivate: [authGuard],
|
path: 'editBusinessListing/:id',
|
||||||
},
|
component: EditBusinessListingComponent,
|
||||||
{
|
canActivate: [authGuard],
|
||||||
path: 'myFavorites',
|
},
|
||||||
component: FavoritesComponent,
|
{
|
||||||
canActivate: [authGuard],
|
path: 'createBusinessListing',
|
||||||
},
|
component: EditBusinessListingComponent,
|
||||||
{
|
canActivate: [authGuard],
|
||||||
path: 'emailUs',
|
},
|
||||||
component: EmailUsComponent,
|
{
|
||||||
canActivate: [authGuard],
|
path: 'editCommercialPropertyListing/:id',
|
||||||
},
|
component: EditCommercialPropertyListingComponent,
|
||||||
{
|
canActivate: [authGuard],
|
||||||
path: 'logout',
|
},
|
||||||
component: LogoutComponent,
|
{
|
||||||
canActivate: [authGuard],
|
path: 'createCommercialPropertyListing',
|
||||||
},
|
component: EditCommercialPropertyListingComponent,
|
||||||
{
|
canActivate: [authGuard],
|
||||||
path: 'pricing',
|
},
|
||||||
component: PricingComponent
|
// #########
|
||||||
},
|
// My Listings
|
||||||
{ path: '**', redirectTo: 'home' },
|
{
|
||||||
|
path: 'myListings',
|
||||||
|
component: MyListingComponent,
|
||||||
|
canActivate: [authGuard],
|
||||||
|
},
|
||||||
|
// #########
|
||||||
|
// My Favorites
|
||||||
|
{
|
||||||
|
path: 'myFavorites',
|
||||||
|
component: FavoritesComponent,
|
||||||
|
canActivate: [authGuard],
|
||||||
|
},
|
||||||
|
// #########
|
||||||
|
// EMAil Us
|
||||||
|
{
|
||||||
|
path: 'emailUs',
|
||||||
|
component: EmailUsComponent,
|
||||||
|
canActivate: [authGuard],
|
||||||
|
},
|
||||||
|
// #########
|
||||||
|
// Logout
|
||||||
|
{
|
||||||
|
path: 'logout',
|
||||||
|
component: LogoutComponent,
|
||||||
|
canActivate: [authGuard],
|
||||||
|
},
|
||||||
|
// #########
|
||||||
|
// Pricing
|
||||||
|
{
|
||||||
|
path: 'pricing',
|
||||||
|
component: PricingComponent,
|
||||||
|
},
|
||||||
|
{ path: '**', redirectTo: 'home' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
<div class="surface-0 px-4 py-4 md:px-6 lg:px-8">
|
<div class="surface-0 px-4 py-4 md:px-6 lg:px-8">
|
||||||
<div class="surface-0">
|
<div class="surface-0">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-3 md:mb-0 mb-3">
|
<div class="col-12 md:col-3 md:mb-0 mb-3">
|
||||||
<img src="assets/images/header-logo.png" alt="footer sections" height="30" class="mr-3">
|
<img src="assets/images/header-logo.png" alt="footer sections" height="30" class="mr-3" />
|
||||||
<div class="text-500">© 2024 Bizmatch All rights reserved.</div>
|
<div class="text-500">© 2024 Bizmatch All rights reserved.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-3">
|
<div class="col-12 md:col-3">
|
||||||
<div class="text-black mb-4 flex flex-wrap" style="max-width: 290px">BizMatch, Inc., 1001 Blucher Street, Corpus Christi, Texas 78401</div>
|
<div class="text-black mb-4 flex flex-wrap" style="max-width: 290px">BizMatch, Inc., 1001 Blucher Street, Corpus Christi, Texas 78401</div>
|
||||||
<div class="text-black mb-3"><i class="text-white pi pi-phone surface-800 border-round p-1 mr-2"></i>1-800-840-6025</div>
|
<div class="text-black mb-3"><i class="text-white pi pi-phone surface-800 border-round p-1 mr-2"></i>1-800-840-6025</div>
|
||||||
<div class="text-black mb-3"><i class="text-white pi pi-inbox surface-800 border-round p-1 mr-2"></i>bizmatch@biz-match.com</div>
|
<div class="text-black mb-3"><i class="text-white pi pi-inbox surface-800 border-round p-1 mr-2"></i>bizmatch@biz-match.com</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-3 text-500">
|
<div class="col-12 md:col-3 text-500">
|
||||||
<div class="text-black font-bold line-height-3 mb-3">Legal</div>
|
<div class="text-black font-bold line-height-3 mb-3">Legal</div>
|
||||||
<a class="line-height-3 block cursor-pointer mb-2">Terms of use</a>
|
<a class="line-height-3 block cursor-pointer mb-2">Terms of use</a>
|
||||||
<a class="line-height-3 block cursor-pointer mb-2">Privacy statement</a>
|
<a class="line-height-3 block cursor-pointer mb-2">Privacy statement</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-3 text-500">
|
<div class="col-12 md:col-3 text-500">
|
||||||
<div class="text-black font-bold line-height-3 mb-3">Actions</div>
|
<div class="text-black font-bold line-height-3 mb-3">Actions</div>
|
||||||
<a *ngIf="!userService.isLoggedIn()" (click)="login()" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline">Login</a>
|
<a *ngIf="!userService.isLoggedIn()" (click)="login()" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline">Login</a>
|
||||||
<a *ngIf="userService.isLoggedIn()" [routerLink]="['/account',userService.getUser()?.id]" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline">Account</a>
|
<a *ngIf="userService.isLoggedIn()" [routerLink]="['/account']" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline">Account</a>
|
||||||
<a *ngIf="userService.isLoggedIn()" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline" (click)="userService.logout()">Log Out</a>
|
<a *ngIf="userService.isLoggedIn()" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline" (click)="userService.logout()">Log Out</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,119 +1,115 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { faUserGear } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { MenuItem } from 'primeng/api';
|
import { MenuItem } from 'primeng/api';
|
||||||
import { ButtonModule } from 'primeng/button';
|
import { ButtonModule } from 'primeng/button';
|
||||||
import { MenubarModule } from 'primeng/menubar';
|
import { MenubarModule } from 'primeng/menubar';
|
||||||
import { OverlayPanelModule } from 'primeng/overlaypanel';
|
import { OverlayPanelModule } from 'primeng/overlaypanel';
|
||||||
import { environment } from '../../../environments/environment';
|
|
||||||
import { UserService } from '../../services/user.service';
|
|
||||||
import { TabMenuModule } from 'primeng/tabmenu';
|
import { TabMenuModule } from 'primeng/tabmenu';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { faUserGear } from '@fortawesome/free-solid-svg-icons';
|
import { User } from '../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { Router } from '@angular/router';
|
import { environment } from '../../../environments/environment';
|
||||||
import { User } from '../../../../../common-models/src/main.model';
|
import { UserService } from '../../services/user.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'header',
|
selector: 'header',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, MenubarModule, ButtonModule, OverlayPanelModule, TabMenuModule ],
|
imports: [CommonModule, MenubarModule, ButtonModule, OverlayPanelModule, TabMenuModule],
|
||||||
templateUrl: './header.component.html',
|
templateUrl: './header.component.html',
|
||||||
styleUrl: './header.component.scss'
|
styleUrl: './header.component.scss',
|
||||||
})
|
})
|
||||||
export class HeaderComponent {
|
export class HeaderComponent {
|
||||||
public buildVersion = environment.buildVersion;
|
public buildVersion = environment.buildVersion;
|
||||||
user$:Observable<User>
|
user$: Observable<User>;
|
||||||
user:User;
|
user: User;
|
||||||
public tabItems: MenuItem[];
|
public tabItems: MenuItem[];
|
||||||
public menuItems: MenuItem[];
|
public menuItems: MenuItem[];
|
||||||
activeItem
|
activeItem;
|
||||||
faUserGear=faUserGear
|
faUserGear = faUserGear;
|
||||||
constructor(public userService: UserService,private router: Router) {
|
constructor(public userService: UserService, private router: Router) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(){
|
ngOnInit() {
|
||||||
this.user$=this.userService.getUserObservable();
|
this.user$ = this.userService.getUserObservable();
|
||||||
this.user$.subscribe(u=>{
|
this.user$.subscribe(u => {
|
||||||
this.user=u;
|
this.user = u;
|
||||||
this.menuItems = [
|
this.menuItems = [
|
||||||
{
|
{
|
||||||
label: 'User Actions',
|
label: 'User Actions',
|
||||||
icon: 'fas fa-cog',
|
icon: 'fas fa-cog',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Account',
|
label: 'Account',
|
||||||
icon: 'pi pi-user',
|
icon: 'pi pi-user',
|
||||||
routerLink: `/account/${this.user.id}`,
|
routerLink: `/account`,
|
||||||
visible: this.isUserLogedIn()
|
visible: this.isUserLogedIn(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Create Listing',
|
label: 'Create Listing',
|
||||||
icon: 'pi pi-plus-circle',
|
icon: 'pi pi-plus-circle',
|
||||||
routerLink: "/createListing",
|
routerLink: '/createBusinessListing',
|
||||||
visible: this.isUserLogedIn()
|
visible: this.isUserLogedIn(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'My Listings',
|
label: 'My Listings',
|
||||||
icon: 'pi pi-list',
|
icon: 'pi pi-list',
|
||||||
routerLink:"/myListings",
|
routerLink: '/myListings',
|
||||||
visible: this.isUserLogedIn()
|
visible: this.isUserLogedIn(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'My Favorites',
|
label: 'My Favorites',
|
||||||
icon: 'pi pi-star',
|
icon: 'pi pi-star',
|
||||||
routerLink:"/myFavorites",
|
routerLink: '/myFavorites',
|
||||||
visible: this.isUserLogedIn()
|
visible: this.isUserLogedIn(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'EMail Us',
|
label: 'EMail Us',
|
||||||
icon: 'fa-regular fa-envelope',
|
icon: 'fa-regular fa-envelope',
|
||||||
routerLink:"/emailUs",
|
routerLink: '/emailUs',
|
||||||
visible: this.isUserLogedIn()
|
visible: this.isUserLogedIn(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Logout',
|
label: 'Logout',
|
||||||
icon: 'fa-solid fa-right-from-bracket',
|
icon: 'fa-solid fa-right-from-bracket',
|
||||||
routerLink:"/logout",
|
routerLink: '/logout',
|
||||||
visible: this.isUserLogedIn()
|
visible: this.isUserLogedIn(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Login',
|
label: 'Login',
|
||||||
icon: 'fa-solid fa-right-from-bracket',
|
icon: 'fa-solid fa-right-from-bracket',
|
||||||
//routerLink:"/account",
|
command: () => this.login(),
|
||||||
command: () => this.login(),
|
visible: !this.isUserLogedIn(),
|
||||||
visible: !this.isUserLogedIn()
|
},
|
||||||
},
|
],
|
||||||
]
|
},
|
||||||
}
|
];
|
||||||
]
|
});
|
||||||
});
|
this.tabItems = [
|
||||||
this.tabItems = [
|
{
|
||||||
{
|
label: 'Businesses for Sale',
|
||||||
label: 'Businesses for Sale',
|
routerLink: '/businessListings',
|
||||||
routerLink: '/listings/business',
|
state: {},
|
||||||
fragment:''
|
},
|
||||||
},
|
{
|
||||||
{
|
label: 'Commercial Property',
|
||||||
label: 'Professionals/Brokers Directory',
|
routerLink: '/commercialPropertyListings',
|
||||||
routerLink: '/listings/professionals_brokers',
|
state: {},
|
||||||
fragment:''
|
},
|
||||||
},
|
{
|
||||||
{
|
label: 'Professionals/Brokers Directory',
|
||||||
label: 'Commercial Property',
|
routerLink: '/brokerListings',
|
||||||
routerLink: '/listings/commercialProperty',
|
state: {},
|
||||||
fragment:''
|
},
|
||||||
}
|
];
|
||||||
];
|
|
||||||
|
|
||||||
this.activeItem=this.tabItems[0];
|
this.activeItem = this.tabItems[0];
|
||||||
}
|
}
|
||||||
navigateWithState(dest: string, state: any) {
|
navigateWithState(dest: string, state: any) {
|
||||||
this.router.navigate([dest], { state: state });
|
this.router.navigate([dest], { state: state });
|
||||||
}
|
}
|
||||||
isUserLogedIn(){
|
isUserLogedIn() {
|
||||||
return this.userService?.isLoggedIn();
|
return this.userService?.isLoggedIn();
|
||||||
}
|
}
|
||||||
login(){
|
login() {
|
||||||
this.userService.login(window.location.href);
|
this.userService.login(window.location.href);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import { HttpEventType } from '@angular/common/http';
|
|||||||
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
|
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
|
||||||
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
|
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { KeyValueRatio, User } from '../../../../../common-models/src/main.model';
|
import { User } from '../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { SharedModule } from '../../shared/shared/shared.module';
|
import { SharedModule } from '../../shared/shared/shared.module';
|
||||||
import { SelectButtonModule } from 'primeng/selectbutton';
|
import { SelectButtonModule } from 'primeng/selectbutton';
|
||||||
|
import { KeyValueRatio } from '../../../../../bizmatch-server/src/models/main.model';
|
||||||
export const stateOptions:KeyValueRatio[]=[
|
export const stateOptions:KeyValueRatio[]=[
|
||||||
{label:'16/9',value:16/9},
|
{label:'16/9',value:16/9},
|
||||||
{label:'1/1',value:1},
|
{label:'1/1',value:1},
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<div class="surface-ground h-full">
|
||||||
|
<div class="px-6 py-5">
|
||||||
|
<div class="surface-card p-4 shadow-2 border-round">
|
||||||
|
<div class="flex justify-content-between align-items-center align-content-center">
|
||||||
|
<div class="font-medium text-3xl text-900 mb-3">{{ listing?.title }}</div>
|
||||||
|
<!-- <button pButton pRipple type="button" label="Go back to listings" icon="pi pi-user-plus" class="mr-3 p-button-rounded"></button> -->
|
||||||
|
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="back()"></p-button>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="text-500 mb-5">Egestas sed tempus urna et pharetra pharetra massa massa ultricies.</div> -->
|
||||||
|
@if(listing){
|
||||||
|
<div class="grid">
|
||||||
|
<div class="col-12 md:col-6">
|
||||||
|
<ul class="list-none p-0 m-0 border-top-1 border-300">
|
||||||
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
|
<div class="text-500 w-full md:w-2 font-medium flex align-self-start">Description</div>
|
||||||
|
<div class="text-900 w-full md:w-10 line-height-3" [innerHTML]="description"></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">Category</div>
|
||||||
|
<div class="text-900 w-full md:w-10">
|
||||||
|
<p-chip [label]="selectOptions.getBusiness(listing.type)"></p-chip>
|
||||||
|
</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">Located in</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>
|
||||||
|
<div class="text-900 w-full md:w-10">{{ listing.price | currency }}</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">Real Estate Included</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{ listing.realEstateIncluded ? 'Yes' : 'No' }}</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">Sales revenue</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{ listing.salesRevenue | currency }}</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">Cash flow</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{ listing.cashFlow | currency }}</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">Employees</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{ listing.employees }}</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">Broker licensing</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{ listing.brokerLicencing }}</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
@if(listing && user && (user.id===listing?.userId || isAdmin())){
|
||||||
|
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/editBusinessListing', listing.id]"></button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="col-12 md:col-6">
|
||||||
|
<div class="surface-card p-4 border-round p-fluid">
|
||||||
|
<div class="font-medium text-xl text-primary text-900 mb-3">Contact The Author of This Listing</div>
|
||||||
|
<div class="font-italic text-sm text-900 mb-5">Please Include your contact info below:</div>
|
||||||
|
<div class="grid formgrid p-fluid">
|
||||||
|
<div class="field mb-4 col-12 md:col-6">
|
||||||
|
<label for="name" class="font-medium text-900">Your Name</label>
|
||||||
|
<input id="name" type="text" pInputText [(ngModel)]="mailinfo.sender.name" />
|
||||||
|
</div>
|
||||||
|
<div class="field mb-4 col-12 md:col-6">
|
||||||
|
<label for="email" class="font-medium text-900">Your Email</label>
|
||||||
|
<input id="email" type="text" pInputText [(ngModel)]="mailinfo.sender.email" />
|
||||||
|
</div>
|
||||||
|
<div class="field mb-4 col-12 md:col-6">
|
||||||
|
<label for="phoneNumber" class="font-medium text-900">Phone Number</label>
|
||||||
|
<input id="phoneNumber" type="text" pInputText [(ngModel)]="mailinfo.sender.phoneNumber" />
|
||||||
|
</div>
|
||||||
|
<div class="field mb-4 col-12 md:col-6">
|
||||||
|
<label for="state" class="font-medium text-900">Country/State</label>
|
||||||
|
<input id="state" type="text" pInputText [(ngModel)]="mailinfo.sender.state" />
|
||||||
|
</div>
|
||||||
|
<div class="surface-border border-top-1 opacity-50 mb-4 col-12"></div>
|
||||||
|
<div class="field mb-4 col-12">
|
||||||
|
<label for="notes" class="font-medium text-900">Questions/Comments</label>
|
||||||
|
<textarea id="notes" pInputTextarea [autoResize]="true" [rows]="5" [(ngModel)]="mailinfo.sender.comments"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="surface-border border-top-1 opacity-50 mb-4 col-12"></div>
|
||||||
|
</div>
|
||||||
|
<button pButton pRipple label="Submit" icon="pi pi-file" class="w-auto" (click)="mail()"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { Location } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import onChange from 'on-change';
|
||||||
|
import { MessageService } from 'primeng/api';
|
||||||
|
import { GalleriaModule } from 'primeng/galleria';
|
||||||
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
|
import { ListingCriteria, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
|
import { MailService } from '../../../services/mail.service';
|
||||||
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
|
import { UserService } from '../../../services/user.service';
|
||||||
|
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||||
|
import { getCriteriaStateObject, getSessionStorageHandler } from '../../../utils/utils';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-details-business-listing',
|
||||||
|
standalone: true,
|
||||||
|
imports: [SharedModule, GalleriaModule],
|
||||||
|
providers: [MessageService],
|
||||||
|
templateUrl: './details-business-listing.component.html',
|
||||||
|
styleUrl: './details-business-listing.component.scss',
|
||||||
|
})
|
||||||
|
export class DetailsBusinessListingComponent {
|
||||||
|
// listings: Array<BusinessListing>;
|
||||||
|
responsiveOptions = [
|
||||||
|
{
|
||||||
|
breakpoint: '1199px',
|
||||||
|
numVisible: 1,
|
||||||
|
numScroll: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '991px',
|
||||||
|
numVisible: 2,
|
||||||
|
numScroll: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '767px',
|
||||||
|
numVisible: 1,
|
||||||
|
numScroll: 1,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
||||||
|
listing: BusinessListing;
|
||||||
|
criteria: ListingCriteria;
|
||||||
|
mailinfo: MailInfo;
|
||||||
|
environment = environment;
|
||||||
|
user: User;
|
||||||
|
description: SafeHtml;
|
||||||
|
constructor(
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private listingsService: ListingsService,
|
||||||
|
private router: Router,
|
||||||
|
private userService: UserService,
|
||||||
|
public selectOptions: SelectOptionsService,
|
||||||
|
private mailService: MailService,
|
||||||
|
private messageService: MessageService,
|
||||||
|
private sanitizer: DomSanitizer,
|
||||||
|
private location: Location,
|
||||||
|
) {
|
||||||
|
this.mailinfo = { sender: {}, userId: '', email: '' };
|
||||||
|
this.userService.getUserObservable().subscribe(user => {
|
||||||
|
this.user = user;
|
||||||
|
});
|
||||||
|
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'business'));
|
||||||
|
this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description);
|
||||||
|
}
|
||||||
|
back() {
|
||||||
|
this.location.back();
|
||||||
|
}
|
||||||
|
isAdmin() {
|
||||||
|
return this.userService.hasAdminRole();
|
||||||
|
}
|
||||||
|
async mail() {
|
||||||
|
this.mailinfo.email = this.user.email;
|
||||||
|
this.mailinfo.userId = this.listing.userId;
|
||||||
|
this.mailinfo.listing = this.listing;
|
||||||
|
await this.mailService.mail(this.mailinfo);
|
||||||
|
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Your message has been sent to the creator of the listing', life: 3000 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<div class="surface-ground h-full">
|
||||||
|
<div class="px-6 py-5">
|
||||||
|
<div class="surface-card p-4 shadow-2 border-round">
|
||||||
|
<div class="flex justify-content-between align-items-center align-content-center">
|
||||||
|
<div class="font-medium text-3xl text-900 mb-3">{{ listing?.title }}</div>
|
||||||
|
<!-- <button pButton pRipple type="button" label="Go back to listings" icon="pi pi-user-plus" class="mr-3 p-button-rounded"></button> -->
|
||||||
|
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="back()"></p-button>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="text-500 mb-5">Egestas sed tempus urna et pharetra pharetra massa massa ultricies.</div> -->
|
||||||
|
@if(listing){
|
||||||
|
<div class="grid">
|
||||||
|
<div class="col-12 md:col-6">
|
||||||
|
<ul class="list-none p-0 m-0 border-top-1 border-300">
|
||||||
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
|
<div class="text-500 w-full md:w-2 font-medium flex align-self-start">Description</div>
|
||||||
|
<div class="text-900 w-full md:w-10 line-height-3" [innerHTML]="description"></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">Property Category</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{ selectOptions.getCommercialProperty(listing.type) }}</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">Located in</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">City</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{ listing.city }}</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">Zip Code</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{ listing.zipCode }}</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">County</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{ listing.county }}</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">Asking Price:</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{ listing.price | currency }}</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p-galleria [value]="propertyImages" [showIndicators]="true" [showThumbnails]="false" [responsiveOptions]="responsiveOptions" [containerStyle]="{ 'max-width': '640px' }" [numVisible]="5">
|
||||||
|
<ng-template pTemplate="item" let-item>
|
||||||
|
<img src="pictures/property/{{ listing.imagePath }}/{{ item }}" style="width: 100%" />
|
||||||
|
</ng-template>
|
||||||
|
</p-galleria>
|
||||||
|
@if(listing && user && (user.id===listing?.userId || isAdmin())){
|
||||||
|
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/editCommercialPropertyListing', listing.id]"></button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (mailinfo){
|
||||||
|
<div class="col-12 md:col-6">
|
||||||
|
<div class="surface-card p-4 border-round p-fluid">
|
||||||
|
<div class="font-medium text-xl text-primary text-900 mb-3">Contact The Author of This Listing</div>
|
||||||
|
<div class="font-italic text-sm text-900 mb-5">Please Include your contact info below:</div>
|
||||||
|
<div class="grid formgrid p-fluid">
|
||||||
|
<div class="field mb-4 col-12 md:col-6">
|
||||||
|
<label for="name" class="font-medium text-900">Your Name</label>
|
||||||
|
<input id="name" type="text" pInputText [(ngModel)]="mailinfo.sender.name" />
|
||||||
|
</div>
|
||||||
|
<div class="field mb-4 col-12 md:col-6">
|
||||||
|
<label for="email" class="font-medium text-900">Your Email</label>
|
||||||
|
<input id="email" type="text" pInputText [(ngModel)]="mailinfo.sender.email" />
|
||||||
|
</div>
|
||||||
|
<div class="field mb-4 col-12 md:col-6">
|
||||||
|
<label for="phoneNumber" class="font-medium text-900">Phone Number</label>
|
||||||
|
<input id="phoneNumber" type="text" pInputText [(ngModel)]="mailinfo.sender.phoneNumber" />
|
||||||
|
</div>
|
||||||
|
<div class="field mb-4 col-12 md:col-6">
|
||||||
|
<label for="state" class="font-medium text-900">Country/State</label>
|
||||||
|
<input id="state" type="text" pInputText [(ngModel)]="mailinfo.sender.state" />
|
||||||
|
</div>
|
||||||
|
<div class="surface-border border-top-1 opacity-50 mb-4 col-12"></div>
|
||||||
|
<div class="field mb-4 col-12">
|
||||||
|
<label for="notes" class="font-medium text-900">Questions/Comments</label>
|
||||||
|
<textarea id="notes" pInputTextarea [autoResize]="true" [rows]="5" [(ngModel)]="mailinfo.sender.comments"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="surface-border border-top-1 opacity-50 mb-4 col-12"></div>
|
||||||
|
</div>
|
||||||
|
<button pButton pRipple label="Submit" icon="pi pi-file" class="w-auto" (click)="mail()"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,94 +1,89 @@
|
|||||||
|
import { Location } from '@angular/common';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ButtonModule } from 'primeng/button';
|
|
||||||
import { CheckboxModule } from 'primeng/checkbox';
|
|
||||||
import { InputTextModule } from 'primeng/inputtext';
|
|
||||||
import { StyleClassModule } from 'primeng/styleclass';
|
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
|
||||||
import { DropdownModule } from 'primeng/dropdown';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { ToggleButtonModule } from 'primeng/togglebutton';
|
|
||||||
import { TagModule } from 'primeng/tag';
|
|
||||||
import data from '../../../../assets/data/listings.json';
|
|
||||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
|
||||||
import { InputTextareaModule } from 'primeng/inputtextarea';
|
|
||||||
import { ChipModule } from 'primeng/chip';
|
|
||||||
import { lastValueFrom } from 'rxjs';
|
|
||||||
import { ListingsService } from '../../../services/listings.service';
|
|
||||||
import { UserService } from '../../../services/user.service';
|
|
||||||
import onChange from 'on-change';
|
|
||||||
import { createGenericObject, getCriteriaStateObject, getSessionStorageHandler } from '../../../utils/utils';
|
|
||||||
import { ImageProperty, ListingCriteria, ListingType, MailInfo, User } from '../../../../../../common-models/src/main.model';
|
|
||||||
import { MailService } from '../../../services/mail.service';
|
|
||||||
import { MessageService } from 'primeng/api';
|
|
||||||
import { SharedModule } from '../../../shared/shared/shared.module';
|
|
||||||
import { GalleriaModule } from 'primeng/galleria';
|
|
||||||
import { environment } from '../../../../environments/environment';
|
|
||||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import onChange from 'on-change';
|
||||||
|
import { MessageService } from 'primeng/api';
|
||||||
|
import { GalleriaModule } from 'primeng/galleria';
|
||||||
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
import { CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
|
import { ListingCriteria, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
|
import { MailService } from '../../../services/mail.service';
|
||||||
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
|
import { UserService } from '../../../services/user.service';
|
||||||
|
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||||
|
import { getCriteriaStateObject, getSessionStorageHandler } from '../../../utils/utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-details-listing',
|
selector: 'app-details-commercial-property-listing',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SharedModule, GalleriaModule],
|
imports: [SharedModule, GalleriaModule],
|
||||||
providers: [MessageService],
|
providers: [MessageService],
|
||||||
templateUrl: './details-listing.component.html',
|
templateUrl: './details-commercial-property-listing.component.html',
|
||||||
styleUrl: './details-listing.component.scss'
|
styleUrl: './details-commercial-property-listing.component.scss',
|
||||||
})
|
})
|
||||||
export class DetailsListingComponent {
|
export class DetailsCommercialPropertyListingComponent {
|
||||||
// listings: Array<BusinessListing>;
|
// listings: Array<BusinessListing>;
|
||||||
responsiveOptions = [
|
responsiveOptions = [
|
||||||
{
|
{
|
||||||
breakpoint: '1199px',
|
breakpoint: '1199px',
|
||||||
numVisible: 1,
|
numVisible: 1,
|
||||||
numScroll: 1
|
numScroll: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
breakpoint: '991px',
|
breakpoint: '991px',
|
||||||
numVisible: 2,
|
numVisible: 2,
|
||||||
numScroll: 1
|
numScroll: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
breakpoint: '767px',
|
breakpoint: '767px',
|
||||||
numVisible: 1,
|
numVisible: 1,
|
||||||
numScroll: 1
|
numScroll: 1,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
||||||
private type: 'business'|'commercialProperty' | undefined = this.activatedRoute.snapshot.params['type'] as 'business'|'commercialProperty' | undefined;
|
listing: CommercialPropertyListing;
|
||||||
listing: ListingType;
|
criteria: ListingCriteria;
|
||||||
criteria: ListingCriteria
|
|
||||||
mailinfo: MailInfo;
|
mailinfo: MailInfo;
|
||||||
propertyImages: ImageProperty[] = []
|
propertyImages: string[] = [];
|
||||||
environment = environment;
|
environment = environment;
|
||||||
user:User
|
user: User;
|
||||||
description:SafeHtml;
|
description: SafeHtml;
|
||||||
constructor(private activatedRoute: ActivatedRoute,
|
constructor(
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
private listingsService: ListingsService,
|
private listingsService: ListingsService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
public selectOptions: SelectOptionsService,
|
public selectOptions: SelectOptionsService,
|
||||||
private mailService: MailService,
|
private mailService: MailService,
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
private sanitizer: DomSanitizer) {
|
private sanitizer: DomSanitizer,
|
||||||
|
private location: Location,
|
||||||
|
) {
|
||||||
|
this.mailinfo = { sender: {}, userId: '', email: '' };
|
||||||
this.userService.getUserObservable().subscribe(user => {
|
this.userService.getUserObservable().subscribe(user => {
|
||||||
this.user = user
|
this.user = user;
|
||||||
});
|
});
|
||||||
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
||||||
this.mailinfo = { sender: {}, userId: '' }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, this.type));
|
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty'));
|
||||||
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
|
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
|
||||||
this.description=this.sanitizer.bypassSecurityTrustHtml(this.listing.description);
|
this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description);
|
||||||
}
|
}
|
||||||
back() {
|
back() {
|
||||||
this.router.navigate(['listings', this.criteria.listingsCategory])
|
this.location.back();
|
||||||
}
|
}
|
||||||
isAdmin() {
|
isAdmin() {
|
||||||
return this.userService.hasAdminRole();
|
return this.userService.hasAdminRole();
|
||||||
}
|
}
|
||||||
async mail() {
|
async mail() {
|
||||||
|
this.mailinfo.email = this.user.email;
|
||||||
this.mailinfo.userId = this.listing.userId;
|
this.mailinfo.userId = this.listing.userId;
|
||||||
|
this.mailinfo.listing = this.listing;
|
||||||
await this.mailService.mail(this.mailinfo);
|
await this.mailService.mail(this.mailinfo);
|
||||||
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Your message has been sent to the creator of the listing', life: 3000 });
|
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Your message has been sent to the creator of the listing', life: 3000 });
|
||||||
}
|
}
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
<div class="surface-ground h-full">
|
|
||||||
<div class="px-6 py-5">
|
|
||||||
<div class="surface-card p-4 shadow-2 border-round">
|
|
||||||
<div class="flex justify-content-between align-items-center align-content-center">
|
|
||||||
<div class="font-medium text-3xl text-900 mb-3">{{listing?.title}}</div>
|
|
||||||
<!-- <button pButton pRipple type="button" label="Go back to listings" icon="pi pi-user-plus" class="mr-3 p-button-rounded"></button> -->
|
|
||||||
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="back()"></p-button>
|
|
||||||
</div>
|
|
||||||
<!-- <div class="text-500 mb-5">Egestas sed tempus urna et pharetra pharetra massa massa ultricies.</div> -->
|
|
||||||
<div class="grid">
|
|
||||||
<div class="col-12 md:col-6">
|
|
||||||
<ul class="list-none p-0 m-0 border-top-1 border-300">
|
|
||||||
|
|
||||||
<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" [innerHTML]="description"></div>
|
|
||||||
</li>
|
|
||||||
@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">Category</div>
|
|
||||||
<div class="text-900 w-full md:w-10">
|
|
||||||
<p-chip [label]="selectOptions.getBusiness(listing.type)"></p-chip>
|
|
||||||
</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">Located in</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>
|
|
||||||
<div class="text-900 w-full md:w-10">{{listing.price | currency}}</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">Real Estate Included</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{listing.realEstateIncluded?'Yes':'No'}}</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">Sales revenue</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{listing.salesRevenue | currency}}</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">Cash flow</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{listing.cashFlow | currency}}</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">Employees</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{listing.employees}}</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">Broker licensing</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{listing.brokerLicencing}}</div>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
@if (listing && (listing.listingsCategory==='commercialProperty')){
|
|
||||||
<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">Property Category</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{selectOptions.getCommercialProperty(listing.type)}}
|
|
||||||
</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">Located in</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">City</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{listing.city}}</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">Zip Code</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{listing.zipCode}}</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">County</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{listing.county}}</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">Asking Price:</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{listing.price | currency}}</div>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
<p-galleria [value]="propertyImages" [showIndicators]="true" [showThumbnails]="false"
|
|
||||||
[responsiveOptions]="responsiveOptions" [containerStyle]="{ 'max-width': '640px' }"
|
|
||||||
[numVisible]="5">
|
|
||||||
<ng-template pTemplate="item" let-item>
|
|
||||||
<img src="{{environment.apiBaseUrl}}/property/{{listing.id}}/{{item.name}}"
|
|
||||||
style="width: 100%;" />
|
|
||||||
</ng-template>
|
|
||||||
<!-- <ng-template pTemplate="thumbnail" let-item>
|
|
||||||
<div class="grid grid-nogutter justify-content-center">
|
|
||||||
<img src="{{environment.apiBaseUrl}}/property/{{listing.id}}/{{item.name}}" />
|
|
||||||
</div>
|
|
||||||
</ng-template> -->
|
|
||||||
</p-galleria>
|
|
||||||
@if(listing && user && (user.id===listing?.userId || isAdmin())){
|
|
||||||
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto"
|
|
||||||
[routerLink]="['/editListing',listing.id]"></button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="col-12 md:col-6">
|
|
||||||
<div class="surface-card p-4 border-round p-fluid">
|
|
||||||
<div class="font-medium text-xl text-primary text-900 mb-3">Contact The Author of This Listing
|
|
||||||
</div>
|
|
||||||
<div class="font-italic text-sm text-900 mb-5">Please Include your contact info below:</div>
|
|
||||||
<div class="grid formgrid p-fluid">
|
|
||||||
<div class="field mb-4 col-12 md:col-6">
|
|
||||||
<label for="name" class="font-medium text-900">Your Name</label>
|
|
||||||
<input id="name" type="text" pInputText [(ngModel)]="mailinfo.sender.name">
|
|
||||||
</div>
|
|
||||||
<div class="field mb-4 col-12 md:col-6">
|
|
||||||
<label for="email" class="font-medium text-900">Your Email</label>
|
|
||||||
<input id="email" type="text" pInputText [(ngModel)]="mailinfo.sender.email">
|
|
||||||
</div>
|
|
||||||
<div class="field mb-4 col-12 md:col-6">
|
|
||||||
<label for="phoneNumber" class="font-medium text-900">Phone Number</label>
|
|
||||||
<input id="phoneNumber" type="text" pInputText
|
|
||||||
[(ngModel)]="mailinfo.sender.phoneNumber">
|
|
||||||
</div>
|
|
||||||
<div class="field mb-4 col-12 md:col-6">
|
|
||||||
<label for="state" class="font-medium text-900">Country/State</label>
|
|
||||||
<input id="state" type="text" pInputText [(ngModel)]="mailinfo.sender.state">
|
|
||||||
</div>
|
|
||||||
<div class="surface-border border-top-1 opacity-50 mb-4 col-12"></div>
|
|
||||||
<div class="field mb-4 col-12">
|
|
||||||
<label for="notes" class="font-medium text-900">Questions/Comments</label>
|
|
||||||
<textarea id="notes" pInputTextarea [autoResize]="true" [rows]="5"
|
|
||||||
[(ngModel)]="mailinfo.sender.comments"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="surface-border border-top-1 opacity-50 mb-4 col-12"></div>
|
|
||||||
</div>
|
|
||||||
<button pButton pRipple label="Submit" icon="pi pi-file" class="w-auto"
|
|
||||||
(click)="mail()"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,132 +1,149 @@
|
|||||||
<div class="surface-ground h-full">
|
<div class="surface-ground h-full">
|
||||||
<div class="px-6 py-5">
|
<div class="px-6 py-5">
|
||||||
<div class="surface-card p-4 shadow-2 border-round">
|
@if (user){
|
||||||
<!-- <div class="flex justify-content-between align-items-center align-content-center">
|
<div class="surface-card p-4 shadow-2 border-round">
|
||||||
|
<!-- <div class="flex justify-content-between align-items-center align-content-center">
|
||||||
<div class="font-medium text-3xl text-900 mb-3">{{listing?.title}}</div>
|
<div class="font-medium text-3xl text-900 mb-3">{{listing?.title}}</div>
|
||||||
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="back()"></p-button>
|
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="back()"></p-button>
|
||||||
</div> -->
|
</div> -->
|
||||||
<div class="surface-section px-6 pt-5">
|
<div class="surface-section px-6 pt-5">
|
||||||
<div class="flex align-items-start flex-column lg:flex-row lg:justify-content-between">
|
<div class="flex align-items-start flex-column lg:flex-row lg:justify-content-between">
|
||||||
<div class="flex align-items-start flex-column md:flex-row">
|
<div class="flex align-items-start flex-column md:flex-row">
|
||||||
@if(user.hasProfile){
|
@if(user.hasProfile){
|
||||||
<img src="{{environment.apiBaseUrl}}/profile/{{user.id}}.avif" class="mr-5 mb-3 lg:mb-0"
|
<img src="pictures//profile/{{ user.id }}.avif" class="mr-5 mb-3 lg:mb-0" style="width: 90px" />
|
||||||
style="width:90px" />
|
} @else {
|
||||||
} @else {
|
<img src="assets/images/person_placeholder.jpg" class="mr-5 mb-3 lg:mb-0" style="width: 90px" />
|
||||||
<img src="assets/images/person_placeholder.jpg" class="mr-5 mb-3 lg:mb-0" style="width:90px" />
|
}
|
||||||
}
|
<div>
|
||||||
<div>
|
<span class="text-900 font-medium text-3xl">{{ user.firstname }} {{ user.lastname }}</span>
|
||||||
<span class="text-900 font-medium text-3xl">{{user.firstname}} {{user.lastname}}</span>
|
<i class="pi pi-star text-2xl ml-4 text-yellow-500"></i>
|
||||||
<i class="pi pi-star text-2xl ml-4 text-yellow-500"></i>
|
<div class="flex align-items-center flex-wrap text-sm">
|
||||||
<div class="flex align-items-center flex-wrap text-sm">
|
<div class="mr-5 mt-3">
|
||||||
<div class="mr-5 mt-3">
|
<span class="font-medium text-500">Company</span>
|
||||||
<span class="font-medium text-500">Company</span>
|
<div class="text-700 mt-2">{{ user.companyName }}</div>
|
||||||
<div class="text-700 mt-2">{{user.companyName}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="mr-5 mt-3">
|
|
||||||
<span class="font-medium text-500">For Sale</span>
|
|
||||||
<div class="text-700 mt-2">12</div>
|
|
||||||
</div>
|
|
||||||
<div class="mr-5 mt-3">
|
|
||||||
<span class="font-medium text-500">Sold</span>
|
|
||||||
<div class="text-700 mt-2">8</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex align-items-center mt-3">
|
|
||||||
<!-- <span class="font-medium text-500">Logo</span> -->
|
|
||||||
<div >
|
|
||||||
@if(user.hasCompanyLogo){
|
|
||||||
<img src="{{environment.apiBaseUrl}}/logo/{{user.id}}.avif"
|
|
||||||
class="mr-5 lg:mb-0" style="height:60px;max-width:100px" />
|
|
||||||
}
|
|
||||||
<!-- <img *ngIf="!user.hasCompanyLogo" src="assets/images/placeholder.png"
|
|
||||||
class="mr-5 lg:mb-0" style="height:60px;max-width:100px" /> -->
|
|
||||||
</div>
|
|
||||||
<!-- <div class="text-700 mt-2">130</div> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-700 line-height-3 text-l font-semibold">{{user.description}}</p>
|
<div class="mr-5 mt-3">
|
||||||
|
<span class="font-medium text-500">For Sale</span>
|
||||||
|
<div class="text-700 mt-2">12</div>
|
||||||
|
</div>
|
||||||
|
<div class="mr-5 mt-3">
|
||||||
|
<span class="font-medium text-500">Sold</span>
|
||||||
|
<div class="text-700 mt-2">8</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex align-items-center mt-3">
|
||||||
|
<!-- <span class="font-medium text-500">Logo</span> -->
|
||||||
|
<div>
|
||||||
|
@if(user.hasCompanyLogo){
|
||||||
|
<img src="pictures/logo/{{ user.id }}.avif" class="mr-5 lg:mb-0" style="height: 60px; max-width: 100px" />
|
||||||
|
}
|
||||||
|
<!-- <img *ngIf="!user.hasCompanyLogo" src="assets/images/placeholder.png"
|
||||||
|
class="mr-5 lg:mb-0" style="height:60px;max-width:100px" /> -->
|
||||||
|
</div>
|
||||||
|
<!-- <div class="text-700 mt-2">130</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-6 py-5">
|
</div>
|
||||||
<div class="surface-card p-4 shadow-2 border-round">
|
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="back()"></p-button>
|
||||||
<div class="font-medium text-3xl text-900 mb-3">Company Profile</div>
|
</div>
|
||||||
<div class="text-500 mb-5" [innerHTML]="companyOverview"></div>
|
<p class="mt-2 text-700 line-height-3 text-l font-semibold">{{ user.description }}</p>
|
||||||
<ul class="list-none p-0 m-0 border-top-1 border-300">
|
</div>
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
<div class="px-6 py-5">
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Name</div>
|
<div class="surface-card p-4 shadow-2 border-round">
|
||||||
<div class="text-900 w-full md:w-10">{{user.firstname}} {{user.lastname}}</div>
|
<div class="font-medium text-3xl text-900 mb-3">Company Profile</div>
|
||||||
</li>
|
<div class="text-500 mb-5" [innerHTML]="companyOverview"></div>
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
<ul class="list-none p-0 m-0 border-top-1 border-300">
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Phone Number</div>
|
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
||||||
<div class="text-900 w-full md:w-10 line-height-3">{{user.phoneNumber}}</div>
|
<div class="text-500 w-full md:w-2 font-medium">Name</div>
|
||||||
</li>
|
<div class="text-900 w-full md:w-10">{{ user.firstname }} {{ user.lastname }}</div>
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
</li>
|
||||||
<div class="text-500 w-full md:w-2 font-medium">EMail Address</div>
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
<div class="text-900 w-full md:w-10 line-height-3">{{user.email}}</div>
|
<div class="text-500 w-full md:w-2 font-medium">Phone Number</div>
|
||||||
</li>
|
<div class="text-900 w-full md:w-10 line-height-3">{{ user.phoneNumber }}</div>
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
</li>
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Company Location</div>
|
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
||||||
<div class="text-900 w-full md:w-10 line-height-3">{{user.companyLocation}}</div>
|
<div class="text-500 w-full md:w-2 font-medium">EMail Address</div>
|
||||||
</li>
|
<div class="text-900 w-full md:w-10 line-height-3">{{ user.email }}</div>
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
</li>
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Services we offer</div>
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
<div class="text-900 w-full md:w-10" [innerHTML]="offeredServices"></div>
|
<div class="text-500 w-full md:w-2 font-medium">Company Location</div>
|
||||||
</li>
|
<div class="text-900 w-full md:w-10 line-height-3">{{ user.companyLocation }}</div>
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap ">
|
</li>
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Areas we serve</div>
|
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
||||||
<div class="text-900 w-full md:w-10">
|
<div class="text-500 w-full md:w-2 font-medium">Services we offer</div>
|
||||||
@for (area of user.areasServed; track area) {
|
<div class="text-900 w-full md:w-10" [innerHTML]="offeredServices"></div>
|
||||||
<p-tag styleClass="mr-2" [value]="area" [rounded]="true"></p-tag>
|
</li>
|
||||||
}
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
<!-- <p-tag styleClass="mr-2" severity="success" value="Javascript" [rounded]="true"></p-tag>
|
<div class="text-500 w-full md:w-2 font-medium">Areas we serve</div>
|
||||||
|
<div class="text-900 w-full md:w-10">
|
||||||
|
@for (area of user.areasServed; track area) {
|
||||||
|
<p-tag styleClass="mr-2" [value]="area" [rounded]="true"></p-tag>
|
||||||
|
}
|
||||||
|
<!-- <p-tag styleClass="mr-2" severity="success" value="Javascript" [rounded]="true"></p-tag>
|
||||||
<p-tag styleClass="mr-2" severity="danger" value="Python" [rounded]="true"></p-tag>
|
<p-tag styleClass="mr-2" severity="danger" value="Python" [rounded]="true"></p-tag>
|
||||||
<p-tag severity="warning" value="SQL" [rounded]="true"></p-tag> -->
|
<p-tag severity="warning" value="SQL" [rounded]="true"></p-tag> -->
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
">
|
<div class="text-500 w-full md:w-2 font-medium">Licensed In</div>
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Licensed In</div>
|
<div class="text-900 w-full md:w-10">
|
||||||
<div class="text-900 w-full md:w-10">
|
@for (license of userLicensedIn; track license) {
|
||||||
@for (license of user.licensedIn; track license) {
|
<div>{{ license.name }} : {{ license.value }}</div>
|
||||||
<div>{{license.name}} : {{license.value}}</div>
|
}
|
||||||
}
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</li>
|
@if(businessListings?.length>0){
|
||||||
<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">My Listings For Sale</div>
|
<div class="text-500 w-full md:w-2 font-medium">My Business Listings For Sale</div>
|
||||||
<div class="text-900 w-full md:w-10">
|
<div class="text-900 w-full md:w-10">
|
||||||
<div class="grid mt-0 mr-0">
|
<div class="grid mt-0 mr-0">
|
||||||
@for (listing of userListings; track listing) {
|
@for (listing of businessListings; track listing) {
|
||||||
<div class="col-12 md:col-6 cursor-pointer" [routerLink]="['/details-listing/business',listing.id]">
|
<div class="col-12 md:col-6 cursor-pointer" [routerLink]="['/details-business-listing', listing.id]">
|
||||||
<div class="p-3 border-1 surface-border border-round surface-card">
|
<div class="p-3 border-1 surface-border border-round surface-card">
|
||||||
<div class="text-900 mb-2">
|
<div class="text-900 mb-2">
|
||||||
<span [class]="selectOptions.getBgColorType(listing.type)"
|
<span [class]="selectOptions.getBgColorType(listing.type)" class="inline-flex border-circle align-items-center justify-content-center mr-3" style="width: 38px; height: 38px">
|
||||||
class="inline-flex border-circle align-items-center justify-content-center mr-3"
|
<i [class]="selectOptions.getIconAndTextColorType(listing.type)" class="pi text-xl"></i>
|
||||||
style="width:38px;height:38px">
|
</span>
|
||||||
<i [class]="selectOptions.getIconAndTextColorType(listing.type)"
|
<span class="font-medium">{{ selectOptions.getBusiness(listing.type) }}</span>
|
||||||
class="pi text-xl"></i>
|
</div>
|
||||||
</span>
|
<div class="text-700">{{ listing.title }}</div>
|
||||||
<span
|
</div>
|
||||||
class="font-medium">{{selectOptions.getBusiness(listing.type)}}</span>
|
</div>
|
||||||
</div>
|
}
|
||||||
<div class="text-700">{{listing.title}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if( user?.id===(user$| async)?.id || isAdmin()){
|
</li>
|
||||||
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto"
|
} @if(commercialPropListings?.length>0){
|
||||||
[routerLink]="['/account',user.id]"></button>
|
<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">My Commercial Property Listings For Sale</div>
|
||||||
|
<div class="text-900 w-full md:w-10">
|
||||||
|
<div class="grid mt-0 mr-0">
|
||||||
|
@for (listing of commercialPropListings; track listing) {
|
||||||
|
<div class="col-12 md:col-6 cursor-pointer" [routerLink]="['/details-commercial-property-listing', listing.id]">
|
||||||
|
<div class="p-3 border-1 surface-border border-round surface-card">
|
||||||
|
<div class="text-900 mb-2 flex align-items-center">
|
||||||
|
@if (listing.imageOrder?.length>0){
|
||||||
|
<img src="pictures/property/{{ listing.imagePath }}/{{ listing.imageOrder[0] }}" class="mr-3" style="width: 45px; height: 45px" />
|
||||||
|
} @else {
|
||||||
|
<img src="assets/images/placeholder_properties.jpg" class="mr-3" style="width: 45px; height: 45px" />
|
||||||
|
}
|
||||||
|
<span class="font-medium">{{ selectOptions.getCommercialProperty(listing.type) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-700">{{ listing.title }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
@if( user?.id===(user$| async)?.id || isAdmin()){
|
||||||
|
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/account']"></button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
|
import { Location } from '@angular/common';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { SharedModule } from '../../../shared/shared/shared.module';
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
import { GalleriaModule } from 'primeng/galleria';
|
|
||||||
import { MessageService } from 'primeng/api';
|
|
||||||
import { BusinessListing, ListingCriteria, ListingType, User } from '../../../../../../common-models/src/main.model';
|
|
||||||
import { environment } from '../../../../environments/environment';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { UserService } from '../../../services/user.service';
|
import { MessageService } from 'primeng/api';
|
||||||
|
import { GalleriaModule } from 'primeng/galleria';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
|
import { KeyValue, ListingCriteria } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { ImageService } from '../../../services/image.service';
|
||||||
import { ListingsService } from '../../../services/listings.service';
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
import { UserService } from '../../../services/user.service';
|
||||||
import { ImageService } from '../../../services/image.service';
|
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-details-user',
|
selector: 'app-details-user',
|
||||||
@@ -18,37 +20,46 @@ import { ImageService } from '../../../services/image.service';
|
|||||||
imports: [SharedModule, GalleriaModule],
|
imports: [SharedModule, GalleriaModule],
|
||||||
providers: [MessageService],
|
providers: [MessageService],
|
||||||
templateUrl: './details-user.component.html',
|
templateUrl: './details-user.component.html',
|
||||||
styleUrl: './details-user.component.scss'
|
styleUrl: './details-user.component.scss',
|
||||||
})
|
})
|
||||||
export class DetailsUserComponent {
|
export class DetailsUserComponent {
|
||||||
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;
|
||||||
user$:Observable<User>
|
user$: Observable<User>;
|
||||||
environment = environment;
|
environment = environment;
|
||||||
criteria:ListingCriteria;
|
criteria: ListingCriteria;
|
||||||
userListings:BusinessListing[]
|
businessListings: BusinessListing[];
|
||||||
companyOverview:SafeHtml;
|
commercialPropListings: CommercialPropertyListing[];
|
||||||
offeredServices:SafeHtml;
|
companyOverview: SafeHtml;
|
||||||
constructor(private activatedRoute: ActivatedRoute,
|
offeredServices: SafeHtml;
|
||||||
|
userLicensedIn: KeyValue[];
|
||||||
|
constructor(
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
private listingsService:ListingsService,
|
private listingsService: ListingsService,
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
public selectOptions: SelectOptionsService,
|
public selectOptions: SelectOptionsService,
|
||||||
private sanitizer: DomSanitizer,
|
private sanitizer: DomSanitizer,
|
||||||
private imageService:ImageService) {
|
private imageService: ImageService,
|
||||||
}
|
private location: Location,
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.user = await this.userService.getById(this.id);
|
this.user = await this.userService.getById(this.id);
|
||||||
|
this.userLicensedIn = this.user.licensedIn.map(l => {
|
||||||
this.userListings = await this.listingsService.getListingByUserId(this.id);
|
return { name: l.split('|')[0], value: l.split('|')[1] };
|
||||||
|
});
|
||||||
|
const results = await Promise.all([await this.listingsService.getListingByUserId(this.id, 'business'), await this.listingsService.getListingByUserId(this.id, 'commercialProperty')]);
|
||||||
|
// Zuweisen der Ergebnisse zu den Member-Variablen der Klasse
|
||||||
|
this.businessListings = results[0];
|
||||||
|
this.commercialPropListings = results[1];
|
||||||
this.user$ = this.userService.getUserObservable();
|
this.user$ = this.userService.getUserObservable();
|
||||||
this.companyOverview=this.sanitizer.bypassSecurityTrustHtml(this.user.companyOverview);
|
this.companyOverview = this.sanitizer.bypassSecurityTrustHtml(this.user.companyOverview);
|
||||||
this.offeredServices=this.sanitizer.bypassSecurityTrustHtml(this.user.offeredServices);
|
this.offeredServices = this.sanitizer.bypassSecurityTrustHtml(this.user.offeredServices);
|
||||||
}
|
}
|
||||||
back() {
|
back() {
|
||||||
this.router.navigate(['listings', this.criteria.listingsCategory])
|
this.location.back();
|
||||||
}
|
}
|
||||||
isAdmin() {
|
isAdmin() {
|
||||||
return this.userService.hasAdminRole();
|
return this.userService.hasAdminRole();
|
||||||
|
|||||||
@@ -1,75 +1,82 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="py-3 px-6 flex align-items-center justify-content-between relative">
|
<div class="py-3 px-6 flex align-items-center justify-content-between relative">
|
||||||
<a routerLink="/home"><img src="../../../assets/images/header-logo.png" alt="Image" height="50" ></a>
|
<a routerLink="/home"><img src="../../../assets/images/header-logo.png" alt="Image" height="50" /></a>
|
||||||
<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>
|
<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">
|
||||||
<div
|
@if(userService.isLoggedIn()){
|
||||||
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>
|
||||||
@if(userService.isLoggedIn()){
|
} @else {
|
||||||
<p-button label="Account" class="ml-3 font-bold" [outlined]="true" severity="secondary" [routerLink]="['/account',(user$|async)?.id]"></p-button>
|
<p-button label="Log In" class="ml-3 font-bold" [outlined]="true" severity="secondary" (click)="login()"></p-button>
|
||||||
} @else {
|
}
|
||||||
<p-button label="Log In" class="ml-3 font-bold" [outlined]="true" severity="secondary" (click)="login()"></p-button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="px-4 py-8 md:px-6 lg:px-8">
|
|
||||||
<div class="flex flex-wrap">
|
|
||||||
<div class="w-12 lg:w-6 p-4">
|
|
||||||
<h1 class="text-6xl font-bold text-blue-900 mt-0 mb-3">Find businesses for sale</h1>
|
|
||||||
<p class="text-3xl text-blue-600 mt-0 mb-5">Arcu cursus euismod quis viverra nibh cras. Amet justo
|
|
||||||
donec
|
|
||||||
enim diam vulputate ut.</p>
|
|
||||||
<ul class="list-none p-0 m-0">
|
|
||||||
<li class="mb-3 flex align-items-center"><i
|
|
||||||
class="pi pi-compass text-yellow-500 text-xl mr-2"></i><span
|
|
||||||
class="text-blue-600 line-height-3">Senectus et netus et malesuada fames.</span></li>
|
|
||||||
<li class="mb-3 flex align-items-center"><i
|
|
||||||
class="pi pi-map text-yellow-500 text-xl mr-2"></i><span
|
|
||||||
class="text-blue-600 line-height-3">Orci a scelerisque purus semper eget.</span></li>
|
|
||||||
<li class="mb-3 flex align-items-center"><i
|
|
||||||
class="pi pi-calendar text-yellow-500 text-xl mr-2"></i><span
|
|
||||||
class="text-blue-600 line-height-3">Aenean sed adipiscing diam donec adipiscing
|
|
||||||
tristique.</span></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="w-12 lg:w-6 text-center lg:text-right flex">
|
|
||||||
<div class="mt-5">
|
|
||||||
<ul class="flex flex-column align-items-left gap-3 px-2 py-3 list-none surface-border">
|
|
||||||
<li><button pButton pRipple icon="pi pi-user" (click)="activeTabAction = 'business'"
|
|
||||||
label="Businesses"
|
|
||||||
[ngClass]="{'p-button-text text-700': activeTabAction !== 'business'}"></button></li>
|
|
||||||
<li><button pButton pRipple icon="pi pi-globe" (click)="activeTabAction = 'professionals_brokers'"
|
|
||||||
label="Professionals/Brokers Directory"
|
|
||||||
[ngClass]="{'p-button-text text-700': activeTabAction != 'professionals_brokers'}"></button></li>
|
|
||||||
<li><button pButton pRipple icon="pi pi-shield" (click)="activeTabAction = 'commercialProperty'"
|
|
||||||
label="Commercial Property"
|
|
||||||
[ngClass]="{'p-button-text text-700': activeTabAction != 'commercialProperty'}"></button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="mt-5">
|
|
||||||
<div class="flex flex-column align-items-right gap-3 px-2 py-3 my-3 surface-border">
|
|
||||||
<p-dropdown [options]="selectOptions.typesOfBusiness" [(ngModel)]="criteria.type" optionLabel="name"
|
|
||||||
optionValue="value" [showClear]="true" placeholder="Category"
|
|
||||||
[style]="{ width: '200px'}"></p-dropdown>
|
|
||||||
<p-dropdown [options]="selectOptions.prices" [(ngModel)]="criteria.minPrice" optionLabel="name" optionValue="value"
|
|
||||||
[showClear]="true" placeholder="Min Price" [style]="{ width: '200px'}"></p-dropdown>
|
|
||||||
<p-dropdown [options]="selectOptions.prices" [(ngModel)]="criteria.maxPrice" optionLabel="name" optionValue="value"
|
|
||||||
[showClear]="true" placeholder="Max Price" [style]="{ width: '200px'}"></p-dropdown>
|
|
||||||
<button pButton pRipple label="Find" class="ml-3 font-bold"
|
|
||||||
[style]="{ width: '170px'}" (click)="search()"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-12 flex justify-content-center">
|
|
||||||
<button type="button" pButton pRipple label="Create Your Listing"
|
|
||||||
class="block mt-7 mb-7 lg:mb-0 p-button-rounded p-button-success p-button-lg font-medium" [routerLink]="userService.isLoggedIn()?'/createListing':'/pricing'"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="px-4 py-8 md:px-6 lg:px-8">
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<div class="w-12 lg:w-6 p-4">
|
||||||
|
<h1 class="text-6xl font-bold text-blue-900 mt-0 mb-3">Find businesses for sale</h1>
|
||||||
|
<p class="text-3xl text-blue-600 mt-0 mb-5">Unlocking Exclusive Opportunities, Empowering Entrepreneurial Dreams</p>
|
||||||
|
<ul class="list-none p-0 m-0">
|
||||||
|
<li class="mb-3 flex align-items-center"><i class="pi pi-compass text-yellow-500 text-xl mr-2"></i><span class="text-blue-600 line-height-3">Texas expertise and nationwide presence</span></li>
|
||||||
|
<li class="mb-3 flex align-items-center"><i class="pi pi-map text-yellow-500 text-xl mr-2"></i><span class="text-blue-600 line-height-3">Industry diversity</span></li>
|
||||||
|
<li class="mb-3 flex align-items-center"><i class="pi pi-calendar text-yellow-500 text-xl mr-2"></i><span class="text-blue-600 line-height-3">Support throughout the entire process</span></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="w-12 lg:w-6 text-center lg:text-right flex">
|
||||||
|
<div class="mt-5">
|
||||||
|
<ul class="flex flex-column align-items-left gap-3 px-2 py-3 list-none surface-border">
|
||||||
|
<li><button pButton pRipple icon="pi pi-user" (click)="changeTab('business')" label="Businesses" [ngClass]="{ 'p-button-text text-700': activeTabAction !== 'business' }"></button></li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
pButton
|
||||||
|
pRipple
|
||||||
|
icon="pi pi-shield"
|
||||||
|
(click)="changeTab('commercialProperty')"
|
||||||
|
label="Commercial Property"
|
||||||
|
[ngClass]="{ 'p-button-text text-700': activeTabAction != 'commercialProperty' }"
|
||||||
|
></button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button pButton pRipple icon="pi pi-globe" (click)="changeTab('broker')" label="Professionals/Brokers Directory" [ngClass]="{ 'p-button-text text-700': activeTabAction != 'broker' }"></button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div [ngClass]="{ 'mt-5': activeTabAction === 'business', 'mt-11': activeTabAction === 'commercialProperty', 'mt-22': activeTabAction === 'broker' }">
|
||||||
|
<div class="flex flex-column align-items-right gap-3 px-2 py-3 my-3 surface-border">
|
||||||
|
<p-dropdown [options]="states" [(ngModel)]="criteria.state" optionLabel="name" optionValue="value" [showClear]="true" placeholder="State" [style]="{ width: '200px' }"></p-dropdown>
|
||||||
|
@if(activeTabAction === 'business'){
|
||||||
|
<p-dropdown [options]="selectOptions.typesOfBusiness" [(ngModel)]="criteria.type" optionLabel="name" optionValue="value" [showClear]="true" placeholder="Category" [style]="{ width: '200px' }"></p-dropdown>
|
||||||
|
} @if(activeTabAction === 'commercialProperty'){
|
||||||
|
<p-dropdown
|
||||||
|
[options]="selectOptions.typesOfCommercialProperty"
|
||||||
|
[(ngModel)]="criteria.type"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="value"
|
||||||
|
[showClear]="true"
|
||||||
|
placeholder="Category"
|
||||||
|
[style]="{ width: '200px' }"
|
||||||
|
></p-dropdown>
|
||||||
|
} @if(activeTabAction === 'business' || activeTabAction === 'commercialProperty'){
|
||||||
|
<p-dropdown [options]="selectOptions.prices" [(ngModel)]="criteria.minPrice" optionLabel="name" optionValue="value" [showClear]="true" placeholder="Min Price" [style]="{ width: '200px' }"></p-dropdown>
|
||||||
|
<p-dropdown [options]="selectOptions.prices" [(ngModel)]="criteria.maxPrice" optionLabel="name" optionValue="value" [showClear]="true" placeholder="Max Price" [style]="{ width: '200px' }"></p-dropdown>
|
||||||
|
}
|
||||||
|
<button pButton pRipple label="Find" class="ml-3 font-bold" [style]="{ width: '200px' }" (click)="search()"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-12 flex justify-content-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
pButton
|
||||||
|
pRipple
|
||||||
|
label="Create Your Listing"
|
||||||
|
class="block mt-7 mb-7 lg:mb-0 p-button-rounded p-button-success p-button-lg font-medium"
|
||||||
|
[routerLink]="userService.isLoggedIn() ? '/createBusinessListing' : '/pricing'"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
:host {
|
:host {
|
||||||
height: 100%
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
background-image: url(../../../assets/images/index-bg.webp);
|
background-image: url(../../../assets/images/index-bg.webp);
|
||||||
//background-image: url(../../../assets/images/corpusChristiSkyline.jpg);
|
// background-image: url(../../../assets/images/1_Version.jpg);
|
||||||
|
//background-image: url(../../../assets/images/2_1_Version.jpg);
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
.combo_lp{
|
.combo_lp {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
.p-button-white{
|
.p-button-white {
|
||||||
color:aliceblue
|
color: aliceblue;
|
||||||
}
|
}
|
||||||
|
.mt-11 {
|
||||||
|
margin-top: 5.9rem !important;
|
||||||
|
}
|
||||||
|
.mt-22 {
|
||||||
|
margin-top: 9.7rem !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,46 +1,63 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { DropdownModule } from 'primeng/dropdown';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { Component } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||||
import { StyleClassModule } from 'primeng/styleclass';
|
import onChange from 'on-change';
|
||||||
import { ButtonModule } from 'primeng/button';
|
import { ButtonModule } from 'primeng/button';
|
||||||
import { CheckboxModule } from 'primeng/checkbox';
|
import { CheckboxModule } from 'primeng/checkbox';
|
||||||
|
import { DropdownModule } from 'primeng/dropdown';
|
||||||
import { InputTextModule } from 'primeng/inputtext';
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { StyleClassModule } from 'primeng/styleclass';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { User } from '../../../../../bizmatch-server/src/models/db.model';
|
||||||
|
import { ListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
import { ListingsService } from '../../services/listings.service';
|
||||||
import { SelectOptionsService } from '../../services/select-options.service';
|
import { SelectOptionsService } from '../../services/select-options.service';
|
||||||
import { UserService } from '../../services/user.service';
|
import { UserService } from '../../services/user.service';
|
||||||
import onChange from 'on-change';
|
import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from '../../utils/utils';
|
||||||
import { getCriteriaStateObject, getSessionStorageHandler } from '../../utils/utils';
|
|
||||||
import { ListingCriteria, User } from '../../../../../common-models/src/main.model';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, StyleClassModule,ButtonModule, CheckboxModule,InputTextModule,DropdownModule,FormsModule, RouterModule],
|
imports: [CommonModule, StyleClassModule, ButtonModule, CheckboxModule, InputTextModule, DropdownModule, FormsModule, RouterModule],
|
||||||
templateUrl: './home.component.html',
|
templateUrl: './home.component.html',
|
||||||
styleUrl: './home.component.scss'
|
styleUrl: './home.component.scss',
|
||||||
})
|
})
|
||||||
export class HomeComponent {
|
export class HomeComponent {
|
||||||
activeTabAction = 'business';
|
activeTabAction: 'business' | 'commercialProperty' | 'broker' = 'business';
|
||||||
type:string;
|
type: string;
|
||||||
maxPrice:string;
|
maxPrice: string;
|
||||||
minPrice:string;
|
minPrice: string;
|
||||||
criteria:ListingCriteria
|
criteria: ListingCriteria;
|
||||||
user$:Observable<User>
|
user$: Observable<User>;
|
||||||
public constructor(private router: Router,private activatedRoute: ActivatedRoute, public selectOptions:SelectOptionsService, public userService:UserService) {
|
states = [];
|
||||||
this.criteria = onChange(getCriteriaStateObject(),getSessionStorageHandler);
|
public constructor(private router: Router, private activatedRoute: ActivatedRoute, public selectOptions: SelectOptionsService, public userService: UserService, private listingsService: ListingsService) {
|
||||||
|
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
||||||
|
resetCriteria(this.criteria);
|
||||||
}
|
}
|
||||||
ngOnInit(){
|
async ngOnInit() {
|
||||||
this.user$=this.userService.getUserObservable();
|
this.user$ = this.userService.getUserObservable();
|
||||||
|
if (this.activeTabAction === 'business' || this.activeTabAction === 'commercialProperty') {
|
||||||
|
const statesResult = await this.listingsService.getAllStates(this.activeTabAction);
|
||||||
|
this.states = statesResult.map(s => s.state).map(ls => ({ name: this.selectOptions.getState(ls as string), value: ls }));
|
||||||
|
} else {
|
||||||
|
this.states = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async changeTab(tabname: 'business' | 'commercialProperty' | 'broker') {
|
||||||
|
this.activeTabAction = tabname;
|
||||||
|
if (this.activeTabAction === 'business' || this.activeTabAction === 'commercialProperty') {
|
||||||
|
const statesResult = await this.listingsService.getAllStates(this.activeTabAction);
|
||||||
|
this.states = statesResult.map(s => s.state).map(ls => ({ name: this.selectOptions.getState(ls as string), value: ls }));
|
||||||
|
} else {
|
||||||
|
this.states = this.selectOptions.states;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
search() {
|
||||||
|
const data = { keep: true };
|
||||||
|
this.router.navigate([`${this.activeTabAction}Listings`]);
|
||||||
}
|
}
|
||||||
|
|
||||||
search(){
|
login() {
|
||||||
this.router.navigate([`listings/${this.activeTabAction}`])
|
this.userService.login(window.location.href);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
login(){
|
|
||||||
this.userService.login(window.location.href);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<div id="sky-line" class="hidden-lg-down"></div>
|
||||||
|
<div class="search">
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="grid p-4 align-items-center">
|
||||||
|
<div class="col-2">
|
||||||
|
<p-dropdown
|
||||||
|
[filter]="true"
|
||||||
|
filterBy="name"
|
||||||
|
[options]="selectOptions.states"
|
||||||
|
[(ngModel)]="criteria.state"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="value"
|
||||||
|
[showClear]="true"
|
||||||
|
placeholder="Location"
|
||||||
|
[style]="{ width: '100%' }"
|
||||||
|
>
|
||||||
|
<ng-template let-state pTemplate="item">
|
||||||
|
<div class="flex align-items-center gap-2">
|
||||||
|
<div>{{ state.name }}</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<p-inputGroup>
|
||||||
|
<input id="name" type="text" pInputText [(ngModel)]="criteria.name" placeholder="Name" />
|
||||||
|
<button type="button" pButton icon="pi pi-times" class="p-button-secondary" (click)="reset()"></button>
|
||||||
|
</p-inputGroup>
|
||||||
|
</div>
|
||||||
|
<div class="col-1 col-offset-7">
|
||||||
|
<p-button label="Refine" (click)="refine()"></p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="surface-200 h-full">
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="grid">
|
||||||
|
@for (user of users; track user) {
|
||||||
|
<div class="col-12 lg:col-6 xl:col-4 p-4 flex flex-column">
|
||||||
|
<div class="surface-card shadow-2 p-2 flex flex-column flex-grow-1 justify-content-between" style="border-radius: 10px">
|
||||||
|
<div class="surface-card p-4 flex flex-column align-items-center md:flex-row md:align-items-stretch h-full">
|
||||||
|
<span>
|
||||||
|
@if(user.hasProfile){
|
||||||
|
<img src="pictures/profile/{{ user.id }}.avif" class="w-5rem" />
|
||||||
|
} @else {
|
||||||
|
<img src="assets/images/person_placeholder.jpg" class="w-5rem" />
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<div class="flex flex-column align-items-center md:align-items-stretch ml-4 mt-4 md:mt-0">
|
||||||
|
<p class="mt-0 mb-3 line-height-3 text-center md:text-left">{{ user.description }}</p>
|
||||||
|
<span class="text-900 font-medium mb-1 mt-auto">{{ user.firstname }} {{ user.lastname }}</span>
|
||||||
|
<div class="text-600 text-sm">{{ user.companyName }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-3 text-right flex justify-content-between align-items-center">
|
||||||
|
@if(user.hasCompanyLogo){
|
||||||
|
<img src="pictures/logo/{{ user.id }}.avif" class="rounded-image" />
|
||||||
|
} @else {
|
||||||
|
<img src="assets/images/placeholder.png" class="rounded-image" />
|
||||||
|
}
|
||||||
|
<button pButton pRipple icon="pi pi-arrow-right" iconPos="right" label="View Full profile" class="p-button-rounded p-button-success" [routerLink]="['/details-user', user.id]"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="mb-2 surface-200 flex align-items-center justify-content-center">
|
||||||
|
<div class="mx-1 text-color">Total number of Professionals/Brokers: {{ totalRecords }}</div>
|
||||||
|
<p-paginator (onPageChange)="onPageChange($event)" [first]="first" [rows]="rows" [totalRecords]="totalRecords" [rowsPerPageOptions]="[12, 24, 48]"></p-paginator>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#sky-line {
|
#sky-line {
|
||||||
background-image: url(../../../assets/images/bw-sky.jpg);
|
background-image: url(../../../../assets/images/bw-sky.jpg);
|
||||||
height: 204px;
|
height: 204px;
|
||||||
background-position: bottom;
|
background-position: bottom;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { CommonModule, NgOptimizedImage } from '@angular/common';
|
||||||
|
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||||
|
import onChange from 'on-change';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { CheckboxModule } from 'primeng/checkbox';
|
||||||
|
import { DropdownModule } from 'primeng/dropdown';
|
||||||
|
import { InputGroupModule } from 'primeng/inputgroup';
|
||||||
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { PaginatorModule } from 'primeng/paginator';
|
||||||
|
import { StyleClassModule } from 'primeng/styleclass';
|
||||||
|
import { ToggleButtonModule } from 'primeng/togglebutton';
|
||||||
|
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
|
import { ListingCriteria, ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { ImageService } from '../../../services/image.service';
|
||||||
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
|
import { UserService } from '../../../services/user.service';
|
||||||
|
import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from '../../../utils/utils';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-broker-listings',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
StyleClassModule,
|
||||||
|
ButtonModule,
|
||||||
|
CheckboxModule,
|
||||||
|
InputTextModule,
|
||||||
|
DropdownModule,
|
||||||
|
FormsModule,
|
||||||
|
StyleClassModule,
|
||||||
|
ToggleButtonModule,
|
||||||
|
RouterModule,
|
||||||
|
PaginatorModule,
|
||||||
|
InputGroupModule,
|
||||||
|
NgOptimizedImage,
|
||||||
|
],
|
||||||
|
templateUrl: './broker-listings.component.html',
|
||||||
|
styleUrl: './broker-listings.component.scss',
|
||||||
|
})
|
||||||
|
export class BrokerListingsComponent {
|
||||||
|
environment = environment;
|
||||||
|
listings: Array<BusinessListing>;
|
||||||
|
users: Array<User>;
|
||||||
|
filteredListings: Array<ListingType>;
|
||||||
|
criteria: ListingCriteria;
|
||||||
|
realEstateChecked: boolean;
|
||||||
|
maxPrice: string;
|
||||||
|
minPrice: string;
|
||||||
|
type: string;
|
||||||
|
states = [];
|
||||||
|
statesSet = new Set();
|
||||||
|
state: string;
|
||||||
|
first: number = 0;
|
||||||
|
rows: number = 12;
|
||||||
|
totalRecords: number = 0;
|
||||||
|
ts = new Date().getTime();
|
||||||
|
public category: 'business' | 'commercialProperty' | 'professionals_brokers' | undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public selectOptions: SelectOptionsService,
|
||||||
|
private listingsService: ListingsService,
|
||||||
|
private userService: UserService,
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private cdRef: ChangeDetectorRef,
|
||||||
|
private imageService: ImageService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
||||||
|
this.criteria.type = undefined;
|
||||||
|
this.route.data.subscribe(async () => {
|
||||||
|
if (this.router.getCurrentNavigation().extras.state) {
|
||||||
|
resetCriteria(this.criteria);
|
||||||
|
}
|
||||||
|
this.init();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async ngOnInit() {
|
||||||
|
const statesResult = await this.listingsService.getAllStates('business');
|
||||||
|
this.states = statesResult.map(ls => ({ name: this.selectOptions.getState(ls.state as string), value: ls.state, count: ls.count }));
|
||||||
|
}
|
||||||
|
async init() {
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
refine() {
|
||||||
|
this.criteria.start = 0;
|
||||||
|
this.criteria.page = 0;
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
async search() {
|
||||||
|
const usersReponse = await this.userService.search(this.criteria);
|
||||||
|
this.users = usersReponse.data;
|
||||||
|
this.totalRecords = usersReponse.total;
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
this.cdRef.detectChanges();
|
||||||
|
}
|
||||||
|
onPageChange(event: any) {
|
||||||
|
this.criteria.start = event.first;
|
||||||
|
this.criteria.length = event.rows;
|
||||||
|
this.criteria.page = event.page;
|
||||||
|
this.criteria.pageCount = event.pageCount;
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
reset() {
|
||||||
|
this.criteria.name = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
<div id="sky-line" class="hidden-lg-down"></div>
|
||||||
|
<div class="search">
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="grid p-4 align-items-center">
|
||||||
|
<div class="col-2">
|
||||||
|
<p-dropdown
|
||||||
|
[filter]="true"
|
||||||
|
filterBy="name"
|
||||||
|
[options]="states"
|
||||||
|
[(ngModel)]="criteria.state"
|
||||||
|
optionLabel="criteria.location"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="value"
|
||||||
|
[showClear]="true"
|
||||||
|
placeholder="State"
|
||||||
|
[style]="{ width: '100%' }"
|
||||||
|
>
|
||||||
|
<ng-template let-state pTemplate="item">
|
||||||
|
<div class="flex align-items-center gap-2">
|
||||||
|
<div>{{ state.name }} ({{ state.count }})</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<p-dropdown
|
||||||
|
[filter]="true"
|
||||||
|
filterBy="name"
|
||||||
|
[options]="selectOptions.typesOfBusiness"
|
||||||
|
[(ngModel)]="criteria.type"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="value"
|
||||||
|
[showClear]="true"
|
||||||
|
placeholder="Categorie of Business"
|
||||||
|
[style]="{ width: '100%' }"
|
||||||
|
></p-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<p-dropdown [options]="selectOptions.prices" [(ngModel)]="criteria.minPrice" optionLabel="name" optionValue="value" [showClear]="true" placeholder="Min Price" [style]="{ width: '100%' }"></p-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<p-dropdown [options]="selectOptions.prices" [(ngModel)]="criteria.maxPrice" optionLabel="name" optionValue="value" [showClear]="true" placeholder="Max Price" [style]="{ width: '100%' }"></p-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<p-inputGroup>
|
||||||
|
<input id="name" type="text" pInputText [(ngModel)]="criteria.title" placeholder="Title" />
|
||||||
|
<button type="button" pButton icon="pi pi-times" class="p-button-secondary" (click)="reset()"></button>
|
||||||
|
</p-inputGroup>
|
||||||
|
</div>
|
||||||
|
<div class="col-1" pTooltip="Real Estate excluded/included" tooltipPosition="top">
|
||||||
|
<!-- <p-toggleButton [(ngModel)]="checked1" onLabel="Sustainable" offLabel="Unsustainable" onIcon="pi pi-check" offIcon="pi pi-times" styleClass="mb-3 lg:mt-0 mr-4 flex-shrink-0 w-12rem"></p-toggleButton> -->
|
||||||
|
<p-toggleButton [(ngModel)]="criteria.realEstateChecked" onLabel="RE incl." offLabel="RE excl."></p-toggleButton>
|
||||||
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<p-button label="Refine" (click)="refine()"></p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="surface-200 h-full">
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="grid">
|
||||||
|
@for (listing of listings; track listing.id) {
|
||||||
|
<div class="col-12 lg:col-3 p-3">
|
||||||
|
<div class="shadow-2 border-round surface-card mb-3 h-full flex-column justify-content-between flex">
|
||||||
|
<div class="p-4 h-full flex flex-column">
|
||||||
|
<div class="flex align-items-center">
|
||||||
|
<span [class]="selectOptions.getBgColorType(listing.type)" class="inline-flex border-circle align-items-center justify-content-center mr-3" style="width: 38px; height: 38px">
|
||||||
|
<i [class]="selectOptions.getIconAndTextColorType(listing.type)" class="pi text-xl"></i>
|
||||||
|
</span>
|
||||||
|
<span class="text-900 font-medium text-2xl">{{ selectOptions.getBusiness(listing.type) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-900 my-3 text-xl font-medium">{{ listing.title }}</div>
|
||||||
|
<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.getState(listing.state) }}</p>
|
||||||
|
<p class="mt-0 mb-1 text-700 line-height-3">Established: {{ listing.established }}</p>
|
||||||
|
<div class="mt-auto ml-auto">
|
||||||
|
<img src="pictures/logo/{{ listing.userId }}.avif" (error)="imageErrorHandler(listing)" class="rounded-image" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-3 surface-100 text-left">
|
||||||
|
<button pButton pRipple icon="pi pi-arrow-right" iconPos="right" label="View Full Listing" class="p-button-rounded p-button-success" [routerLink]="['/details-business-listing', listing.id]"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="mb-2 surface-200 flex align-items-center justify-content-center">
|
||||||
|
<div class="mx-1 text-color">Total number of Listings: {{ totalRecords }}</div>
|
||||||
|
<p-paginator (onPageChange)="onPageChange($event)" [first]="first" [rows]="rows" [totalRecords]="totalRecords" [rowsPerPageOptions]="[12, 24, 48]"></p-paginator>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#sky-line {
|
||||||
|
background-image: url(../../../../assets/images/bw-sky.jpg);
|
||||||
|
height: 204px;
|
||||||
|
background-position: bottom;
|
||||||
|
background-size: cover;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
|
.search {
|
||||||
|
background-color: #343f69;
|
||||||
|
}
|
||||||
|
::ng-deep p-paginator div {
|
||||||
|
background-color: var(--surface-200) !important;
|
||||||
|
// background-color: var(--surface-400) !important;
|
||||||
|
}
|
||||||
|
.rounded-image {
|
||||||
|
border-radius: 6px;
|
||||||
|
// width: 100px;
|
||||||
|
max-width: 100px;
|
||||||
|
height: 45px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 1px 1px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
::ng-deep span.p-button-label {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||||
|
import onChange from 'on-change';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { CheckboxModule } from 'primeng/checkbox';
|
||||||
|
import { DropdownModule } from 'primeng/dropdown';
|
||||||
|
import { InputGroupModule } from 'primeng/inputgroup';
|
||||||
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { PaginatorModule } from 'primeng/paginator';
|
||||||
|
import { StyleClassModule } from 'primeng/styleclass';
|
||||||
|
import { ToggleButtonModule } from 'primeng/togglebutton';
|
||||||
|
import { TooltipModule } from 'primeng/tooltip';
|
||||||
|
import { BusinessListing } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
|
import { ListingCriteria, ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { ImageService } from '../../../services/image.service';
|
||||||
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
|
import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from '../../../utils/utils';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-business-listings',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
StyleClassModule,
|
||||||
|
ButtonModule,
|
||||||
|
CheckboxModule,
|
||||||
|
InputTextModule,
|
||||||
|
DropdownModule,
|
||||||
|
FormsModule,
|
||||||
|
StyleClassModule,
|
||||||
|
ToggleButtonModule,
|
||||||
|
RouterModule,
|
||||||
|
PaginatorModule,
|
||||||
|
InputGroupModule,
|
||||||
|
TooltipModule,
|
||||||
|
],
|
||||||
|
templateUrl: './business-listings.component.html',
|
||||||
|
styleUrl: './business-listings.component.scss',
|
||||||
|
})
|
||||||
|
export class BusinessListingsComponent {
|
||||||
|
environment = environment;
|
||||||
|
listings: Array<BusinessListing>;
|
||||||
|
filteredListings: Array<BusinessListing>;
|
||||||
|
criteria: ListingCriteria;
|
||||||
|
realEstateChecked: boolean;
|
||||||
|
maxPrice: string;
|
||||||
|
minPrice: string;
|
||||||
|
type: string;
|
||||||
|
states = [];
|
||||||
|
state: string;
|
||||||
|
totalRecords: number = 0;
|
||||||
|
ts = new Date().getTime();
|
||||||
|
first: number = 0;
|
||||||
|
rows: number = 12;
|
||||||
|
public category: 'business' | 'commercialProperty' | 'professionals_brokers' | undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public selectOptions: SelectOptionsService,
|
||||||
|
private listingsService: ListingsService,
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private cdRef: ChangeDetectorRef,
|
||||||
|
private imageService: ImageService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
||||||
|
this.criteria.type = undefined;
|
||||||
|
this.route.data.subscribe(async () => {
|
||||||
|
if (this.router.getCurrentNavigation().extras.state) {
|
||||||
|
resetCriteria(this.criteria);
|
||||||
|
}
|
||||||
|
this.init();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async ngOnInit() {}
|
||||||
|
async init() {
|
||||||
|
const statesResult = await this.listingsService.getAllStates('business');
|
||||||
|
this.states = statesResult.map(ls => ({ name: this.selectOptions.getState(ls.state as string), value: ls.state, count: ls.count }));
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
refine() {
|
||||||
|
this.criteria.start = 0;
|
||||||
|
this.criteria.page = 0;
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
async search() {
|
||||||
|
const listingReponse = await this.listingsService.getListings(this.criteria, 'business');
|
||||||
|
this.listings = listingReponse.data;
|
||||||
|
this.totalRecords = listingReponse.total;
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
this.cdRef.detectChanges();
|
||||||
|
}
|
||||||
|
onPageChange(event: any) {
|
||||||
|
this.criteria.start = event.first;
|
||||||
|
this.criteria.length = event.rows;
|
||||||
|
this.criteria.page = event.page;
|
||||||
|
this.criteria.pageCount = event.pageCount;
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
imageErrorHandler(listing: ListingType) {
|
||||||
|
// listing.hideImage = true; // Bild ausblenden, wenn es nicht geladen werden kann
|
||||||
|
}
|
||||||
|
reset() {
|
||||||
|
this.criteria.title = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<div id="sky-line" class="hidden-lg-down"></div>
|
||||||
|
<div class="search">
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="grid p-4 align-items-center">
|
||||||
|
<div class="col-2">
|
||||||
|
<p-dropdown [filter]="true" filterBy="name" [options]="states" [(ngModel)]="criteria.state" optionLabel="name" optionValue="value" [showClear]="true" placeholder="Location" [style]="{ width: '100%' }">
|
||||||
|
<ng-template let-state pTemplate="item">
|
||||||
|
<div class="flex align-items-center gap-2">
|
||||||
|
<div>{{ state.name }} ({{ state.count }})</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<p-dropdown
|
||||||
|
[filter]="true"
|
||||||
|
filterBy="name"
|
||||||
|
[options]="selectOptions.typesOfCommercialProperty"
|
||||||
|
[(ngModel)]="criteria.type"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="value"
|
||||||
|
[showClear]="true"
|
||||||
|
placeholder="Categorie of Property"
|
||||||
|
[style]="{ width: '100%' }"
|
||||||
|
></p-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<p-dropdown [options]="selectOptions.prices" [(ngModel)]="criteria.minPrice" optionLabel="name" optionValue="value" [showClear]="true" placeholder="Min Price" [style]="{ width: '100%' }"></p-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<p-dropdown [options]="selectOptions.prices" [(ngModel)]="criteria.maxPrice" optionLabel="name" optionValue="value" [showClear]="true" placeholder="Max Price" [style]="{ width: '100%' }"></p-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<p-inputGroup>
|
||||||
|
<input id="name" type="text" pInputText [(ngModel)]="criteria.title" placeholder="Title" />
|
||||||
|
<button type="button" pButton icon="pi pi-times" class="p-button-secondary" (click)="reset()"></button>
|
||||||
|
</p-inputGroup>
|
||||||
|
</div>
|
||||||
|
<div class="col-1 col-offset-1">
|
||||||
|
<p-button label="Refine" (click)="refine()"></p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="surface-200 h-full">
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="grid">
|
||||||
|
@for (listing of listings; track listing.id) {
|
||||||
|
<div class="col-12 xl:col-4 flex">
|
||||||
|
<div class="surface-card p-2 flex flex-column flex-grow-1 justify-content-between" style="border-radius: 10px">
|
||||||
|
<article class="flex flex-column md:flex-row w-full gap-3 p-3 surface-card">
|
||||||
|
<div class="relative">
|
||||||
|
@if (listing.imageOrder?.length>0){
|
||||||
|
<img src="pictures/property/{{ listing.imagePath }}/{{ listing.imageOrder[0] }}" alt="Image" class="border-round w-full h-full md:w-12rem md:h-9rem" />
|
||||||
|
} @else {
|
||||||
|
<img src="assets/images/placeholder_properties.jpg" alt="Image" class="border-round w-full h-full md:w-12rem md:h-9rem" />
|
||||||
|
}
|
||||||
|
<p class="absolute px-2 py-1 border-round-lg text-sm font-normal text-white mt-0 mb-0" style="background-color: rgba(255, 255, 255, 0.3); backdrop-filter: invert(30%); top: 3%; left: 3%">
|
||||||
|
{{ selectOptions.getState(listing.state) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-column w-full gap-3">
|
||||||
|
<div class="flex w-full justify-content-between align-items-center flex-wrap gap-3">
|
||||||
|
<p class="font-semibold text-lg mt-0 mb-0">{{ listing.title }}</p>
|
||||||
|
<!-- <p-rating [ngModel]="val1" readonly="true" stars="5" [cancel]="false" ngClass="flex-shrink-0"></p-rating> -->
|
||||||
|
</div>
|
||||||
|
<p class="font-normal text-lg text-600 mt-0 mb-0">{{ listing.city }}</p>
|
||||||
|
<div class="flex flex-wrap justify-content-between xl:h-2rem mt-auto">
|
||||||
|
<p class="text-base flex align-items-center text-900 mt-0 mb-1">
|
||||||
|
<i class="pi pi-list mr-2"></i>
|
||||||
|
<span class="font-medium">{{ selectOptions.getCommercialProperty(listing.type) }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p class="font-semibold text-3xl text-900 mt-0 mb-2">{{ listing.price | currency }}</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<div class="px-4 py-3 text-left">
|
||||||
|
<button
|
||||||
|
pButton
|
||||||
|
pRipple
|
||||||
|
icon="pi pi-arrow-right"
|
||||||
|
iconPos="right"
|
||||||
|
label="View Full Listing"
|
||||||
|
class="p-button-rounded p-button-success"
|
||||||
|
[routerLink]="['/details-commercial-property-listing', listing.id]"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2 surface-200 flex align-items-center justify-content-center">
|
||||||
|
<!-- @if(listings && listings.length>12){ -->
|
||||||
|
<div class="mx-1 text-color">Total number of Listings: {{ totalRecords }}</div>
|
||||||
|
<p-paginator (onPageChange)="onPageChange($event)" [first]="first" [rows]="rows" [totalRecords]="totalRecords" [rowsPerPageOptions]="[12, 24, 48]"></p-paginator>
|
||||||
|
<!-- } -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#sky-line {
|
||||||
|
background-image: url(../../../../assets/images/bw-sky.jpg);
|
||||||
|
height: 204px;
|
||||||
|
background-position: bottom;
|
||||||
|
background-size: cover;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
|
.search{
|
||||||
|
background-color: #343F69;
|
||||||
|
}
|
||||||
|
::ng-deep p-paginator div {
|
||||||
|
background-color: var(--surface-200) !important;
|
||||||
|
// background-color: var(--surface-400) !important;
|
||||||
|
}
|
||||||
|
.rounded-image {
|
||||||
|
border-radius: 6px;
|
||||||
|
// width: 100px;
|
||||||
|
max-width: 100px;
|
||||||
|
height: 45px;
|
||||||
|
border: 1px solid rgba(0,0,0,0.2);
|
||||||
|
padding: 1px 1px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||||
|
import onChange from 'on-change';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { CheckboxModule } from 'primeng/checkbox';
|
||||||
|
import { DropdownModule } from 'primeng/dropdown';
|
||||||
|
import { InputGroupModule } from 'primeng/inputgroup';
|
||||||
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { PaginatorModule } from 'primeng/paginator';
|
||||||
|
import { StyleClassModule } from 'primeng/styleclass';
|
||||||
|
import { ToggleButtonModule } from 'primeng/togglebutton';
|
||||||
|
import { CommercialPropertyListing } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
|
import { ListingCriteria } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { ImageService } from '../../../services/image.service';
|
||||||
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
|
import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from '../../../utils/utils';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-commercial-property-listings',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, StyleClassModule, ButtonModule, CheckboxModule, InputTextModule, DropdownModule, FormsModule, StyleClassModule, ToggleButtonModule, RouterModule, PaginatorModule, InputGroupModule],
|
||||||
|
templateUrl: './commercial-property-listings.component.html',
|
||||||
|
styleUrl: './commercial-property-listings.component.scss',
|
||||||
|
})
|
||||||
|
export class CommercialPropertyListingsComponent {
|
||||||
|
environment = environment;
|
||||||
|
listings: Array<CommercialPropertyListing>;
|
||||||
|
filteredListings: Array<CommercialPropertyListing>;
|
||||||
|
criteria: ListingCriteria;
|
||||||
|
realEstateChecked: boolean;
|
||||||
|
first: number = 0;
|
||||||
|
rows: number = 12;
|
||||||
|
maxPrice: string;
|
||||||
|
minPrice: string;
|
||||||
|
type: string;
|
||||||
|
states = [];
|
||||||
|
statesSet = new Set();
|
||||||
|
state: string;
|
||||||
|
totalRecords: number = 0;
|
||||||
|
ts = new Date().getTime();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public selectOptions: SelectOptionsService,
|
||||||
|
private listingsService: ListingsService,
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private cdRef: ChangeDetectorRef,
|
||||||
|
private imageService: ImageService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
||||||
|
this.criteria.type = undefined;
|
||||||
|
this.route.data.subscribe(async () => {
|
||||||
|
if (this.router.getCurrentNavigation().extras.state) {
|
||||||
|
resetCriteria(this.criteria);
|
||||||
|
}
|
||||||
|
this.init();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async ngOnInit() {}
|
||||||
|
async init() {
|
||||||
|
const statesResult = await this.listingsService.getAllStates('commercialProperty');
|
||||||
|
this.states = statesResult.map(ls => ({ name: this.selectOptions.getState(ls.state as string), value: ls.state, count: ls.count }));
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
refine() {
|
||||||
|
this.criteria.start = 0;
|
||||||
|
this.criteria.page = 0;
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
async search() {
|
||||||
|
const listingReponse = await this.listingsService.getListings(this.criteria, 'commercialProperty');
|
||||||
|
this.listings = listingReponse.data;
|
||||||
|
this.totalRecords = listingReponse.total;
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
this.cdRef.detectChanges();
|
||||||
|
}
|
||||||
|
onPageChange(event: any) {
|
||||||
|
this.criteria.start = event.first;
|
||||||
|
this.criteria.length = event.rows;
|
||||||
|
this.criteria.page = event.page;
|
||||||
|
this.criteria.pageCount = event.pageCount;
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
reset() {
|
||||||
|
this.criteria.title = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
<div id="sky-line" class="hidden-lg-down">
|
|
||||||
</div>
|
|
||||||
<div class="search">
|
|
||||||
<div class="wrapper">
|
|
||||||
<div class="grid p-4 align-items-center">
|
|
||||||
@if (category==='business'){
|
|
||||||
<div class="col-2">
|
|
||||||
<p-dropdown [options]="selectOptions.typesOfBusiness" [(ngModel)]="criteria.type" optionLabel="name"
|
|
||||||
optionValue="value" [showClear]="true" placeholder="Categorie of Business"
|
|
||||||
[style]="{ width: '100%'}"></p-dropdown>
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<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"
|
|
||||||
optionValue="value" [showClear]="true" placeholder="Min Price"
|
|
||||||
[style]="{ width: '100%'}"></p-dropdown>
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<p-dropdown [options]="selectOptions.prices" [(ngModel)]="criteria.maxPrice" optionLabel="name"
|
|
||||||
optionValue="value" [showClear]="true" placeholder="Max Price"
|
|
||||||
[style]="{ width: '100%'}"></p-dropdown>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<!-- <p-toggleButton [(ngModel)]="checked1" onLabel="Sustainable" offLabel="Unsustainable" onIcon="pi pi-check" offIcon="pi pi-times" styleClass="mb-3 lg:mt-0 mr-4 flex-shrink-0 w-12rem"></p-toggleButton> -->
|
|
||||||
<p-toggleButton [(ngModel)]="criteria.realEstateChecked" onLabel="Real Estate not included"
|
|
||||||
offLabel="Real Estate included"></p-toggleButton>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (category==='commercialProperty'){
|
|
||||||
<div class="col-2">
|
|
||||||
<p-dropdown [options]="states" [(ngModel)]="criteria.state" optionLabel="name" optionValue="value"
|
|
||||||
[showClear]="true" placeholder="Location" [style]="{ width: '100%'}"></p-dropdown>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<!-- @if (listingCategory==='professionals_brokers'){ -->
|
|
||||||
<!-- <div class="col-2">
|
|
||||||
<p-dropdown [options]="selectOptions.categories" [(ngModel)]="criteria.category" optionLabel="name"
|
|
||||||
optionValue="value" [showClear]="true" placeholder="Category"
|
|
||||||
[style]="{ width: '100%'}"></p-dropdown>
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<p-dropdown [options]="states" [(ngModel)]="criteria.state" optionLabel="name" optionValue="value"
|
|
||||||
[showClear]="true" placeholder="Location" [style]="{ width: '100%'}"></p-dropdown>
|
|
||||||
</div> -->
|
|
||||||
<!-- } -->
|
|
||||||
<div [ngClass]="{'col-offset-9':type==='commercialProperty','col-offset-7':type==='professionals_brokers'}" class="col-1">
|
|
||||||
<p-button label="Refine" (click)="search()"></p-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="surface-200 h-full">
|
|
||||||
<div class="wrapper">
|
|
||||||
<div class="grid">
|
|
||||||
@for (listing of filteredListings; track listing.id) {
|
|
||||||
<div *ngIf="listing.listingsCategory==='business'" class="col-12 lg:col-3 p-3">
|
|
||||||
<div class="shadow-2 border-round surface-card mb-3 h-full flex-column justify-content-between flex">
|
|
||||||
<div class="p-4 h-full flex flex-column">
|
|
||||||
<div class="flex align-items-center">
|
|
||||||
<span [class]="selectOptions.getBgColorType(listing.type)"
|
|
||||||
class="inline-flex border-circle align-items-center justify-content-center mr-3"
|
|
||||||
style="width:38px;height:38px">
|
|
||||||
<i [class]="selectOptions.getIconAndTextColorType(listing.type)" class="pi text-xl"></i>
|
|
||||||
</span>
|
|
||||||
<span class="text-900 font-medium text-2xl">{{selectOptions.getBusiness(listing.type)}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-900 my-3 text-xl font-medium">{{listing.title}}</div>
|
|
||||||
<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.getState(listing.state)}}</p>
|
|
||||||
<p class="mt-0 mb-1 text-700 line-height-3">Established: {{listing.established}}</p>
|
|
||||||
<div class="mt-auto ml-auto">
|
|
||||||
<img *ngIf="!listing.hideImage" src="{{environment.apiBaseUrl}}/logo/{{listing.userId}}" (error)="imageErrorHandler(listing)" class="rounded-image"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="px-4 py-3 surface-100 text-left">
|
|
||||||
<button pButton pRipple icon="pi pi-arrow-right" iconPos="right" label="View Full Listing"
|
|
||||||
class="p-button-rounded p-button-success" [routerLink]="['/details-listing/business',listing.id]"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@for (listing of filteredListings; track listing.id) {
|
|
||||||
<div *ngIf="listing.listingsCategory==='commercialProperty'" class="col-12 xl:col-4 flex">
|
|
||||||
<div class="surface-card p-2 flex flex-column flex-grow-1 justify-content-between" style="border-radius: 10px">
|
|
||||||
<article class="flex flex-column md:flex-row w-full gap-3 p-3 surface-card">
|
|
||||||
<div class="relative">
|
|
||||||
@if (listing.imageOrder.length>0){
|
|
||||||
<img src="{{environment.apiBaseUrl}}/property/{{listing.id}}/{{listing.imageOrder[0].name}}" alt="Image" class="border-round w-full h-full md:w-12rem md:h-9rem">
|
|
||||||
} @else {
|
|
||||||
<!-- <img src="{{environment.apiBaseUrl}}/property/{{listing.id}}/{{listing.imageOrder[0].name}}" alt="Image" class="border-round w-full h-full md:w-12rem md:h-9rem"> -->
|
|
||||||
<img src="assets/images/placeholder_properties.jpg" alt="Image" class="border-round w-full h-full md:w-12rem md:h-9rem" />
|
|
||||||
}
|
|
||||||
<p class="absolute px-2 py-1 border-round-lg text-sm font-normal text-white mt-0 mb-0" style="background-color: rgba(255, 255, 255, 0.3); backdrop-filter: invert(30%);; top: 3%; left: 3%;">{{selectOptions.getState(listing.state)}}</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-column w-full gap-3">
|
|
||||||
<div class="flex w-full justify-content-between align-items-center flex-wrap gap-3">
|
|
||||||
<p class="font-semibold text-lg mt-0 mb-0">{{listing.title}}</p>
|
|
||||||
<!-- <p-rating [ngModel]="val1" readonly="true" stars="5" [cancel]="false" ngClass="flex-shrink-0"></p-rating> -->
|
|
||||||
</div>
|
|
||||||
<p class="font-normal text-lg text-600 mt-0 mb-0">{{listing.city}}</p>
|
|
||||||
<div class="flex flex-wrap justify-content-between xl:h-2rem mt-auto">
|
|
||||||
<p class="text-base flex align-items-center text-900 mt-0 mb-1">
|
|
||||||
<i class="pi pi-list mr-2"></i>
|
|
||||||
<span class="font-medium">{{selectOptions.getCommercialProperty(listing.type)}}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<p class="font-semibold text-3xl text-900 mt-0 mb-2">{{listing.price | currency}}</p>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
<div class="px-4 py-3 text-left ">
|
|
||||||
<button pButton pRipple icon="pi pi-arrow-right" iconPos="right" label="View Full Listing"
|
|
||||||
class="p-button-rounded p-button-success" [routerLink]="['/details-listing/commercialProperty',listing.id]"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
}
|
|
||||||
@for (user of users; track user.id) {
|
|
||||||
<div class="col-12 lg:col-6 xl:col-4 p-4 flex flex-column flex-grow-1">
|
|
||||||
<div class="surface-card shadow-2 p-2 flex flex-column flex-grow-1 justify-content-between" style="border-radius: 10px">
|
|
||||||
<div class="surface-card p-4 flex flex-column align-items-center md:flex-row md:align-items-stretch h-full" >
|
|
||||||
<span>
|
|
||||||
@if(user.hasProfile){
|
|
||||||
<img src="{{environment.apiBaseUrl}}/profile/{{user.id}}.avif?_ts={{ts}}" class="w-5rem" />
|
|
||||||
} @else {
|
|
||||||
<img src="assets/images/person_placeholder.jpg" class="w-5rem" />
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
<div class="flex flex-column align-items-center md:align-items-stretch ml-4 mt-4 md:mt-0">
|
|
||||||
<p class="mt-0 mb-3 line-height-3 text-center md:text-left">{{user.description}}</p>
|
|
||||||
<span class="text-900 font-medium mb-1 mt-auto">{{user.firstname}} {{user.lastname}}</span>
|
|
||||||
<div class="text-600 text-sm">{{user.companyName}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="px-4 py-3 text-right flex justify-content-between align-items-center">
|
|
||||||
@if(user.hasCompanyLogo){
|
|
||||||
<img src="{{environment.apiBaseUrl}}/logo/{{user.id}}.avif" class="rounded-image"/>
|
|
||||||
} @else {
|
|
||||||
<img src="assets/images/placeholder.png" class="rounded-image"/>
|
|
||||||
}
|
|
||||||
<button pButton pRipple icon="pi pi-arrow-right" iconPos="right" label="View Full profile"
|
|
||||||
class="p-button-rounded p-button-success" [routerLink]="['/details-user',user.id]"></button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="mb-2 surface-200 flex align-items-center justify-content-center">
|
|
||||||
<div class="mx-1 text-color">Total number of Listings: {{totalRecords}}</div>
|
|
||||||
<p-paginator (onPageChange)="onPageChange($event)" [first]="first" [rows]="rows" [totalRecords]="totalRecords" [rowsPerPageOptions]="[12, 24, 48]" ></p-paginator>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
|
||||||
import { ButtonModule } from 'primeng/button';
|
|
||||||
import { CheckboxModule } from 'primeng/checkbox';
|
|
||||||
import { InputTextModule } from 'primeng/inputtext';
|
|
||||||
import { StyleClassModule } from 'primeng/styleclass';
|
|
||||||
import { SelectOptionsService } from '../../services/select-options.service';
|
|
||||||
import { DropdownModule } from 'primeng/dropdown';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { ToggleButtonModule } from 'primeng/togglebutton';
|
|
||||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
|
||||||
import { ListingsService } from '../../services/listings.service';
|
|
||||||
import { Observable, lastValueFrom } from 'rxjs';
|
|
||||||
import { PaginatorModule } from 'primeng/paginator';
|
|
||||||
import onChange from 'on-change';
|
|
||||||
import { createGenericObject, getCriteriaStateObject, getSessionStorageHandler } from '../../utils/utils';
|
|
||||||
import { InitEditableRow } from 'primeng/table';
|
|
||||||
import { environment } from '../../../environments/environment';
|
|
||||||
import { ListingCriteria, ListingType, User } from '../../../../../common-models/src/main.model';
|
|
||||||
import { UserService } from '../../services/user.service';
|
|
||||||
import { ImageService } from '../../services/image.service';
|
|
||||||
@Component({
|
|
||||||
selector: 'app-listings',
|
|
||||||
standalone: true,
|
|
||||||
imports: [CommonModule, StyleClassModule, ButtonModule, CheckboxModule, InputTextModule, DropdownModule, FormsModule, StyleClassModule, ToggleButtonModule, RouterModule, PaginatorModule],
|
|
||||||
templateUrl: './listings.component.html',
|
|
||||||
styleUrls: ['./listings.component.scss', '../pages.scss']
|
|
||||||
})
|
|
||||||
export class ListingsComponent {
|
|
||||||
environment=environment;
|
|
||||||
listings: Array<ListingType>;
|
|
||||||
users: Array<User>
|
|
||||||
filteredListings: Array<ListingType>;
|
|
||||||
criteria:ListingCriteria;
|
|
||||||
realEstateChecked: boolean;
|
|
||||||
// category: string;
|
|
||||||
maxPrice: string;
|
|
||||||
minPrice: string;
|
|
||||||
type:string;
|
|
||||||
states = [];
|
|
||||||
statesSet = new Set();
|
|
||||||
state:string;
|
|
||||||
first: number = 0;
|
|
||||||
rows: number = 12;
|
|
||||||
totalRecords:number = 0;
|
|
||||||
ts = new Date().getTime()
|
|
||||||
public category: 'business' | 'commercialProperty' | 'professionals_brokers' | undefined;
|
|
||||||
|
|
||||||
constructor(public selectOptions: SelectOptionsService,
|
|
||||||
private listingsService:ListingsService,
|
|
||||||
private userService:UserService,
|
|
||||||
private activatedRoute: ActivatedRoute,
|
|
||||||
private router:Router,
|
|
||||||
private cdRef:ChangeDetectorRef,
|
|
||||||
private imageService:ImageService) {
|
|
||||||
this.criteria = onChange(getCriteriaStateObject(),getSessionStorageHandler);
|
|
||||||
this.router.getCurrentNavigation()
|
|
||||||
this.activatedRoute.snapshot
|
|
||||||
this.activatedRoute.params.subscribe(params => {
|
|
||||||
if (this.activatedRoute.snapshot.fragment===''){
|
|
||||||
this.criteria = onChange(createGenericObject<ListingCriteria>(),getSessionStorageHandler)
|
|
||||||
this.first=0;
|
|
||||||
}
|
|
||||||
this.category = (<any>params).type;
|
|
||||||
this.criteria.listingsCategory=this.category;
|
|
||||||
this.init()
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
async ngOnInit(){
|
|
||||||
}
|
|
||||||
async init(){
|
|
||||||
if (this.category==='business' || this.category==='commercialProperty'){
|
|
||||||
this.users=[]
|
|
||||||
this.listings=await this.listingsService.getListings(this.criteria);
|
|
||||||
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();
|
|
||||||
} else {
|
|
||||||
this.listings=[]
|
|
||||||
this.filteredListings=[];
|
|
||||||
this.users=await this.userService.search(this.criteria);
|
|
||||||
const profiles = await this.imageService.getProfileImagesForUsers(this.users.map(u=>u.id));
|
|
||||||
const logos = await this.imageService.getCompanyLogosForUsers(this.users.map(u=>u.id));
|
|
||||||
this.users.forEach(u=>{
|
|
||||||
u.hasProfile=profiles[u.id]
|
|
||||||
u.hasCompanyLogo=logos[u.id]
|
|
||||||
})
|
|
||||||
this.cdRef.markForCheck();
|
|
||||||
this.cdRef.detectChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
setStates(){
|
|
||||||
this.statesSet=new Set();
|
|
||||||
this.listings.forEach(l=>{
|
|
||||||
if (l.state){
|
|
||||||
this.statesSet.add(l.state)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
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.setStates();
|
|
||||||
this.totalRecords=this.listings.length
|
|
||||||
this.filteredListings =[...this.listings].splice(this.first,this.rows);
|
|
||||||
this.cdRef.markForCheck();
|
|
||||||
this.cdRef.detectChanges();
|
|
||||||
}
|
|
||||||
onPageChange(event: any) {
|
|
||||||
this.first = event.first;
|
|
||||||
this.rows = event.rows;
|
|
||||||
this.filteredListings=[...this.listings].splice(this.first,this.rows);
|
|
||||||
}
|
|
||||||
imageErrorHandler(listing: ListingType) {
|
|
||||||
listing.hideImage = true; // Bild ausblenden, wenn es nicht geladen werden kann
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,42 +1,62 @@
|
|||||||
<div class="surface-ground ">
|
<div class="surface-ground">
|
||||||
<div class="p-fluid flex flex-column lg:flex-row">
|
<div class="p-fluid flex flex-column lg:flex-row">
|
||||||
<ul class="list-none m-0 p-0 flex flex-row lg:flex-column justify-content-evenly md:justify-content-between lg:justify-content-start mb-5 lg:pr-8 lg:mb-0">
|
<ul class="list-none m-0 p-0 flex flex-row lg:flex-column justify-content-evenly md:justify-content-between lg:justify-content-start mb-5 lg:pr-8 lg:mb-0">
|
||||||
<li>
|
<li>
|
||||||
<a routerLink="/account" routerLinkActive="text-blue-500" pRipple class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline" >
|
<a routerLink="/account" routerLinkActive="text-blue-500" pRipple class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline">
|
||||||
<i class="pi pi-user md:mr-2"></i>
|
<i class="pi pi-user md:mr-2"></i>
|
||||||
<span class="font-medium hidden md:block">Account</span>
|
<span class="font-medium hidden md:block">Account</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a routerLink="/createListing" routerLinkActive="text-blue-500" pRipple class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline">
|
<a
|
||||||
<i class="pi pi-plus-circle md:mr-2"></i>
|
routerLink="/createBusinessListing"
|
||||||
<span class="font-medium hidden md:block">Create Listing</span>
|
routerLinkActive="text-blue-500"
|
||||||
</a>
|
pRipple
|
||||||
</li>
|
class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline"
|
||||||
<li>
|
>
|
||||||
<a routerLink="/myListings" routerLinkActive="text-blue-500" pRipple class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline">
|
<i class="pi pi-plus-circle md:mr-2"></i>
|
||||||
<i class="pi pi-list md:mr-2"></i>
|
<span class="font-medium hidden md:block">Create Listing</span>
|
||||||
<span class="font-medium hidden md:block">My Listings</span>
|
</a>
|
||||||
</a>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<a
|
||||||
<a routerLink="/myFavorites" routerLinkActive="text-blue-500" pRipple class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline">
|
routerLink="/myListings"
|
||||||
<i class="pi pi-star md:mr-2"></i>
|
routerLinkActive="text-blue-500"
|
||||||
<span class="font-medium hidden md:block">My Favorites</span>
|
pRipple
|
||||||
</a>
|
class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline"
|
||||||
</li>
|
>
|
||||||
<li>
|
<i class="pi pi-list md:mr-2"></i>
|
||||||
<a routerLink="/emailUs" routerLinkActive="text-blue-500" pRipple class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline">
|
<span class="font-medium hidden md:block">My Listings</span>
|
||||||
<fa-icon [icon]="faEnvelope" class="mr-2 flex"></fa-icon>
|
</a>
|
||||||
<span class="font-medium hidden md:block">Email Us</span>
|
</li>
|
||||||
</a>
|
<li>
|
||||||
</li>
|
<a
|
||||||
<li>
|
routerLink="/myFavorites"
|
||||||
<a (click)="userService.logout()" routerLinkActive="text-blue-500" pRipple class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline">
|
routerLinkActive="text-blue-500"
|
||||||
<fa-icon [icon]="faRightFromBracket" class="mr-2 flex"></fa-icon>
|
pRipple
|
||||||
<span class="font-medium hidden md:block">Logout</span>
|
class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline"
|
||||||
</a>
|
>
|
||||||
</li>
|
<i class="pi pi-star md:mr-2"></i>
|
||||||
</ul>
|
<span class="font-medium hidden md:block">My Favorites</span>
|
||||||
</div>
|
</a>
|
||||||
</div>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a routerLink="/emailUs" routerLinkActive="text-blue-500" pRipple class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline">
|
||||||
|
<fa-icon [icon]="faEnvelope" class="mr-2 flex"></fa-icon>
|
||||||
|
<span class="font-medium hidden md:block">Email Us</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
(click)="userService.logout()"
|
||||||
|
routerLinkActive="text-blue-500"
|
||||||
|
pRipple
|
||||||
|
class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline"
|
||||||
|
>
|
||||||
|
<fa-icon [icon]="faRightFromBracket" class="mr-2 flex"></fa-icon>
|
||||||
|
<span class="font-medium hidden md:block">Logout</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,85 +1,92 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="py-3 px-6 flex flex-column align-items-center justify-content-between relative">
|
<div class="py-3 px-6 flex flex-column align-items-center justify-content-between relative">
|
||||||
<a routerLink="/home"><img src="../../../assets/images/header-logo.png" alt="Image" height="50" ></a>
|
<a routerLink="/home"><img src="../../../assets/images/header-logo.png" alt="Image" height="50" /></a>
|
||||||
<div class="px-4 py-8 md:px-6 lg:px-8 bg-no-repeat bg-cover" >
|
<div class="px-4 py-8 md:px-6 lg:px-8 bg-no-repeat bg-cover">
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
<div class="w-full lg:w-6 lg:pr-8">
|
<div class="w-full lg:w-6 lg:pr-8">
|
||||||
<div class="text-900 font-bold text-6xl text-blue-900 mb-4">Pricing</div>
|
<div class="text-900 font-bold text-6xl text-blue-900 mb-4">Pricing</div>
|
||||||
<div class="text-700 text-xl text-blue-600 line-height-3 mb-4 lg:mb-0">Lorem ipsum dolor sit, amet consectetur adipisicing elit. Velitnumquam eligendi quos.</div>
|
<div class="text-700 text-xl text-blue-600 line-height-3 mb-4 lg:mb-0">
|
||||||
</div>
|
With the "Forever Free" package, you can get started right away. If you need more support or a larger data volume, you can upgrade to the "Monthly" or "Yearly" package at any time.
|
||||||
<div class="w-full md:w-6 lg:w-3">
|
|
||||||
<ul class="list-none p-0 m-0">
|
|
||||||
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
|
|
||||||
<i class="pi pi-check text-green-500 mr-3"></i>
|
|
||||||
<span>Arcu vitae elementum</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
|
|
||||||
<i class="pi pi-check text-green-500 mr-3"></i>
|
|
||||||
<span>Dui faucibus in ornare</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
|
|
||||||
<i class="pi pi-check text-green-500 mr-3"></i>
|
|
||||||
<span>Morbi tincidunt augue</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="w-full md:w-6 lg:w-3 md:pl-5">
|
|
||||||
<ul class="list-none p-0 m-0">
|
|
||||||
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
|
|
||||||
<i class="pi pi-check text-green-500 mr-3"></i>
|
|
||||||
<span>Duis ultricies lacus sed</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
|
|
||||||
<i class="pi pi-check text-green-500 mr-3"></i>
|
|
||||||
<span>Imperdiet proin</span>
|
|
||||||
</li>
|
|
||||||
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
|
|
||||||
<i class="pi pi-check text-green-500 mr-3"></i>
|
|
||||||
<span>Nisi scelerisque</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-wrap mt-5 -mx-3">
|
|
||||||
<div class="w-full lg:w-4 p-3">
|
|
||||||
<div class="shadow-2 p-3 h-full bg-primary" style="border-radius: 6px">
|
|
||||||
<div class="font-medium text-xl mb-5">Free Forever</div>
|
|
||||||
<div class="font-bold text-5xl mb-5">Free</div>
|
|
||||||
<button (click)="register()" type="button" pRipple class="font-medium appearance-none border-none p-2 surface-0 text-primary hover:surface-100 p-component lg:w-full border-rounded cursor-pointer transition-colors transition-duration-150" style="border-radius: 6px">
|
|
||||||
<span>Create Account</span>
|
|
||||||
</button>
|
|
||||||
<p class="text-sm line-height-3 mb-0 mt-5">Lorem ipsum dolor sit, amet consectetur adipisicing elit.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-full lg:w-4 p-3">
|
|
||||||
<div class="shadow-2 p-3 h-full surface-card" style="border-radius: 6px">
|
|
||||||
<div class="font-medium text-xl mb-5 text-900 ">Monthly</div>
|
|
||||||
<div class="flex align-items-center mb-5">
|
|
||||||
<span class="text-900 font-bold text-5xl">$29</span>
|
|
||||||
<span class="font-medium text-500 ml-2">per month</span>
|
|
||||||
</div>
|
|
||||||
<button (click)="register()" pButton pRipple label="Proceed Monthly" icon="pi pi-arrow-right" iconPos="right" class="lg:w-full font-medium p-2" style="border-radius: 6px"></button>
|
|
||||||
<p class="text-sm line-height-3 mb-0 mt-5">Nec ultrices dui sapien eget. Amet nulla facilisi morbi tempus.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-full lg:w-4 p-3">
|
|
||||||
<div class="shadow-2 p-3 h-full flex flex-column surface-card" style="border-radius: 6px">
|
|
||||||
<div class="flex flex-row justify-content-between mb-5 align-items-center">
|
|
||||||
<div class="text-900 text-xl font-medium">Yearly</div>
|
|
||||||
<span class="bg-orange-100 500 text-orange-500 font-semibold px-2 py-1 border-round">🎉 Save 20%</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex align-items-center mb-5">
|
|
||||||
<span class="text-900 font-bold text-5xl">$275</span>
|
|
||||||
<span class="font-medium text-500 ml-2">per year</span>
|
|
||||||
</div>
|
|
||||||
<button (click)="register()" pButton pRipple label="Proceed Yearly" icon="pi pi-arrow-right" iconPos="right" class="lg:w-full font-medium p-2" style="border-radius: 6px"></button>
|
|
||||||
<p class="text-sm line-height-3 mb-0 mt-5">Placerat in egestas erat imperdiet sed euismod nisi porta.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-6 lg:w-3">
|
||||||
|
<ul class="list-none p-0 m-0">
|
||||||
|
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
|
||||||
|
<i class="pi pi-check text-green-500 mr-3"></i>
|
||||||
|
<span>Flexible pricing</span>
|
||||||
|
</li>
|
||||||
|
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
|
||||||
|
<i class="pi pi-check text-green-500 mr-3"></i>
|
||||||
|
<span>Upgradeable plans</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-6 lg:w-3 md:pl-5">
|
||||||
|
<ul class="list-none p-0 m-0">
|
||||||
|
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
|
||||||
|
<i class="pi pi-check text-green-500 mr-3"></i>
|
||||||
|
<span>Customizable options</span>
|
||||||
|
</li>
|
||||||
|
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
|
||||||
|
<i class="pi pi-check text-green-500 mr-3"></i>
|
||||||
|
<span>Monthly/Yearly package</span>
|
||||||
|
</li>
|
||||||
|
<!-- <li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
|
||||||
|
<i class="pi pi-check text-green-500 mr-3"></i>
|
||||||
|
<span>Imperdiet proin</span>
|
||||||
|
</li>
|
||||||
|
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
|
||||||
|
<i class="pi pi-check text-green-500 mr-3"></i>
|
||||||
|
<span>Nisi scelerisque</span>
|
||||||
|
</li> -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-wrap mt-5 -mx-3">
|
||||||
|
<div class="w-full lg:w-4 p-3">
|
||||||
|
<div class="shadow-2 p-3 h-full bg-primary" style="border-radius: 6px">
|
||||||
|
<div class="font-medium text-xl mb-5">Free Forever</div>
|
||||||
|
<div class="font-bold text-5xl mb-5">Free</div>
|
||||||
|
<button
|
||||||
|
(click)="register()"
|
||||||
|
type="button"
|
||||||
|
pRipple
|
||||||
|
class="font-medium appearance-none border-none p-2 surface-0 text-primary hover:surface-100 p-component lg:w-full border-rounded cursor-pointer transition-colors transition-duration-150"
|
||||||
|
style="border-radius: 6px"
|
||||||
|
>
|
||||||
|
<span>Create Account</span>
|
||||||
|
</button>
|
||||||
|
<p class="text-sm line-height-3 mb-0 mt-5">Lorem ipsum dolor sit, amet consectetur adipisicing elit.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full lg:w-4 p-3">
|
||||||
|
<div class="shadow-2 p-3 h-full surface-card" style="border-radius: 6px">
|
||||||
|
<div class="font-medium text-xl mb-5 text-900">Monthly</div>
|
||||||
|
<div class="flex align-items-center mb-5">
|
||||||
|
<span class="text-900 font-bold text-5xl">$29</span>
|
||||||
|
<span class="font-medium text-500 ml-2">per month</span>
|
||||||
|
</div>
|
||||||
|
<button (click)="register()" pButton pRipple label="Proceed Monthly" icon="pi pi-arrow-right" iconPos="right" class="lg:w-full font-medium p-2" style="border-radius: 6px"></button>
|
||||||
|
<p class="text-sm line-height-3 mb-0 mt-5">Nec ultrices dui sapien eget. Amet nulla facilisi morbi tempus.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full lg:w-4 p-3">
|
||||||
|
<div class="shadow-2 p-3 h-full flex flex-column surface-card" style="border-radius: 6px">
|
||||||
|
<div class="flex flex-row justify-content-between mb-5 align-items-center">
|
||||||
|
<div class="text-900 text-xl font-medium">Yearly</div>
|
||||||
|
<span class="bg-orange-100 500 text-orange-500 font-semibold px-2 py-1 border-round">🎉 Save 20%</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex align-items-center mb-5">
|
||||||
|
<span class="text-900 font-bold text-5xl">$275</span>
|
||||||
|
<span class="font-medium text-500 ml-2">per year</span>
|
||||||
|
</div>
|
||||||
|
<button (click)="register()" pButton pRipple label="Proceed Yearly" icon="pi pi-arrow-right" iconPos="right" class="lg:w-full font-medium p-2" style="border-radius: 6px"></button>
|
||||||
|
<p class="text-sm line-height-3 mb-0 mt-5">Placerat in egestas erat imperdiet sed euismod nisi porta.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,212 +1,204 @@
|
|||||||
<div class="surface-ground px-4 py-8 md:px-6 lg:px-8">
|
<div class="surface-ground px-4 py-8 md:px-6 lg:px-8">
|
||||||
<div class="p-fluid flex flex-column lg:flex-row">
|
<div class="p-fluid flex flex-column lg:flex-row">
|
||||||
<menu-account></menu-account>
|
<menu-account></menu-account>
|
||||||
<p-toast></p-toast>
|
<p-toast></p-toast>
|
||||||
<div class="surface-card p-5 shadow-2 border-round flex-auto">
|
<div class="surface-card p-5 shadow-2 border-round flex-auto">
|
||||||
<div class="text-900 font-semibold text-lg mt-3">Account Details</div>
|
<div class="text-900 font-semibold text-lg mt-3">Account Details</div>
|
||||||
<p-divider></p-divider>
|
<p-divider></p-divider>
|
||||||
<div class="flex gap-5 flex-column-reverse md:flex-row">
|
<div class="flex gap-5 flex-column-reverse md:flex-row">
|
||||||
<div class="flex-auto p-fluid">
|
<div class="flex-auto p-fluid">
|
||||||
<!-- <div class="mb-4">
|
@if (user){
|
||||||
<label for="email" class="block font-medium text-900 mb-2">Username</label>
|
<div class="mb-4">
|
||||||
<input id="email" type="text" [disabled]="true" pInputText [(ngModel)]="user.username">
|
<label for="state" class="block font-medium text-900 mb-2">E-mail (required)</label>
|
||||||
<p class="font-italic text-sm line-height-1">Usernames cannot be changed.</p>
|
<input id="state" type="text" [disabled]="true" pInputText [(ngModel)]="user.email" />
|
||||||
</div> -->
|
<p class="font-italic text-sm line-height-1">You can only modify your email by contacting us at support@bizmatch.net</p>
|
||||||
<div class="mb-4">
|
</div>
|
||||||
<label for="state" class="block font-medium text-900 mb-2">E-mail (required)</label>
|
<div class="grid">
|
||||||
<input id="state" type="text" [disabled]="true" pInputText [(ngModel)]="user.email">
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<p class="font-italic text-sm line-height-1">You can only modify your email by contacting us at
|
<label for="firstname" class="block font-medium text-900 mb-2">First Name</label>
|
||||||
emailchange@bizmatch.net</p>
|
<input id="firstname" type="text" pInputText [(ngModel)]="user.firstname" />
|
||||||
</div>
|
|
||||||
<div class="grid">
|
|
||||||
<div class="mb-4 col-12 md:col-6">
|
|
||||||
<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 col-12 md:col-6">
|
|
||||||
<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>
|
|
||||||
<div class="grid">
|
|
||||||
<div class="mb-4 col-12 md:col-6">
|
|
||||||
<label for="firstname" class="block font-medium text-900 mb-2">Company Name</label>
|
|
||||||
<input id="firstname" type="text" pInputText [(ngModel)]="user.companyName">
|
|
||||||
</div>
|
|
||||||
<div class="mb-4 col-12 md:col-6">
|
|
||||||
<label for="lastname" class="block font-medium text-900 mb-2">Describe yourself</label>
|
|
||||||
<input id="lastname" type="text" pInputText [(ngModel)]="user.description">
|
|
||||||
</div>
|
|
||||||
</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>
|
|
||||||
<p-editor [(ngModel)]="user.companyOverview" [style]="{ height: '320px' }" [modules]="editorModules">
|
|
||||||
<ng-template pTemplate="header"></ng-template>
|
|
||||||
</p-editor>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<label for="companyOverview" class="block font-medium text-900 mb-2">Services We offer</label>
|
|
||||||
<p-editor [(ngModel)]="user.offeredServices" [style]="{ height: '320px' }" [modules]="editorModules">
|
|
||||||
<ng-template pTemplate="header"></ng-template>
|
|
||||||
</p-editor>
|
|
||||||
</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>
|
|
||||||
<button pButton pRipple label="Update Profile" class="w-auto"
|
|
||||||
(click)="updateProfile(user)"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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>
|
|
||||||
@if(user.hasCompanyLogo){
|
|
||||||
<img src="{{companyLogoUrl}}" class="rounded-profile" />
|
|
||||||
} @else {
|
|
||||||
<img src="assets/images/placeholder.png" class="rounded-profile" />
|
|
||||||
}
|
|
||||||
<p-fileUpload #companyUpload mode="basic" chooseLabel="Upload" name="file" [customUpload]="true"
|
|
||||||
accept="image/*" [maxFileSize]="maxFileSize" (onSelect)="select($event,'company')"
|
|
||||||
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>
|
|
||||||
@if(user.hasProfile){
|
|
||||||
<img src="{{profileUrl}}" class="rounded-profile" />
|
|
||||||
} @else {
|
|
||||||
<img src="assets/images/person_placeholder.jpg" class="rounded-profile" />
|
|
||||||
}
|
|
||||||
<p-fileUpload #profileUpload mode="basic" chooseLabel="Upload" name="file" [customUpload]="true"
|
|
||||||
accept="image/*" [maxFileSize]="maxFileSize" (onSelect)="select($event,'profile')"
|
|
||||||
styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"></p-fileUpload>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="text-900 font-semibold text-lg mt-3">Membership Level</div>
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<p-divider></p-divider>
|
<label for="lastname" class="block font-medium text-900 mb-2">Last Name</label>
|
||||||
<p-table [value]="userSubscriptions" [tableStyle]="{ 'min-width': '50rem' }" dataKey="id">
|
<input id="lastname" type="text" pInputText [(ngModel)]="user.lastname" />
|
||||||
<ng-template pTemplate="header">
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<th style="width: 5rem"></th>
|
<div class="grid">
|
||||||
<th>ID</th>
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<th>Level</th>
|
<label for="firstname" class="block font-medium text-900 mb-2">Company Name</label>
|
||||||
<th>Start Date</th>
|
<input id="firstname" type="text" pInputText [(ngModel)]="user.companyName" />
|
||||||
<th>Date Modified</th>
|
</div>
|
||||||
<th>End Date</th>
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<th>Status</th>
|
<label for="lastname" class="block font-medium text-900 mb-2">Describe yourself</label>
|
||||||
</tr>
|
<input id="lastname" type="text" pInputText [(ngModel)]="user.description" />
|
||||||
</ng-template>
|
</div>
|
||||||
<ng-template pTemplate="body" let-subscription let-expanded="expanded">
|
</div>
|
||||||
<tr>
|
<div class="grid">
|
||||||
<td>
|
<div class="mb-4 col-12 md:col-4">
|
||||||
<button type="button" pButton pRipple [pRowToggler]="subscription"
|
<label for="phoneNumber" class="block font-medium text-900 mb-2">Your Phone Number</label>
|
||||||
class="p-button-text p-button-rounded p-button-plain"
|
<input id="phoneNumber" type="text" pInputText [(ngModel)]="user.phoneNumber" />
|
||||||
[icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></button>
|
</div>
|
||||||
</td>
|
<div class="mb-4 col-12 md:col-4">
|
||||||
<td>{{ subscription.id }}</td>
|
<label for="companyWebsite" class="block font-medium text-900 mb-2">Company Website</label>
|
||||||
<td>{{ subscription.level }}</td>
|
<input id="companyWebsite" type="text" pInputText [(ngModel)]="user.companyWebsite" />
|
||||||
<td>{{ subscription.start | date }}</td>
|
</div>
|
||||||
<td>{{ subscription.modified | date }}</td>
|
<div class="mb-4 col-12 md:col-4">
|
||||||
<td>{{ subscription.end | date }}</td>
|
<label for="companyLocation" class="block font-medium text-900 mb-2">Company Location</label>
|
||||||
<td>{{ subscription.status }}</td>
|
<p-autoComplete [(ngModel)]="user.companyLocation" [suggestions]="suggestions" (completeMethod)="search($event)"></p-autoComplete>
|
||||||
</tr>
|
</div>
|
||||||
</ng-template>
|
</div>
|
||||||
<ng-template pTemplate="rowexpansion" let-subscription>
|
<div class="mb-4">
|
||||||
<tr>
|
<label for="companyOverview" class="block font-medium text-900 mb-2">Company Overview</label>
|
||||||
<td colspan="7">
|
<p-editor [(ngModel)]="user.companyOverview" [style]="{ height: '320px' }" [modules]="editorModules">
|
||||||
<div class="p-3">
|
<ng-template pTemplate="header"></ng-template>
|
||||||
<p-table [value]="subscription.invoices" dataKey="id">
|
</p-editor>
|
||||||
<ng-template pTemplate="header">
|
</div>
|
||||||
<tr>
|
<div class="mb-4">
|
||||||
<th style="width: 5rem"></th>
|
<label for="companyOverview" class="block font-medium text-900 mb-2">Services We offer</label>
|
||||||
<th>ID</th>
|
<p-editor [(ngModel)]="user.offeredServices" [style]="{ height: '320px' }" [modules]="editorModules">
|
||||||
<th>Date</th>
|
<ng-template pTemplate="header"></ng-template>
|
||||||
<th>Price</th>
|
</p-editor>
|
||||||
</tr>
|
</div>
|
||||||
</ng-template>
|
|
||||||
<ng-template pTemplate="body" let-invoice>
|
<div class="mb-4">
|
||||||
<tr>
|
<label for="areasServed" class="block font-medium text-900 mb-2">Areas We Serve</label>
|
||||||
<td>
|
<textarea id="areasServed" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="user.areasServed"></textarea>
|
||||||
<button pButton pRipple icon="pi pi-print" class="p-button-rounded p-button-success mr-2"
|
</div>
|
||||||
(click)="printInvoice(invoice)"></button>
|
<div>
|
||||||
</td>
|
<label for="companyOverview" class="block font-medium text-900 mb-2">Licensed In</label>
|
||||||
<td>{{ invoice.id }}</td>
|
@for (licensedIn of userLicensedIn; track licensedIn.value){
|
||||||
<td>{{ invoice.date | date}}</td>
|
<div class="grid">
|
||||||
<td>{{ invoice.price | currency}}</td>
|
<div class="flex col-12 md:col-6">
|
||||||
<td></td>
|
<p-dropdown
|
||||||
<td></td>
|
id="states"
|
||||||
</tr>
|
[options]="selectOptions?.states"
|
||||||
</ng-template>
|
[(ngModel)]="licensedIn.name"
|
||||||
</p-table>
|
optionLabel="name"
|
||||||
</div>
|
optionValue="value"
|
||||||
</td>
|
[showClear]="true"
|
||||||
</tr>
|
placeholder="State"
|
||||||
</ng-template>
|
[ngStyle]="{ width: '100%' }"
|
||||||
</p-table>
|
></p-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="flex col-12 md:col-6">
|
||||||
</div>
|
<input id="companyWebsite" type="text" pInputText [(ngModel)]="licensedIn.value" placeholder="Licence Number" />
|
||||||
<!-- <p-dialog header="Edit Image" [visible]="imageUrl" [modal]="true" [style]="{ width: '50vw' }" [draggable]="false" [resizable]="false">
|
</div>
|
||||||
<angular-cropper #cropper [imageUrl]="imageUrl" [cropperOptions]="config"></angular-cropper>
|
|
||||||
<ng-template pTemplate="footer" let-config="config">
|
|
||||||
<div class="flex justify-content-between">
|
|
||||||
@if(type==='company'){
|
|
||||||
<div>
|
|
||||||
<p-selectButton [options]="stateOptions" [ngModel]="value" (ngModelChange)="changeAspectRation($event)" optionLabel="label" optionValue="value"></p-selectButton>
|
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
|
||||||
<div></div>
|
|
||||||
}
|
}
|
||||||
<div>
|
</div>
|
||||||
<p-button icon="pi" (click)="cancelUpload()" label="Cancel" [outlined]="true"></p-button>
|
<div class="field mb-5 col-12 md:col-6 flex align-items-center">
|
||||||
<p-button icon="pi pi-check" (click)="sendImage()" label="Finish" pAutoFocus [autofocus]="true"></p-button>
|
<p-button class="mr-1" icon="pi pi-plus" severity="success" (click)="addLicence()"></p-button>
|
||||||
</div>
|
<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>
|
||||||
|
<button pButton pRipple label="Update Profile" class="w-auto" (click)="updateProfile(user)"></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
<div>
|
||||||
</p-dialog> -->
|
<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>
|
||||||
|
@if(user?.hasCompanyLogo){
|
||||||
|
<img src="{{ companyLogoUrl }}" class="rounded-profile" />
|
||||||
|
<!-- <img src="profile/{{ user.id }}.avif" class="rounded-profile" /> -->
|
||||||
|
} @else {
|
||||||
|
<img src="assets/images/placeholder.png" class="rounded-profile" />
|
||||||
|
}
|
||||||
|
<p-fileUpload
|
||||||
|
#companyUpload
|
||||||
|
mode="basic"
|
||||||
|
chooseLabel="Upload"
|
||||||
|
name="file"
|
||||||
|
[customUpload]="true"
|
||||||
|
accept="image/*"
|
||||||
|
[maxFileSize]="maxFileSize"
|
||||||
|
(onSelect)="select($event, 'company')"
|
||||||
|
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>
|
||||||
|
@if(user?.hasProfile){
|
||||||
|
<img src="{{ profileUrl }}" class="rounded-profile" />
|
||||||
|
} @else {
|
||||||
|
<img src="assets/images/person_placeholder.jpg" class="rounded-profile" />
|
||||||
|
}
|
||||||
|
<p-fileUpload
|
||||||
|
#profileUpload
|
||||||
|
mode="basic"
|
||||||
|
chooseLabel="Upload"
|
||||||
|
name="file"
|
||||||
|
[customUpload]="true"
|
||||||
|
accept="image/*"
|
||||||
|
[maxFileSize]="maxFileSize"
|
||||||
|
(onSelect)="select($event, 'profile')"
|
||||||
|
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>
|
||||||
|
<p-divider></p-divider>
|
||||||
|
<p-table [value]="userSubscriptions" [tableStyle]="{ 'min-width': '50rem' }" dataKey="id">
|
||||||
|
<ng-template pTemplate="header">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 5rem"></th>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Level</th>
|
||||||
|
<th>Start Date</th>
|
||||||
|
<th>Date Modified</th>
|
||||||
|
<th>End Date</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="body" let-subscription let-expanded="expanded">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<button type="button" pButton pRipple [pRowToggler]="subscription" class="p-button-text p-button-rounded p-button-plain" [icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></button>
|
||||||
|
</td>
|
||||||
|
<td>{{ subscription.id }}</td>
|
||||||
|
<td>{{ subscription.level }}</td>
|
||||||
|
<td>{{ subscription.start | date }}</td>
|
||||||
|
<td>{{ subscription.modified | date }}</td>
|
||||||
|
<td>{{ subscription.end | date }}</td>
|
||||||
|
<td>{{ subscription.status }}</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="rowexpansion" let-subscription>
|
||||||
|
<tr>
|
||||||
|
<td colspan="7">
|
||||||
|
<div class="p-3">
|
||||||
|
<p-table [value]="subscription.invoices" dataKey="id">
|
||||||
|
<ng-template pTemplate="header">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 5rem"></th>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Price</th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="body" let-invoice>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<button pButton pRipple icon="pi pi-print" class="p-button-rounded p-button-success mr-2" (click)="printInvoice(invoice)"></button>
|
||||||
|
</td>
|
||||||
|
<td>{{ invoice.id }}</td>
|
||||||
|
<td>{{ invoice.date | date }}</td>
|
||||||
|
<td>{{ invoice.price | currency }}</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,40 +1,25 @@
|
|||||||
|
import { HttpEventType } from '@angular/common/http';
|
||||||
import { ChangeDetectorRef, Component, ViewChild } from '@angular/core';
|
import { ChangeDetectorRef, Component, ViewChild } from '@angular/core';
|
||||||
import { ButtonModule } from 'primeng/button';
|
|
||||||
import { CheckboxModule } from 'primeng/checkbox';
|
|
||||||
import { InputTextModule } from 'primeng/inputtext';
|
|
||||||
import { StyleClassModule } from 'primeng/styleclass';
|
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
|
||||||
import { DropdownModule } from 'primeng/dropdown';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { ToggleButtonModule } from 'primeng/togglebutton';
|
|
||||||
import { TagModule } from 'primeng/tag';
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { InputTextareaModule } from 'primeng/inputtextarea';
|
import { AngularCropperjsModule } from 'angular-cropperjs';
|
||||||
import { ChipModule } from 'primeng/chip';
|
import { MessageService } from 'primeng/api';
|
||||||
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
|
import { DialogModule } from 'primeng/dialog';
|
||||||
import { DividerModule } from 'primeng/divider';
|
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
|
||||||
import { TableModule } from 'primeng/table';
|
import { EditorModule } from 'primeng/editor';
|
||||||
import { HttpClient, HttpEventType } from '@angular/common/http';
|
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
|
||||||
|
import { SelectButtonModule } from 'primeng/selectbutton';
|
||||||
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
|
import { AutoCompleteCompleteEvent, Invoice, KeyValue, Subscription } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { ImageCropperComponent, stateOptions } from '../../../components/image-cropper/image-cropper.component';
|
||||||
|
import { GeoService } from '../../../services/geo.service';
|
||||||
|
import { ImageService } from '../../../services/image.service';
|
||||||
|
import { LoadingService } from '../../../services/loading.service';
|
||||||
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
|
import { SubscriptionsService } from '../../../services/subscriptions.service';
|
||||||
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 { SubscriptionsService } from '../../../services/subscriptions.service';
|
|
||||||
import { lastValueFrom } from 'rxjs';
|
|
||||||
import { MessageService } from 'primeng/api';
|
|
||||||
import { environment } from '../../../../environments/environment';
|
|
||||||
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
|
|
||||||
import { AutoCompleteCompleteEvent, Invoice, KeyValue, KeyValueRatio, Subscription, User } from '../../../../../../common-models/src/main.model';
|
|
||||||
import { GeoService } from '../../../services/geo.service';
|
|
||||||
import { ChangeDetectionStrategy } from '@angular/compiler';
|
|
||||||
import { EditorModule } from 'primeng/editor';
|
|
||||||
import { LoadingService } from '../../../services/loading.service';
|
|
||||||
import { AngularCropperjsModule, CropperComponent } from 'angular-cropperjs';
|
|
||||||
import { ImageService } from '../../../services/image.service';
|
|
||||||
import { DialogModule } from 'primeng/dialog';
|
|
||||||
import { SelectButtonModule } from 'primeng/selectbutton';
|
|
||||||
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
|
|
||||||
import { ImageCropperComponent, stateOptions } from '../../../components/image-cropper/image-cropper.component';
|
|
||||||
import Quill from 'quill'
|
|
||||||
import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-account',
|
selector: 'app-account',
|
||||||
@@ -42,7 +27,7 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
|||||||
imports: [SharedModule, FileUploadModule, EditorModule, AngularCropperjsModule, DialogModule, SelectButtonModule, DynamicDialogModule],
|
imports: [SharedModule, FileUploadModule, EditorModule, AngularCropperjsModule, DialogModule, SelectButtonModule, DynamicDialogModule],
|
||||||
providers: [MessageService, DialogService],
|
providers: [MessageService, DialogService],
|
||||||
templateUrl: './account.component.html',
|
templateUrl: './account.component.html',
|
||||||
styleUrl: './account.component.scss'
|
styleUrl: './account.component.scss',
|
||||||
})
|
})
|
||||||
export class AccountComponent {
|
export class AccountComponent {
|
||||||
@ViewChild('companyUpload') public companyUpload: FileUpload;
|
@ViewChild('companyUpload') public companyUpload: FileUpload;
|
||||||
@@ -54,11 +39,13 @@ export class AccountComponent {
|
|||||||
maxFileSize = 1000000;
|
maxFileSize = 1000000;
|
||||||
companyLogoUrl: string;
|
companyLogoUrl: string;
|
||||||
profileUrl: string;
|
profileUrl: string;
|
||||||
type: 'company' | 'profile'
|
type: 'company' | 'profile';
|
||||||
dialogRef: DynamicDialogRef | undefined;
|
dialogRef: DynamicDialogRef | undefined;
|
||||||
environment = environment
|
environment = environment;
|
||||||
editorModules = TOOLBAR_OPTIONS
|
editorModules = TOOLBAR_OPTIONS;
|
||||||
constructor(public userService: UserService,
|
userLicensedIn: KeyValue[];
|
||||||
|
constructor(
|
||||||
|
public userService: UserService,
|
||||||
private subscriptionService: SubscriptionsService,
|
private subscriptionService: SubscriptionsService,
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
private geoService: GeoService,
|
private geoService: GeoService,
|
||||||
@@ -67,60 +54,72 @@ export class AccountComponent {
|
|||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private loadingService: LoadingService,
|
private loadingService: LoadingService,
|
||||||
private imageUploadService: ImageService,
|
private imageUploadService: ImageService,
|
||||||
public dialogService: DialogService) {}
|
public dialogService: DialogService,
|
||||||
|
) {}
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.user = await this.userService.getById(this.id);
|
const keycloakUser = this.userService.getKeycloakUser();
|
||||||
|
const email = keycloakUser.email;
|
||||||
|
try {
|
||||||
|
this.user = await this.userService.getByMail(email);
|
||||||
|
} catch (e) {
|
||||||
|
this.user = { email, firstname: keycloakUser.firstname, lastname: keycloakUser.lastname };
|
||||||
|
this.user = await this.userService.save(this.user);
|
||||||
|
}
|
||||||
|
this.userLicensedIn = this.user.licensedIn
|
||||||
|
? this.user.licensedIn.map(l => {
|
||||||
|
return { name: l.split('|')[0], value: l.split('|')[1] };
|
||||||
|
})
|
||||||
|
: [];
|
||||||
this.userSubscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions());
|
this.userSubscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions());
|
||||||
if (!this.user.licensedIn || this.user.licensedIn?.length === 0) {
|
if (!this.user.licensedIn || this.user.licensedIn?.length === 0) {
|
||||||
this.user.licensedIn = [{ name: '', value: '' }]
|
this.user.licensedIn = [''];
|
||||||
}
|
}
|
||||||
this.user = await this.userService.getById(this.user.id);
|
this.profileUrl = this.user.hasProfile ? `pictures/profile/${this.user.id}.avif` : `/assets/images/placeholder.png`;
|
||||||
this.profileUrl = this.user.hasProfile ? `${environment.apiBaseUrl}/profile/${this.user.id}.avif` : `/assets/images/placeholder.png`
|
this.companyLogoUrl = this.user.hasCompanyLogo ? `pictures/logo/${this.user.id}.avif` : `/assets/images/placeholder.png`;
|
||||||
this.companyLogoUrl = this.user.hasCompanyLogo ? `${environment.apiBaseUrl}/logo/${this.user.id}.avif` : `/assets/images/placeholder.png`
|
|
||||||
}
|
}
|
||||||
printInvoice(invoice: Invoice) { }
|
printInvoice(invoice: Invoice) {}
|
||||||
|
|
||||||
async updateProfile(user: User) {
|
async updateProfile(user: User) {
|
||||||
|
this.user.licensedIn = this.userLicensedIn.map(l => `${l.name}|${l.value}`);
|
||||||
await this.userService.save(this.user);
|
await this.userService.save(this.user);
|
||||||
|
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Acount changes have been persisted', life: 3000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onUploadCompanyLogo(event: any) {
|
onUploadCompanyLogo(event: any) {
|
||||||
const uniqueSuffix = '?_ts=' + new Date().getTime();
|
const uniqueSuffix = '?_ts=' + new Date().getTime();
|
||||||
this.companyLogoUrl = `${environment.apiBaseUrl}/logo/${this.user.id}${uniqueSuffix}` //`http://IhrServer:Port/${newImagePath}${uniqueSuffix}`;
|
this.companyLogoUrl = `pictures/logo/${this.user.id}${uniqueSuffix}`;
|
||||||
}
|
}
|
||||||
onUploadProfilePicture(event: any) {
|
onUploadProfilePicture(event: any) {
|
||||||
const uniqueSuffix = '?_ts=' + new Date().getTime();
|
const uniqueSuffix = '?_ts=' + new Date().getTime();
|
||||||
this.profileUrl = `${environment.apiBaseUrl}/profile/${this.user.id}${uniqueSuffix}` //`http://IhrServer:Port/${newImagePath}${uniqueSuffix}`;
|
this.profileUrl = `pictures/profile/${this.user.id}${uniqueSuffix}`;
|
||||||
}
|
}
|
||||||
setImageToFallback(event: Event) {
|
setImageToFallback(event: Event) {
|
||||||
(event.target as HTMLImageElement).src = `/assets/images/placeholder.png`; // Pfad zum Platzhalterbild
|
(event.target as HTMLImageElement).src = `/assets/images/placeholder.png`; // Pfad zum Platzhalterbild
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
suggestions: string[] | undefined;
|
suggestions: string[] | undefined;
|
||||||
|
|
||||||
async search(event: AutoCompleteCompleteEvent) {
|
async search(event: AutoCompleteCompleteEvent) {
|
||||||
const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query))
|
const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query));
|
||||||
this.suggestions = result.map(r => `${r.city} - ${r.state_code}`).slice(0, 5);
|
this.suggestions = result.map(r => `${r.city} - ${r.state_code}`).slice(0, 5);
|
||||||
}
|
}
|
||||||
addLicence() {
|
addLicence() {
|
||||||
this.user.licensedIn.push({ name: '', value: '' });
|
this.userLicensedIn.push({ name: '', value: '' });
|
||||||
}
|
}
|
||||||
removeLicence() {
|
removeLicence() {
|
||||||
this.user.licensedIn.splice(this.user.licensedIn.length - 2, 1);
|
this.userLicensedIn.splice(this.user.licensedIn.length - 2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
select(event: any, type: 'company' | 'profile') {
|
select(event: any, type: 'company' | 'profile') {
|
||||||
const imageUrl = URL.createObjectURL(event.files[0]);
|
const imageUrl = URL.createObjectURL(event.files[0]);
|
||||||
this.type = type
|
this.type = type;
|
||||||
const config = { aspectRatio: type === 'company' ? stateOptions[0].value : stateOptions[2].value }
|
const config = { aspectRatio: type === 'company' ? stateOptions[0].value : stateOptions[2].value };
|
||||||
this.dialogRef = this.dialogService.open(ImageCropperComponent, {
|
this.dialogRef = this.dialogService.open(ImageCropperComponent, {
|
||||||
data: {
|
data: {
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
fileUpload: type === 'company' ? this.companyUpload : this.profileUpload,
|
fileUpload: type === 'company' ? this.companyUpload : this.profileUpload,
|
||||||
config: config,
|
config: config,
|
||||||
ratioVariable: type === 'company' ? true : false
|
ratioVariable: type === 'company' ? true : false,
|
||||||
},
|
},
|
||||||
header: 'Edit Image',
|
header: 'Edit Image',
|
||||||
width: '50vw',
|
width: '50vw',
|
||||||
@@ -130,27 +129,30 @@ export class AccountComponent {
|
|||||||
closable: false,
|
closable: false,
|
||||||
breakpoints: {
|
breakpoints: {
|
||||||
'960px': '75vw',
|
'960px': '75vw',
|
||||||
'640px': '90vw'
|
'640px': '90vw',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.dialogRef.onClose.subscribe(cropper => {
|
this.dialogRef.onClose.subscribe(cropper => {
|
||||||
if (cropper){
|
if (cropper) {
|
||||||
this.loadingService.startLoading('uploadImage');
|
this.loadingService.startLoading('uploadImage');
|
||||||
cropper.getCroppedCanvas().toBlob(async (blob) => {
|
cropper.getCroppedCanvas().toBlob(async blob => {
|
||||||
this.imageUploadService.uploadImage(blob, type==='company'?'uploadCompanyLogo':'uploadProfile',this.user.id).subscribe(async(event) => {
|
this.imageUploadService.uploadImage(blob, type === 'company' ? 'uploadCompanyLogo' : 'uploadProfile', this.user.id).subscribe(
|
||||||
if (event.type === HttpEventType.Response) {
|
async event => {
|
||||||
this.loadingService.stopLoading('uploadImage');
|
if (event.type === HttpEventType.Response) {
|
||||||
if (this.type==='company'){
|
this.loadingService.stopLoading('uploadImage');
|
||||||
this.user.hasCompanyLogo=true;
|
if (this.type === 'company') {
|
||||||
this.companyLogoUrl=`${environment.apiBaseUrl}/logo/${this.user.id}.avif?_ts=${new Date().getTime()}`
|
this.user.hasCompanyLogo = true; //
|
||||||
} else {
|
this.companyLogoUrl = `${environment.apiBaseUrl}/logo/${this.user.id}.avif?_ts=${new Date().getTime()}`;
|
||||||
this.user.hasProfile=true;
|
} else {
|
||||||
this.profileUrl=`${environment.apiBaseUrl}/profile/${this.user.id}.avif?_ts=${new Date().getTime()}`
|
this.user.hasProfile = true;
|
||||||
|
this.profileUrl = `${environment.apiBaseUrl}/profile/${this.user.id}.avif?_ts=${new Date().getTime()}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, error => console.error('Fehler beim Upload:', error));
|
error => console.error('Fehler beim Upload:', error),
|
||||||
})
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
<div class="surface-ground px-4 py-8 md:px-6 lg:px-8">
|
||||||
|
<div class="p-fluid flex flex-column lg:flex-row">
|
||||||
|
<menu-account></menu-account>
|
||||||
|
<p-toast></p-toast>
|
||||||
|
<div *ngIf="listing" class="surface-card p-5 shadow-2 border-round flex-auto">
|
||||||
|
<div class="text-900 font-semibold text-lg mt-3">{{ mode === 'create' ? 'New' : 'Edit' }} Listing</div>
|
||||||
|
<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">
|
||||||
|
<label for="listingCategory" class="block font-medium text-900 mb-2">Listing category</label>
|
||||||
|
<p-dropdown
|
||||||
|
id="listingCategory"
|
||||||
|
[options]="selectOptions?.listingCategories"
|
||||||
|
[ngModel]="listingsCategory"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="value"
|
||||||
|
(ngModelChange)="changeListingCategory($event)"
|
||||||
|
placeholder="Listing category"
|
||||||
|
[disabled]="mode === 'edit'"
|
||||||
|
[style]="{ width: '100%' }"
|
||||||
|
></p-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<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>
|
||||||
|
<div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="description" class="block font-medium text-900 mb-2">Description</label>
|
||||||
|
<!-- <textarea id="description" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.description"></textarea> -->
|
||||||
|
<p-editor [(ngModel)]="listing.description" [style]="{ height: '320px' }" [modules]="editorModules">
|
||||||
|
<ng-template pTemplate="header"></ng-template>
|
||||||
|
</p-editor>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="type" class="block font-medium text-900 mb-2">Type of business</label>
|
||||||
|
<p-dropdown id="type" [options]="typesOfBusiness" [(ngModel)]="listing.type" optionLabel="name" optionValue="value" [showClear]="true" placeholder="Type of business" [style]="{ width: '100%' }"></p-dropdown>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
<div class="mb-4 col-12 md:col-6">
|
||||||
|
<label for="listingCategory" class="block font-medium text-900 mb-2">City</label>
|
||||||
|
<p-autoComplete [(ngModel)]="listing.city" [suggestions]="suggestions" (completeMethod)="search($event)"></p-autoComplete>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p-divider></p-divider>
|
||||||
|
<div class="flex gap-5 flex-column-reverse md:flex-row">
|
||||||
|
<div class="flex-auto p-fluid">
|
||||||
|
<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>
|
||||||
|
<!-- <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 class="mb-4 col-12 md:col-6">
|
||||||
|
<label for="salesRevenue" class="block font-medium text-900 mb-2">Sales Revenue</label>
|
||||||
|
<app-inputNumber mode="currency" currency="USD" inputId="salesRevenue" [(ngModel)]="listing.salesRevenue"></app-inputNumber>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="mb-4 col-12 md:col-6">
|
||||||
|
<label for="cashFlow" class="block font-medium text-900 mb-2">Cash Flow</label>
|
||||||
|
<app-inputNumber mode="currency" currency="USD" inputId="cashFlow" [(ngModel)]="listing.cashFlow"></app-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>
|
||||||
|
<app-inputNumber mode="decimal" inputId="established" [(ngModel)]="listing.established"></app-inputNumber>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4 col-12 md:col-6">
|
||||||
|
<label for="employees" class="block font-medium text-900 mb-2">Employees</label>
|
||||||
|
<app-inputNumber mode="decimal" inputId="employees" [(ngModel)]="listing.employees"></app-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="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="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>
|
||||||
|
<app-inputNumber mode="decimal" inputId="internalListingNumber" type="text" [(ngModel)]="listing.internalListingNumber"></app-inputNumber>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<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'){
|
||||||
|
<button pButton pRipple label="Post Listing" class="w-auto" (click)="save()"></button>
|
||||||
|
} @else {
|
||||||
|
<button pButton pRipple label="Update Listing" class="w-auto" (click)="save()"></button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p-toast></p-toast>
|
||||||
|
<p-confirmDialog></p-confirmDialog>
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import { Component, ViewChild } from '@angular/core';
|
||||||
|
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||||
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
|
import { createGenericObject, getListingType, routeListingWithState } from '../../../utils/utils';
|
||||||
|
|
||||||
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||||
|
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { AngularCropperjsModule } from 'angular-cropperjs';
|
||||||
|
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
|
||||||
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
|
import { CarouselModule } from 'primeng/carousel';
|
||||||
|
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
||||||
|
import { DialogModule } from 'primeng/dialog';
|
||||||
|
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
|
||||||
|
import { EditorModule } from 'primeng/editor';
|
||||||
|
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
|
||||||
|
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
|
import { AutoCompleteCompleteEvent, ImageProperty } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { InputNumberModule } from '../../../components/inputnumber/inputnumber.component';
|
||||||
|
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
|
||||||
|
import { GeoService } from '../../../services/geo.service';
|
||||||
|
import { ImageService } from '../../../services/image.service';
|
||||||
|
import { LoadingService } from '../../../services/loading.service';
|
||||||
|
import { UserService } from '../../../services/user.service';
|
||||||
|
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||||
|
import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||||
|
@Component({
|
||||||
|
selector: 'business-listing',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
SharedModule,
|
||||||
|
ArrayToStringPipe,
|
||||||
|
InputNumberModule,
|
||||||
|
CarouselModule,
|
||||||
|
DialogModule,
|
||||||
|
AngularCropperjsModule,
|
||||||
|
FileUploadModule,
|
||||||
|
EditorModule,
|
||||||
|
DynamicDialogModule,
|
||||||
|
DragDropModule,
|
||||||
|
ConfirmDialogModule,
|
||||||
|
MixedCdkDragDropModule,
|
||||||
|
],
|
||||||
|
providers: [MessageService, DialogService, ConfirmationService],
|
||||||
|
templateUrl: './edit-business-listing.component.html',
|
||||||
|
styleUrl: './edit-business-listing.component.scss',
|
||||||
|
})
|
||||||
|
export class EditBusinessListingComponent {
|
||||||
|
@ViewChild(FileUpload) public fileUpload: FileUpload;
|
||||||
|
listingsCategory = 'business';
|
||||||
|
category: string;
|
||||||
|
location: string;
|
||||||
|
mode: 'edit' | 'create';
|
||||||
|
separator: '\n\n';
|
||||||
|
listing: BusinessListing;
|
||||||
|
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
||||||
|
user: User;
|
||||||
|
maxFileSize = 3000000;
|
||||||
|
uploadUrl: string;
|
||||||
|
environment = environment;
|
||||||
|
propertyImages: string[];
|
||||||
|
responsiveOptions = [
|
||||||
|
{
|
||||||
|
breakpoint: '1199px',
|
||||||
|
numVisible: 1,
|
||||||
|
numScroll: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '991px',
|
||||||
|
numVisible: 2,
|
||||||
|
numScroll: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '767px',
|
||||||
|
numVisible: 1,
|
||||||
|
numScroll: 1,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
config = { aspectRatio: 16 / 9 };
|
||||||
|
editorModules = TOOLBAR_OPTIONS;
|
||||||
|
dialogRef: DynamicDialogRef | undefined;
|
||||||
|
draggedImage: ImageProperty;
|
||||||
|
faTrash = faTrash;
|
||||||
|
data: CommercialPropertyListing;
|
||||||
|
typesOfBusiness = [];
|
||||||
|
constructor(
|
||||||
|
public selectOptions: SelectOptionsService,
|
||||||
|
private router: Router,
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private listingsService: ListingsService,
|
||||||
|
public userService: UserService,
|
||||||
|
private messageService: MessageService,
|
||||||
|
private geoService: GeoService,
|
||||||
|
private imageService: ImageService,
|
||||||
|
private loadingService: LoadingService,
|
||||||
|
public dialogService: DialogService,
|
||||||
|
private confirmationService: ConfirmationService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
this.router.events.subscribe(event => {
|
||||||
|
if (event instanceof NavigationEnd) {
|
||||||
|
this.mode = event.url === '/createBusinessListing' ? 'create' : 'edit';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.route.data.subscribe(async () => {
|
||||||
|
if (this.router.getCurrentNavigation().extras.state) {
|
||||||
|
this.data = this.router.getCurrentNavigation().extras.state['data'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.typesOfBusiness = selectOptions.typesOfBusiness.map(e => {
|
||||||
|
return { name: e.name, value: parseInt(e.value) };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async ngOnInit() {
|
||||||
|
if (this.mode === 'edit') {
|
||||||
|
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'business'));
|
||||||
|
} else {
|
||||||
|
this.listing = createGenericObject<BusinessListing>();
|
||||||
|
this.listing.listingsCategory = 'business';
|
||||||
|
this.listing.userId = await this.userService.getId();
|
||||||
|
this.listing.title = this.data?.title;
|
||||||
|
this.listing.description = this.data?.description;
|
||||||
|
}
|
||||||
|
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`;
|
||||||
|
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async save() {
|
||||||
|
this.listing = await this.listingsService.save(this.listing, getListingType(this.listing));
|
||||||
|
this.router.navigate(['editBusinessListing', this.listing.id]);
|
||||||
|
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestions: string[] | undefined;
|
||||||
|
|
||||||
|
async search(event: AutoCompleteCompleteEvent) {
|
||||||
|
const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query, this.listing.state));
|
||||||
|
this.suggestions = result.map(r => r.city).slice(0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeListingCategory(value: 'business' | 'commercialProperty') {
|
||||||
|
routeListingWithState(this.router, value, this.listing);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
<div class="surface-ground px-4 py-8 md:px-6 lg:px-8">
|
||||||
|
<div class="p-fluid flex flex-column lg:flex-row">
|
||||||
|
<menu-account></menu-account>
|
||||||
|
<p-toast></p-toast>
|
||||||
|
<div *ngIf="listing" class="surface-card p-5 shadow-2 border-round flex-auto">
|
||||||
|
<div class="text-900 font-semibold text-lg mt-3">{{ mode === 'create' ? 'New' : 'Edit' }} Listing</div>
|
||||||
|
<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">
|
||||||
|
<label for="listingCategory" class="block font-medium text-900 mb-2">Listing category</label>
|
||||||
|
<p-dropdown
|
||||||
|
id="listingCategory"
|
||||||
|
[options]="selectOptions?.listingCategories"
|
||||||
|
[ngModel]="listingsCategory"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="value"
|
||||||
|
(ngModelChange)="changeListingCategory($event)"
|
||||||
|
placeholder="Listing category"
|
||||||
|
[disabled]="mode === 'edit'"
|
||||||
|
[style]="{ width: '100%' }"
|
||||||
|
></p-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<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>
|
||||||
|
<div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="description" class="block font-medium text-900 mb-2">Description</label>
|
||||||
|
<!-- <textarea id="description" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.description"></textarea> -->
|
||||||
|
<p-editor [(ngModel)]="listing.description" [style]="{ height: '320px' }" [modules]="editorModules">
|
||||||
|
<ng-template pTemplate="header"></ng-template>
|
||||||
|
</p-editor>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="type" class="block font-medium text-900 mb-2">Property Category</label>
|
||||||
|
<p-dropdown
|
||||||
|
id="type"
|
||||||
|
[options]="typesOfCommercialProperty"
|
||||||
|
[(ngModel)]="listing.type"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="value"
|
||||||
|
[showClear]="true"
|
||||||
|
placeholder="Property Category"
|
||||||
|
[style]="{ width: '100%' }"
|
||||||
|
></p-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="mb-4 col-12 md:col-6">
|
||||||
|
<label for="states" class="block font-medium text-900 mb-2">State</label>
|
||||||
|
<p-dropdown id="states" [options]="selectOptions?.states" [(ngModel)]="listing.state" optionLabel="name" optionValue="value" [showClear]="true" placeholder="State" [style]="{ width: '100%' }"></p-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4 col-12 md:col-6">
|
||||||
|
<label for="city" class="block font-medium text-900 mb-2">City</label>
|
||||||
|
<p-autoComplete id="city" [(ngModel)]="listing.city" [suggestions]="suggestions" (completeMethod)="search($event)"></p-autoComplete>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="mb-4 col-12 md:col-6">
|
||||||
|
<label for="zipCode" class="block font-medium text-900 mb-2">Zip Code</label>
|
||||||
|
<input id="zipCode" type="text" pInputText [(ngModel)]="listing.zipCode" />
|
||||||
|
</div>
|
||||||
|
<div class="mb-4 col-12 md:col-6">
|
||||||
|
<label for="county" class="block font-medium text-900 mb-2">County</label>
|
||||||
|
<input id="county" type="text" pInputText [(ngModel)]="listing.county" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p-divider></p-divider>
|
||||||
|
<div class="flex gap-5 flex-column-reverse md:flex-row">
|
||||||
|
<div class="flex-auto p-fluid">
|
||||||
|
<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>
|
||||||
|
<!-- <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 class="mb-4 col-12 md:col-6">
|
||||||
|
<div class="flex flex-column align-items-center flex-or">
|
||||||
|
<span class="font-medium text-900 mb-2">Property Pictures</span>
|
||||||
|
<span class="font-light text-sm text-900 mb-2">(Pictures can be uploaded once the listing is posted initially)</span>
|
||||||
|
<!-- <img [src]="propertyPictureUrl" (error)="setImageToFallback($event)" class="image"/> -->
|
||||||
|
<p-fileUpload
|
||||||
|
mode="basic"
|
||||||
|
chooseLabel="Upload"
|
||||||
|
[customUpload]="true"
|
||||||
|
name="file"
|
||||||
|
accept="image/*"
|
||||||
|
[maxFileSize]="maxFileSize"
|
||||||
|
(onSelect)="select($event)"
|
||||||
|
styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"
|
||||||
|
[disabled]="!listing.id"
|
||||||
|
>
|
||||||
|
</p-fileUpload>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (propertyImages?.length>0){
|
||||||
|
<div class="p-2 border-1 surface-border border-round mb-4 image-container" cdkDropListGroup mixedCdkDragDrop (dropped)="onDrop($event)" cdkDropListOrientation="horizontal">
|
||||||
|
@for (image of propertyImages; track image) {
|
||||||
|
<span cdkDropList mixedCdkDropList>
|
||||||
|
<div cdkDrag mixedCdkDragSizeHelper class="image-wrap">
|
||||||
|
<img src="pictures/property/{{ listing.imagePath }}/{{ image }}" [alt]="image" class="shadow-2" cdkDrag />
|
||||||
|
<fa-icon [icon]="faTrash" (click)="deleteConfirm(image)"></fa-icon>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div>
|
||||||
|
@if (mode==='create'){
|
||||||
|
<button pButton pRipple label="Post Listing" class="w-auto" (click)="save()"></button>
|
||||||
|
} @else {
|
||||||
|
<button pButton pRipple label="Update Listing" class="w-auto" (click)="save()"></button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p-toast></p-toast>
|
||||||
|
<p-confirmDialog></p-confirmDialog>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
.translate-y-5 {
|
||||||
|
transform: translateY(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container {
|
||||||
|
display: flex; /* Erlaubt ein flexibles Box-Layout */
|
||||||
|
flex-wrap: wrap; /* Erlaubt das Umfließen der Elemente auf die nächste Zeile */
|
||||||
|
justify-content: flex-start; /* Startet die Anordnung der Elemente am Anfang des Containers */
|
||||||
|
align-items: flex-start; /* Ausrichtung der Elemente am Anfang der Querachse */
|
||||||
|
padding: 10px; /* Abstand zwischen den Inhalten des Containers und dessen Rand */
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container span {
|
||||||
|
flex-flow: row;
|
||||||
|
display: flex;
|
||||||
|
width: fit-content;
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container span img {
|
||||||
|
max-height: 150px; /* Maximale Höhe der Bilder */
|
||||||
|
width: auto; /* Die Breite der Bilder passt sich automatisch an die Höhe an */
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
// .image-container fa-icon {
|
||||||
|
// top: 0; /* Positioniert das Icon am oberen Rand des Bildes */
|
||||||
|
// right: 0; /* Positioniert das Icon am rechten Rand des Bildes */
|
||||||
|
// color: #fff; /* Weiße Farbe für das Icon */
|
||||||
|
// background-color: rgba(0,0,0,0.5); /* Halbtransparenter Hintergrund für bessere Sichtbarkeit */
|
||||||
|
// padding: 5px; /* Ein wenig Platz um das Icon */
|
||||||
|
// cursor: pointer; /* Verwandelt den Cursor in eine Hand, um Interaktivität anzudeuten */
|
||||||
|
// }
|
||||||
|
|
||||||
|
.image-wrap {
|
||||||
|
position: relative; /* Ermöglicht die absolute Positionierung des Icons bezogen auf diesen Container */
|
||||||
|
display: inline-block; /* Erlaubt die Inline-Anordnung, falls mehrere Bilder vorhanden sind */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stil für das Bild */
|
||||||
|
.image-wrap img {
|
||||||
|
max-height: 150px;
|
||||||
|
width: auto;
|
||||||
|
display: block; /* Verhindert unerwünschten Abstand unter dem Bild */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stil für das FontAwesome Icon */
|
||||||
|
.image-wrap fa-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px; /* Positioniert das Icon am oberen Rand des Bildes */
|
||||||
|
right: 15px; /* Positioniert das Icon am rechten Rand des Bildes */
|
||||||
|
color: #fff; /* Weiße Farbe für das Icon */
|
||||||
|
background-color: rgba(0,0,0,0.5); /* Halbtransparenter Hintergrund für bessere Sichtbarkeit */
|
||||||
|
padding: 5px; /* Ein wenig Platz um das Icon */
|
||||||
|
cursor: pointer; /* Verwandelt den Cursor in eine Hand, um Interaktivität anzudeuten */
|
||||||
|
border-radius: 8px; /* Optional: Abrunden der linken unteren Ecke für ästhetische Zwecke */
|
||||||
|
}
|
||||||
@@ -1,97 +1,97 @@
|
|||||||
import { Component, ViewChild } from '@angular/core';
|
import { Component, ViewChild } from '@angular/core';
|
||||||
import { ButtonModule } from 'primeng/button';
|
|
||||||
import { CheckboxModule } from 'primeng/checkbox';
|
|
||||||
import { InputTextModule } from 'primeng/inputtext';
|
|
||||||
import { StyleClassModule } from 'primeng/styleclass';
|
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
|
||||||
import { DropdownModule } from 'primeng/dropdown';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { ToggleButtonModule } from 'primeng/togglebutton';
|
|
||||||
import { TagModule } from 'primeng/tag';
|
|
||||||
import data from '../../../../assets/data/user.json';
|
|
||||||
import dataListings from '../../../../assets/data/listings.json';
|
|
||||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||||
import { InputTextareaModule } from 'primeng/inputtextarea';
|
|
||||||
import { ChipModule } from 'primeng/chip';
|
|
||||||
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
|
|
||||||
import { DividerModule } from 'primeng/divider';
|
|
||||||
import { TableModule } from 'primeng/table';
|
|
||||||
import { createGenericObject } from '../../../utils/utils';
|
|
||||||
import { ListingsService } from '../../../services/listings.service';
|
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
|
import { createGenericObject, getListingType, routeListingWithState } from '../../../utils/utils';
|
||||||
|
|
||||||
|
import { DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||||
|
import { HttpEventType } from '@angular/common/http';
|
||||||
|
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { AngularCropperjsModule } from 'angular-cropperjs';
|
||||||
|
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
|
||||||
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
|
import { CarouselModule } from 'primeng/carousel';
|
||||||
|
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
||||||
|
import { DialogModule } from 'primeng/dialog';
|
||||||
|
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
|
||||||
|
import { EditorModule } from 'primeng/editor';
|
||||||
|
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
|
||||||
|
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
|
import { AutoCompleteCompleteEvent, ImageProperty } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { ImageCropperComponent } from '../../../components/image-cropper/image-cropper.component';
|
||||||
|
import { InputNumberModule } from '../../../components/inputnumber/inputnumber.component';
|
||||||
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
|
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
|
||||||
|
import { GeoService } from '../../../services/geo.service';
|
||||||
|
import { ImageService } from '../../../services/image.service';
|
||||||
|
import { LoadingService } from '../../../services/loading.service';
|
||||||
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 { ConfirmationService, MessageService } from 'primeng/api';
|
|
||||||
import { AutoCompleteCompleteEvent, BusinessListing, CommercialPropertyListing, ImageProperty, ListingType, User } from '../../../../../../common-models/src/main.model';
|
|
||||||
import { GeoResult, GeoService } from '../../../services/geo.service';
|
|
||||||
import { InputNumberComponent, InputNumberModule } from '../../../components/inputnumber/inputnumber.component';
|
|
||||||
import { environment } from '../../../../environments/environment';
|
|
||||||
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
|
|
||||||
import { CarouselModule } from 'primeng/carousel';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { DialogModule } from 'primeng/dialog';
|
|
||||||
import { AngularCropperjsModule, CropperComponent } from 'angular-cropperjs';
|
|
||||||
import { HttpClient, HttpEventType } from '@angular/common/http';
|
|
||||||
import { ImageService } from '../../../services/image.service'
|
|
||||||
import { LoadingService } from '../../../services/loading.service';
|
|
||||||
import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||||
import { EditorModule } from 'primeng/editor';
|
|
||||||
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
|
|
||||||
import { ImageCropperComponent } from '../../../components/image-cropper/image-cropper.component';
|
|
||||||
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
|
||||||
import { CdkDragDrop, CdkDragEnter, CdkDragExit, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
|
|
||||||
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
|
|
||||||
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'create-listing',
|
selector: 'commercial-property-listing',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SharedModule, ArrayToStringPipe, InputNumberModule, CarouselModule,
|
imports: [
|
||||||
DialogModule, AngularCropperjsModule, FileUploadModule, EditorModule, DynamicDialogModule, DragDropModule,
|
SharedModule,
|
||||||
ConfirmDialogModule, MixedCdkDragDropModule],
|
ArrayToStringPipe,
|
||||||
|
InputNumberModule,
|
||||||
|
CarouselModule,
|
||||||
|
DialogModule,
|
||||||
|
AngularCropperjsModule,
|
||||||
|
FileUploadModule,
|
||||||
|
EditorModule,
|
||||||
|
DynamicDialogModule,
|
||||||
|
DragDropModule,
|
||||||
|
ConfirmDialogModule,
|
||||||
|
MixedCdkDragDropModule,
|
||||||
|
],
|
||||||
providers: [MessageService, DialogService, ConfirmationService],
|
providers: [MessageService, DialogService, ConfirmationService],
|
||||||
templateUrl: './edit-listing.component.html',
|
templateUrl: './edit-commercial-property-listing.component.html',
|
||||||
styleUrl: './edit-listing.component.scss'
|
styleUrl: './edit-commercial-property-listing.component.scss',
|
||||||
})
|
})
|
||||||
export class EditListingComponent {
|
export class EditCommercialPropertyListingComponent {
|
||||||
@ViewChild(FileUpload) public fileUpload: FileUpload;
|
@ViewChild(FileUpload) public fileUpload: FileUpload;
|
||||||
listingCategory: 'Business' | 'Commercial Property';
|
listingsCategory = 'commercialProperty';
|
||||||
category: string;
|
category: string;
|
||||||
location: string;
|
location: string;
|
||||||
mode: 'edit' | 'create';
|
mode: 'edit' | 'create';
|
||||||
separator: '\n\n'
|
separator: '\n\n';
|
||||||
listing: ListingType
|
listing: CommercialPropertyListing;
|
||||||
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;
|
||||||
maxFileSize = 3000000;
|
maxFileSize = 3000000;
|
||||||
uploadUrl: string;
|
uploadUrl: string;
|
||||||
environment = environment;
|
environment = environment;
|
||||||
propertyImages: ImageProperty[]
|
propertyImages: string[];
|
||||||
responsiveOptions = [
|
responsiveOptions = [
|
||||||
{
|
{
|
||||||
breakpoint: '1199px',
|
breakpoint: '1199px',
|
||||||
numVisible: 1,
|
numVisible: 1,
|
||||||
numScroll: 1
|
numScroll: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
breakpoint: '991px',
|
breakpoint: '991px',
|
||||||
numVisible: 2,
|
numVisible: 2,
|
||||||
numScroll: 1
|
numScroll: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
breakpoint: '767px',
|
breakpoint: '767px',
|
||||||
numVisible: 1,
|
numVisible: 1,
|
||||||
numScroll: 1
|
numScroll: 1,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
config = { aspectRatio: 16 / 9 }
|
config = { aspectRatio: 16 / 9 };
|
||||||
editorModules = TOOLBAR_OPTIONS
|
editorModules = TOOLBAR_OPTIONS;
|
||||||
dialogRef: DynamicDialogRef | undefined;
|
dialogRef: DynamicDialogRef | undefined;
|
||||||
draggedImage: ImageProperty
|
draggedImage: ImageProperty;
|
||||||
faTrash = faTrash;
|
faTrash = faTrash;
|
||||||
constructor(public selectOptions: SelectOptionsService,
|
suggestions: string[] | undefined;
|
||||||
|
data: BusinessListing;
|
||||||
|
userId: string;
|
||||||
|
typesOfCommercialProperty = [];
|
||||||
|
constructor(
|
||||||
|
public selectOptions: SelectOptionsService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private listingsService: ListingsService,
|
private listingsService: ListingsService,
|
||||||
@@ -101,41 +101,45 @@ export class EditListingComponent {
|
|||||||
private imageService: ImageService,
|
private imageService: ImageService,
|
||||||
private loadingService: LoadingService,
|
private loadingService: LoadingService,
|
||||||
public dialogService: DialogService,
|
public dialogService: DialogService,
|
||||||
private confirmationService: ConfirmationService) {
|
private confirmationService: ConfirmationService,
|
||||||
this.user = this.userService.getUser();
|
private route: ActivatedRoute,
|
||||||
|
) {
|
||||||
// Abonniere Router-Events, um den aktiven Link zu ermitteln
|
// Abonniere Router-Events, um den aktiven Link zu ermitteln
|
||||||
this.router.events.subscribe(event => {
|
this.router.events.subscribe(event => {
|
||||||
if (event instanceof NavigationEnd) {
|
if (event instanceof NavigationEnd) {
|
||||||
this.mode = event.url === '/createListing' ? 'create' : 'edit';
|
this.mode = event.url === '/createCommercialPropertyListing' ? 'create' : 'edit';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.route.data.subscribe(async () => {
|
||||||
|
if (this.router.getCurrentNavigation().extras.state) {
|
||||||
|
this.data = this.router.getCurrentNavigation().extras.state['data'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.typesOfCommercialProperty = selectOptions.typesOfCommercialProperty.map(e => {
|
||||||
|
return { name: e.name, value: parseInt(e.value) };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
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, 'commercialProperty'));
|
||||||
} else {
|
} else {
|
||||||
const uuid = sessionStorage.getItem('uuid') ? sessionStorage.getItem('uuid') : uuidv4();
|
this.listing = createGenericObject<CommercialPropertyListing>();
|
||||||
sessionStorage.setItem('uuid', uuid);
|
this.listing.userId = await this.userService.getId();
|
||||||
this.listing = createGenericObject<BusinessListing>();
|
this.listing.title = this.data?.title;
|
||||||
this.listing.id = uuid
|
this.listing.description = this.data?.description;
|
||||||
this.listing.userId = this.user.id
|
|
||||||
this.listing.listingsCategory = 'business';
|
|
||||||
}
|
}
|
||||||
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`;
|
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`;
|
||||||
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
|
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
sessionStorage.removeItem('uuid')
|
this.listing = await this.listingsService.save(this.listing, getListingType(this.listing));
|
||||||
await this.listingsService.save(this.listing, this.listing.listingsCategory);
|
this.router.navigate(['editCommercialPropertyListing', this.listing.id]);
|
||||||
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 });
|
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
suggestions: string[] | undefined;
|
|
||||||
|
|
||||||
async search(event: AutoCompleteCompleteEvent) {
|
async search(event: AutoCompleteCompleteEvent) {
|
||||||
const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query, this.listing.state))
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +149,7 @@ export class EditListingComponent {
|
|||||||
data: {
|
data: {
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
fileUpload: this.fileUpload,
|
fileUpload: this.fileUpload,
|
||||||
ratioVariable: false
|
ratioVariable: false,
|
||||||
},
|
},
|
||||||
header: 'Edit Image',
|
header: 'Edit Image',
|
||||||
width: '50vw',
|
width: '50vw',
|
||||||
@@ -155,38 +159,27 @@ export class EditListingComponent {
|
|||||||
closable: false,
|
closable: false,
|
||||||
breakpoints: {
|
breakpoints: {
|
||||||
'960px': '75vw',
|
'960px': '75vw',
|
||||||
'640px': '90vw'
|
'640px': '90vw',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.dialogRef.onClose.subscribe(cropper => {
|
this.dialogRef.onClose.subscribe(cropper => {
|
||||||
if (cropper){
|
if (cropper) {
|
||||||
this.loadingService.startLoading('uploadImage');
|
this.loadingService.startLoading('uploadImage');
|
||||||
cropper.getCroppedCanvas().toBlob(async (blob) => {
|
cropper.getCroppedCanvas().toBlob(async blob => {
|
||||||
this.imageService.uploadImage(blob, 'uploadPropertyPicture', this.listing.id).subscribe(async (event) => {
|
this.imageService.uploadImage(blob, 'uploadPropertyPicture', this.listing.id).subscribe(
|
||||||
if (event.type === HttpEventType.Response) {
|
async event => {
|
||||||
console.log('Upload abgeschlossen', event.body);
|
if (event.type === HttpEventType.Response) {
|
||||||
this.loadingService.stopLoading('uploadImage');
|
console.log('Upload abgeschlossen', event.body);
|
||||||
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
|
this.loadingService.stopLoading('uploadImage');
|
||||||
}
|
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
|
||||||
}, error => console.error('Fehler beim Upload:', error));
|
}
|
||||||
|
},
|
||||||
|
error => console.error('Fehler beim Upload:', error),
|
||||||
|
);
|
||||||
}, 'image/jpg');
|
}, 'image/jpg');
|
||||||
cropper.destroy();
|
cropper.destroy();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
// this.dialogRef.onClose.subscribe(blob => {
|
|
||||||
// if (blob) {
|
|
||||||
// // this.loadingService.startLoading('uploadImage');
|
|
||||||
// setTimeout(()=>{
|
|
||||||
// this.imageService.uploadImage(blob, 'uploadPropertyPicture', this.listing.id).subscribe(async (event) => {
|
|
||||||
// if (event.type === HttpEventType.Response) {
|
|
||||||
// console.log('Upload abgeschlossen', event.body);
|
|
||||||
// // this.loadingService.stopLoading('uploadImage');
|
|
||||||
// this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
|
|
||||||
// }
|
|
||||||
// }, error => console.error('Fehler beim Upload:', error));
|
|
||||||
// },10)
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteConfirm(imageName: string) {
|
deleteConfirm(imageName: string) {
|
||||||
@@ -195,27 +188,28 @@ export class EditListingComponent {
|
|||||||
message: `Do you want to delete this image ${imageName}?`,
|
message: `Do you want to delete this image ${imageName}?`,
|
||||||
header: 'Delete Confirmation',
|
header: 'Delete Confirmation',
|
||||||
icon: 'pi pi-info-circle',
|
icon: 'pi pi-info-circle',
|
||||||
acceptButtonStyleClass: "p-button-danger p-button-text",
|
acceptButtonStyleClass: 'p-button-danger p-button-text',
|
||||||
rejectButtonStyleClass: "p-button-text p-button-text",
|
rejectButtonStyleClass: 'p-button-text p-button-text',
|
||||||
acceptIcon: "none",
|
acceptIcon: 'none',
|
||||||
rejectIcon: "none",
|
rejectIcon: 'none',
|
||||||
|
|
||||||
accept: async () => {
|
accept: async () => {
|
||||||
await this.imageService.deleteListingImage(this.listing.id, imageName);
|
await this.imageService.deleteListingImage(this.listing.id, imageName);
|
||||||
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Image deleted' });
|
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Image deleted' });
|
||||||
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
|
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
|
||||||
|
|
||||||
},
|
},
|
||||||
reject: () => {
|
reject: () => {
|
||||||
// this.messageService.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected' });
|
// this.messageService.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected' });
|
||||||
console.log('deny')
|
console.log('deny');
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onDrop(event: { previousIndex: number; currentIndex: number }) {
|
onDrop(event: { previousIndex: number; currentIndex: number }) {
|
||||||
moveItemInArray(this.propertyImages, event.previousIndex, event.currentIndex);
|
moveItemInArray(this.propertyImages, event.previousIndex, event.currentIndex);
|
||||||
this.listingsService.changeImageOrder(this.listing.id, this.propertyImages)
|
this.listingsService.changeImageOrder(this.listing.id, this.propertyImages);
|
||||||
|
}
|
||||||
|
changeListingCategory(value: 'business' | 'commercialProperty') {
|
||||||
|
routeListingWithState(this.router, value, this.listing);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
<div class="surface-ground px-4 py-8 md:px-6 lg:px-8">
|
|
||||||
<div class="p-fluid flex flex-column lg:flex-row">
|
|
||||||
<menu-account></menu-account>
|
|
||||||
<p-toast></p-toast>
|
|
||||||
<div *ngIf="listing" class="surface-card p-5 shadow-2 border-round flex-auto">
|
|
||||||
<div class="text-900 font-semibold text-lg mt-3">{{mode==='create'?'New':'Edit'}} Listing</div>
|
|
||||||
<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">
|
|
||||||
<label for="listingCategory" class="block font-medium text-900 mb-2">Listing category</label>
|
|
||||||
<p-dropdown id="listingCategory" [options]="selectOptions?.listingCategories" [(ngModel)]="listing.listingsCategory" optionLabel="name"
|
|
||||||
optionValue="value" placeholder="Listing category" [disabled]="mode==='edit'"
|
|
||||||
[style]="{ width: '100%'}"></p-dropdown>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<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>
|
|
||||||
<div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<label for="description" class="block font-medium text-900 mb-2">Description</label>
|
|
||||||
<!-- <textarea id="description" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.description"></textarea> -->
|
|
||||||
<p-editor [(ngModel)]="listing.description" [style]="{ height: '320px' }" [modules]="editorModules">
|
|
||||||
<ng-template pTemplate="header"></ng-template>
|
|
||||||
</p-editor>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@if (listing.listingsCategory==='business'){
|
|
||||||
<div class="mb-4">
|
|
||||||
<label for="type" class="block font-medium text-900 mb-2">Type of business</label>
|
|
||||||
<p-dropdown id="type" [options]="selectOptions?.typesOfBusiness" [(ngModel)]="listing.type" optionLabel="name"
|
|
||||||
optionValue="value" [showClear]="true" placeholder="Type of business"
|
|
||||||
[style]="{ width: '100%'}"></p-dropdown>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (listing.listingsCategory==='commercialProperty'){
|
|
||||||
<div class="mb-4">
|
|
||||||
<label for="type" class="block font-medium text-900 mb-2">Property Category</label>
|
|
||||||
<p-dropdown id="type" [options]="selectOptions?.typesOfCommercialProperty" [(ngModel)]="listing.type" optionLabel="name"
|
|
||||||
optionValue="value" [showClear]="true" placeholder="Property Category"
|
|
||||||
[style]="{ width: '100%'}"></p-dropdown>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<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>
|
|
||||||
<div class="mb-4 col-12 md:col-6">
|
|
||||||
<label for="listingCategory" class="block font-medium text-900 mb-2">City</label>
|
|
||||||
<p-autoComplete [(ngModel)]="listing.city" [suggestions]="suggestions" (completeMethod)="search($event)"></p-autoComplete>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@if (listing.listingsCategory==='commercialProperty'){
|
|
||||||
<div class="grid">
|
|
||||||
<div class="mb-4 col-12 md:col-6">
|
|
||||||
<label for="zipCode" class="block font-medium text-900 mb-2">Zip Code</label>
|
|
||||||
<input id="zipCode" type="text" pInputText [(ngModel)]="listing.zipCode">
|
|
||||||
</div>
|
|
||||||
<div class="mb-4 col-12 md:col-6">
|
|
||||||
<label for="county" class="block font-medium text-900 mb-2">County</label>
|
|
||||||
<input id="county" type="text" pInputText [(ngModel)]="listing.county">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p-divider></p-divider>
|
|
||||||
<div class="flex gap-5 flex-column-reverse md:flex-row">
|
|
||||||
<div class="flex-auto p-fluid">
|
|
||||||
@if (listing.listingsCategory==='commercialProperty'){
|
|
||||||
<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>
|
|
||||||
<!-- <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 class="mb-4 col-12 md:col-6">
|
|
||||||
<div class="flex flex-column align-items-center flex-or">
|
|
||||||
<span class="font-medium text-900 mb-2">Property Pictures</span>
|
|
||||||
<!-- <img [src]="propertyPictureUrl" (error)="setImageToFallback($event)" class="image"/> -->
|
|
||||||
<p-fileUpload mode="basic"
|
|
||||||
chooseLabel="Upload"
|
|
||||||
[customUpload]="true"
|
|
||||||
name="file"
|
|
||||||
accept="image/*"
|
|
||||||
[maxFileSize]="maxFileSize"
|
|
||||||
(onSelect)="select($event)"
|
|
||||||
styleClass="p-button-outlined p-button-plain p-button-rounded mt-4">
|
|
||||||
</p-fileUpload>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="p-2 border-1 surface-border border-round mb-4 image-container" cdkDropListGroup mixedCdkDragDrop
|
|
||||||
(dropped)="onDrop($event)"
|
|
||||||
cdkDropListOrientation="horizontal">
|
|
||||||
@for (image of propertyImages; track image) {
|
|
||||||
<span cdkDropList mixedCdkDropList>
|
|
||||||
<div cdkDrag mixedCdkDragSizeHelper class="image-wrap">
|
|
||||||
<img src="{{environment.apiBaseUrl}}/property/{{listing.id}}/{{image.name}}"
|
|
||||||
[alt]="image.name" class="shadow-2" cdkDrag>
|
|
||||||
<fa-icon [icon]="faTrash" (click)="deleteConfirm(image.name)"></fa-icon>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (listing.listingsCategory==='business'){
|
|
||||||
<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>
|
|
||||||
<!-- <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 class="mb-4 col-12 md:col-6">
|
|
||||||
<label for="salesRevenue" class="block font-medium text-900 mb-2">Sales Revenue</label>
|
|
||||||
<app-inputNumber mode="currency" currency="USD" inputId="salesRevenue" [(ngModel)]="listing.salesRevenue"></app-inputNumber>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid">
|
|
||||||
<div class="mb-4 col-12 md:col-6">
|
|
||||||
<label for="cashFlow" class="block font-medium text-900 mb-2">Cash Flow</label>
|
|
||||||
<app-inputNumber mode="currency" currency="USD" inputId="cashFlow" [(ngModel)]="listing.cashFlow"></app-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>
|
|
||||||
<app-inputNumber mode="decimal" inputId="established" [(ngModel)]="listing.established"></app-inputNumber>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4 col-12 md:col-6">
|
|
||||||
<label for="employees" class="block font-medium text-900 mb-2">Employees</label>
|
|
||||||
<app-inputNumber mode="decimal" inputId="employees" [(ngModel)]="listing.employees"></app-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="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="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>
|
|
||||||
<app-inputNumber mode="decimal" inputId="internalListingNumber" type="text" [(ngModel)]="listing.internalListingNumber"></app-inputNumber>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<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'){
|
|
||||||
<button pButton pRipple label="Post Listing" class="w-auto" (click)="save()"></button>
|
|
||||||
} @else {
|
|
||||||
<button pButton pRipple label="Update Listing" class="w-auto" (click)="save()"></button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p-toast></p-toast>
|
|
||||||
<p-confirmDialog></p-confirmDialog>
|
|
||||||
@@ -1,29 +1,28 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
|
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
import dataListings from '../../../../assets/data/listings.json';
|
import { ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { SharedModule } from '../../../shared/shared/shared.module';
|
|
||||||
import { UserService } from '../../../services/user.service';
|
|
||||||
import { lastValueFrom } from 'rxjs';
|
|
||||||
import { ListingsService } from '../../../services/listings.service';
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
import { BusinessListing, ListingType, User } from '../../../../../../common-models/src/main.model';
|
import { UserService } from '../../../services/user.service';
|
||||||
|
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||||
|
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-favorites',
|
selector: 'app-favorites',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [MenuAccountComponent, SharedModule],
|
imports: [MenuAccountComponent, SharedModule],
|
||||||
templateUrl: './favorites.component.html',
|
templateUrl: './favorites.component.html',
|
||||||
styleUrl: './favorites.component.scss'
|
styleUrl: './favorites.component.scss',
|
||||||
})
|
})
|
||||||
export class FavoritesComponent {
|
export class FavoritesComponent {
|
||||||
user: User;
|
user: User;
|
||||||
listings: Array<ListingType> =[]//= dataListings as unknown as Array<BusinessListing>;
|
listings: Array<ListingType> = []; //= dataListings as unknown as Array<BusinessListing>;
|
||||||
favorites: Array<ListingType>
|
favorites: Array<ListingType>;
|
||||||
constructor(public userService: UserService, private listingsService:ListingsService, public selectOptions:SelectOptionsService){
|
constructor(public userService: UserService, private listingsService: ListingsService, public selectOptions: SelectOptionsService) {
|
||||||
this.user=this.userService.getUser();
|
this.user = this.userService.getKeycloakUser();
|
||||||
}
|
}
|
||||||
async ngOnInit(){
|
async ngOnInit() {
|
||||||
// this.listings=await lastValueFrom(this.listingsService.getAllListings());
|
// this.listings=await lastValueFrom(this.listingsService.getAllListings());
|
||||||
this.favorites=this.listings.filter(l=>l.favoritesForUser?.includes(this.user.id));
|
this.favorites = this.listings.filter(l => l.favoritesForUser?.includes(this.user.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,45 @@
|
|||||||
|
|
||||||
<div class="surface-ground px-4 py-8 md:px-6 lg:px-8 h-full">
|
<div class="surface-ground px-4 py-8 md:px-6 lg:px-8 h-full">
|
||||||
<div class="p-fluid flex flex-column lg:flex-row">
|
<div class="p-fluid flex flex-column lg:flex-row">
|
||||||
<menu-account></menu-account>
|
<menu-account></menu-account>
|
||||||
<p-toast></p-toast>
|
<p-toast></p-toast>
|
||||||
<p-confirmPopup></p-confirmPopup>
|
<p-confirmPopup></p-confirmPopup>
|
||||||
<div class="surface-card p-5 shadow-2 border-round flex-auto">
|
<div class="surface-card p-5 shadow-2 border-round flex-auto">
|
||||||
<div class="text-900 font-semibold text-lg mt-3">My Listings</div>
|
<div class="text-900 font-semibold text-lg mt-3">My Listings</div>
|
||||||
<p-divider></p-divider>
|
<p-divider></p-divider>
|
||||||
<p-table [value]="myListings" [tableStyle]="{ 'min-width': '50rem' }" dataKey="id" [paginator]="true" [rows]="10" [rowsPerPageOptions]="[10, 20, 50]" [showCurrentPageReport]="true" currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries">
|
<p-table
|
||||||
<ng-template pTemplate="header">
|
[value]="myListings"
|
||||||
<tr>
|
[tableStyle]="{ 'min-width': '50rem' }"
|
||||||
<th class="wide-column">Title</th>
|
dataKey="id"
|
||||||
<th>Category</th>
|
[paginator]="true"
|
||||||
<th>Located in</th>
|
[rows]="10"
|
||||||
<th></th>
|
[rowsPerPageOptions]="[10, 20, 50]"
|
||||||
</tr>
|
[showCurrentPageReport]="true"
|
||||||
</ng-template>
|
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries"
|
||||||
<ng-template pTemplate="body" let-listing>
|
>
|
||||||
<tr>
|
<ng-template pTemplate="header">
|
||||||
<td class="wide-column line-height-3">{{ listing.title }}</td>
|
<tr>
|
||||||
<td>{{ selectOptions.getListingsCategory(listing.listingsCategory) }}</td>
|
<th class="wide-column">Title</th>
|
||||||
<td>{{ selectOptions.getState(listing.location) }}</td>
|
<th>Category</th>
|
||||||
<td>
|
<th>Located in</th>
|
||||||
<button pButton pRipple icon="pi pi-pencil" class="p-button-rounded p-button-success mr-2" [routerLink]="['/editListing',listing.id]"></button>
|
<th></th>
|
||||||
<button pButton pRipple icon="pi pi-trash" class="p-button-rounded p-button-warning" (click)="confirm($event,listing)"></button>
|
</tr>
|
||||||
</td>
|
</ng-template>
|
||||||
</tr>
|
<ng-template pTemplate="body" let-listing>
|
||||||
</ng-template>
|
<tr>
|
||||||
</p-table>
|
<td class="wide-column line-height-3">{{ listing.title }}</td>
|
||||||
</div>
|
<td>{{ selectOptions.getListingsCategory(listing.listingsCategory) }}</td>
|
||||||
|
<td>{{ selectOptions.getState(listing.state) }}</td>
|
||||||
|
<td>
|
||||||
|
@if(isBusinessListing(listing)){
|
||||||
|
<button pButton pRipple icon="pi pi-pencil" class="p-button-rounded p-button-success mr-2" [routerLink]="['/editBusinessListing', listing.id]"></button>
|
||||||
|
} @if(isCommercialPropertyListing(listing)){
|
||||||
|
<button pButton pRipple icon="pi pi-pencil" class="p-button-rounded p-button-success mr-2" [routerLink]="['/editCommercialPropertyListing', listing.id]"></button>
|
||||||
|
}
|
||||||
|
<button pButton pRipple icon="pi pi-trash" class="p-button-rounded p-button-warning" (click)="confirm($event, listing)"></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user