Compare commits
11 Commits
e784b424b0
...
5230ef1230
| Author | SHA1 | Date |
|---|---|---|
|
|
5230ef1230 | |
|
|
d508415de4 | |
|
|
6b61c19bd7 | |
|
|
bb5a408cdc | |
|
|
9121ca1a69 | |
|
|
4230867608 | |
|
|
9e03620be7 | |
|
|
7f0f21b598 | |
|
|
c90d6b72b7 | |
|
|
c4cdcf4505 | |
|
|
7d10080069 |
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -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('*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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-----
|
||||||
|
|
@ -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-----
|
||||||
|
|
@ -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
Loading…
Reference in New Issue