Umbau auf postgres 2. step
This commit is contained in:
parent
c90d6b72b7
commit
7f0f21b598
|
|
@ -17,6 +17,49 @@
|
|||
"sourceMaps": true,
|
||||
"stopOnEntry": false,
|
||||
"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,
|
||||
|
||||
},
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -22,7 +22,8 @@
|
|||
"generate": "drizzle-kit generate:pg",
|
||||
"drop": "drizzle-kit drop",
|
||||
"migrate": "tsx src/drizzle/migrate.ts",
|
||||
"import": "tsx src/drizzle/import.ts"
|
||||
"import": "tsx src/drizzle/import.ts",
|
||||
"generateTypes":"tsx src/drizzle/generateTypes.ts src/drizzle/schema.ts src/models/db.model.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs-modules/mailer": "^1.10.3",
|
||||
|
|
@ -55,6 +56,8 @@
|
|||
"winston": "^3.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/parser": "^7.24.4",
|
||||
"@babel/traverse": "^7.24.1",
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
"@nestjs/schematics": "^10.0.0",
|
||||
"@nestjs/testing": "^10.0.0",
|
||||
|
|
@ -70,11 +73,13 @@
|
|||
"@types/supertest": "^6.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"commander": "^12.0.0",
|
||||
"drizzle-kit": "^0.20.16",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"kysely-codegen": "^0.15.0",
|
||||
"prettier": "^3.0.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^6.3.3",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { PG_CONNECTION } from './schema.js';
|
|||
// ssl: true,
|
||||
});
|
||||
|
||||
return drizzle(pool, { schema });
|
||||
return drizzle(pool, { schema, logger:true });
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -4,7 +4,8 @@ import pkg from 'pg';
|
|||
const { Pool } = pkg;
|
||||
import * as schema from './schema.js';
|
||||
import { readFileSync } from 'fs';
|
||||
import { User } from './schema.js';
|
||||
import { BusinessListing, CommercialPropertyListing, User } from 'src/models/db.model.js';
|
||||
|
||||
|
||||
const connectionString = process.env.DATABASE_URL
|
||||
// const pool = new Pool({connectionString})
|
||||
|
|
@ -31,7 +32,7 @@ for (const user of userData) {
|
|||
//Business Listings
|
||||
filePath = `./data/businesses.json`
|
||||
data = readFileSync(filePath, 'utf8');
|
||||
const businessJsonData = JSON.parse(data) as schema.BusinessListing[]; // Erwartet ein Array von Objekten
|
||||
const businessJsonData = JSON.parse(data) as BusinessListing[]; // Erwartet ein Array von Objekten
|
||||
|
||||
for (const business of businessJsonData) {
|
||||
delete business.id
|
||||
|
|
@ -42,7 +43,7 @@ for (const business of businessJsonData) {
|
|||
//Corporate Listings
|
||||
filePath = `./data/commercials.json`
|
||||
data = readFileSync(filePath, 'utf8');
|
||||
const commercialJsonData = JSON.parse(data) as schema.CommercialPropertyListing[]; // Erwartet ein Array von Objekten
|
||||
const commercialJsonData = JSON.parse(data) as CommercialPropertyListing[]; // Erwartet ein Array von Objekten
|
||||
for (const commercial of commercialJsonData) {
|
||||
delete commercial.id
|
||||
commercial.created = getRandomDateWithinLastYear();
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
CREATE TABLE IF NOT EXISTS "businesses" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"userId" uuid,
|
||||
"type" varchar(255),
|
||||
"type" integer,
|
||||
"title" varchar(255),
|
||||
"description" text,
|
||||
"city" varchar(255),
|
||||
"state" char(2),
|
||||
"price" numeric(10, 2),
|
||||
"price" double precision,
|
||||
"favoritesForUser" varchar(30)[],
|
||||
"draft" boolean,
|
||||
"listingsCategory" varchar(255),
|
||||
"realEstateIncluded" boolean,
|
||||
"leasedLocation" boolean,
|
||||
"franchiseResale" boolean,
|
||||
"salesRevenue" numeric(10, 2),
|
||||
"cashFlow" numeric(10, 2),
|
||||
"salesRevenue" double precision,
|
||||
"cashFlow" double precision,
|
||||
"supportAndTraining" text,
|
||||
"employees" integer,
|
||||
"established" integer,
|
||||
|
|
@ -31,12 +31,12 @@ CREATE TABLE IF NOT EXISTS "businesses" (
|
|||
CREATE TABLE IF NOT EXISTS "commercials" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"userId" uuid,
|
||||
"type" varchar(255),
|
||||
"type" integer,
|
||||
"title" varchar(255),
|
||||
"description" text,
|
||||
"city" varchar(255),
|
||||
"state" char(2),
|
||||
"price" numeric(10, 2),
|
||||
"price" double precision,
|
||||
"favoritesForUser" varchar(30)[],
|
||||
"hideImage" boolean,
|
||||
"draft" boolean,
|
||||
|
|
@ -46,6 +46,7 @@ CREATE TABLE IF NOT EXISTS "commercials" (
|
|||
"website" varchar(255),
|
||||
"phoneNumber" varchar(255),
|
||||
"imageOrder" varchar(30)[],
|
||||
"imagePath" varchar(30)[],
|
||||
"created" timestamp,
|
||||
"updated" timestamp,
|
||||
"visits" integer,
|
||||
|
|
@ -1 +0,0 @@
|
|||
ALTER TABLE "commercials" ADD COLUMN "imagePath" varchar(30)[];
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
ALTER TABLE "businesses" ALTER COLUMN "price" SET DATA TYPE numeric(12, 2);--> statement-breakpoint
|
||||
ALTER TABLE "businesses" ALTER COLUMN "salesRevenue" SET DATA TYPE numeric(12, 2);--> statement-breakpoint
|
||||
ALTER TABLE "businesses" ALTER COLUMN "cashFlow" SET DATA TYPE numeric(12, 2);--> statement-breakpoint
|
||||
ALTER TABLE "commercials" ALTER COLUMN "price" SET DATA TYPE numeric(12, 2);
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
ALTER TABLE "businesses" ALTER COLUMN "price" SET DATA TYPE double precision;--> statement-breakpoint
|
||||
ALTER TABLE "businesses" ALTER COLUMN "salesRevenue" SET DATA TYPE double precision;--> statement-breakpoint
|
||||
ALTER TABLE "businesses" ALTER COLUMN "cashFlow" SET DATA TYPE double precision;--> statement-breakpoint
|
||||
ALTER TABLE "commercials" ALTER COLUMN "price" SET DATA TYPE double precision;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"id": "221e028b-75cd-43da-83aa-9e3908ea9788",
|
||||
"id": "e8d0776a-ea8b-4c75-8a3a-e741620c4c4d",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "varchar(255)",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
},
|
||||
"price": {
|
||||
"name": "price",
|
||||
"type": "numeric(10, 2)",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
|
|
@ -95,13 +95,13 @@
|
|||
},
|
||||
"salesRevenue": {
|
||||
"name": "salesRevenue",
|
||||
"type": "numeric(10, 2)",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cashFlow": {
|
||||
"name": "cashFlow",
|
||||
"type": "numeric(10, 2)",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
|
|
@ -210,7 +210,7 @@
|
|||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "varchar(255)",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
|
|
@ -240,7 +240,7 @@
|
|||
},
|
||||
"price": {
|
||||
"name": "price",
|
||||
"type": "numeric(10, 2)",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
|
|
@ -298,6 +298,12 @@
|
|||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"imagePath": {
|
||||
"name": "imagePath",
|
||||
"type": "varchar(30)[]",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created": {
|
||||
"name": "created",
|
||||
"type": "timestamp",
|
||||
|
|
|
|||
|
|
@ -1,460 +0,0 @@
|
|||
{
|
||||
"id": "e3a1fac7-b93b-49e6-9ab4-b6dbb2362188",
|
||||
"prevId": "221e028b-75cd-43da-83aa-9e3908ea9788",
|
||||
"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": "varchar(255)",
|
||||
"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": "numeric(10, 2)",
|
||||
"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": "numeric(10, 2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cashFlow": {
|
||||
"name": "cashFlow",
|
||||
"type": "numeric(10, 2)",
|
||||
"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": "varchar(255)",
|
||||
"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": "numeric(10, 2)",
|
||||
"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(30)[]",
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,460 +0,0 @@
|
|||
{
|
||||
"id": "7bf04c81-2206-4dfd-a4d1-3f47dc91feff",
|
||||
"prevId": "e3a1fac7-b93b-49e6-9ab4-b6dbb2362188",
|
||||
"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": "varchar(255)",
|
||||
"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": "numeric(12, 2)",
|
||||
"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": "numeric(12, 2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cashFlow": {
|
||||
"name": "cashFlow",
|
||||
"type": "numeric(12, 2)",
|
||||
"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": "varchar(255)",
|
||||
"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": "numeric(12, 2)",
|
||||
"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(30)[]",
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,460 +0,0 @@
|
|||
{
|
||||
"id": "5e653a3e-6fb0-4ab1-9cce-46886e4a0c41",
|
||||
"prevId": "7bf04c81-2206-4dfd-a4d1-3f47dc91feff",
|
||||
"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": "varchar(255)",
|
||||
"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": "varchar(255)",
|
||||
"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(30)[]",
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,29 +5,8 @@
|
|||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1713620510315,
|
||||
"tag": "0000_wet_wasp",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "5",
|
||||
"when": 1713620815960,
|
||||
"tag": "0001_burly_daimon_hellstrom",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "5",
|
||||
"when": 1713631666329,
|
||||
"tag": "0002_same_loners",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "5",
|
||||
"when": 1713638770444,
|
||||
"tag": "0003_solid_senator_kelly",
|
||||
"when": 1713791559934,
|
||||
"tag": "0000_safe_natasha_romanoff",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export const users = pgTable('users', {
|
|||
export const businesses = pgTable('businesses', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('userId').references(()=>users.id),
|
||||
type: varchar('type', { length: 255 }),
|
||||
type: integer('type'),
|
||||
title: varchar('title', { length: 255 }),
|
||||
description: text('description'),
|
||||
city: varchar('city', { length: 255 }),
|
||||
|
|
@ -50,10 +50,11 @@ export const businesses = pgTable('businesses', {
|
|||
visits: integer('visits'),
|
||||
lastVisit: timestamp('lastVisit'),
|
||||
});
|
||||
|
||||
export const commercials = pgTable('commercials', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('userId').references(()=>users.id),
|
||||
type: varchar('type', { length: 255 }),
|
||||
type: integer('type'),
|
||||
title: varchar('title', { length: 255 }),
|
||||
description: text('description'),
|
||||
city: varchar('city', { length: 255 }),
|
||||
|
|
@ -74,6 +75,4 @@ export const commercials = pgTable('commercials', {
|
|||
visits: integer('visits'),
|
||||
lastVisit: timestamp('lastVisit'),
|
||||
});
|
||||
export type BusinessListing = InferInsertModel<typeof businesses>;
|
||||
export type CommercialPropertyListing = InferInsertModel<typeof commercials>;
|
||||
export type User = InferSelectModel<typeof users>;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@ import { DrizzleModule } from '../drizzle/drizzle.module.js';
|
|||
|
||||
|
||||
@Module({
|
||||
imports: [RedisModule,DrizzleModule
|
||||
],
|
||||
imports: [RedisModule,DrizzleModule],
|
||||
controllers: [BusinessListingsController, CommercialPropertyListingsController,UnknownListingsController,BrokerListingsController],
|
||||
providers: [ListingsService,FileService,UserService],
|
||||
exports: [ListingsService],
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
|||
import { Logger } from 'winston';
|
||||
import { EntityData, EntityId, Schema, SchemaDefinition } from 'redis-om';
|
||||
import { SQL, eq, gte, ilike, lte, sql, and} from 'drizzle-orm';
|
||||
import { BusinessListing, CommercialPropertyListing, PG_CONNECTION, businesses, commercials, } from '../drizzle/schema.js';
|
||||
import { PG_CONNECTION, businesses, commercials, } from '../drizzle/schema.js';
|
||||
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import * as schema from '../drizzle/schema.js';
|
||||
import { PgTableFn, PgTableWithColumns, QueryBuilder } from 'drizzle-orm/pg-core';
|
||||
import { BusinessListing, CommercialPropertyListing } from 'src/models/db.model.js';
|
||||
|
||||
@Injectable()
|
||||
export class ListingsService {
|
||||
|
|
@ -22,17 +23,6 @@ export class ListingsService {
|
|||
constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,) {
|
||||
}
|
||||
// private buildWhereClause(criteria: ListingCriteria): SQL {
|
||||
// const finalSql = sql`1=1`;
|
||||
// finalSql.append(criteria.type ? sql` AND 'type' = ${criteria.type}` : sql``)
|
||||
// finalSql.append(criteria.state ? sql` AND data->>'state' = ${criteria.state}` : sql``)
|
||||
// finalSql.append(criteria.minPrice ? sql` AND CAST(data->>'price' AS NUMERIC) >= ${parseFloat(criteria.minPrice)}` : sql``)
|
||||
// finalSql.append(criteria.maxPrice ? sql` AND CAST(data->>'price' AS NUMERIC) < ${parseFloat(criteria.maxPrice)}` : sql``)
|
||||
// finalSql.append(criteria.realEstateChecked !== undefined ? sql` AND CAST(data->>'realEstateIncluded' AS BOOLEAN) = ${criteria.realEstateChecked}` : sql``)
|
||||
// finalSql.append(criteria.title ? sql` AND LOWER(data->>'title') LIKE LOWER('%' || ${criteria.title} || '%')` : sql``)
|
||||
|
||||
// return finalSql
|
||||
// }
|
||||
private getConditions(criteria: ListingCriteria): any[] {
|
||||
const conditions = [];
|
||||
if (criteria.type) {
|
||||
|
|
@ -76,20 +66,10 @@ export class ListingsService {
|
|||
return result[0] as BusinessListing | CommercialPropertyListing
|
||||
}
|
||||
|
||||
// async findByPriceRange(minPrice: number, maxPrice: number, table: typeof businesses | typeof commercials): Promise<BusinessesJson[]> {
|
||||
// return this.conn.select().from(table).where(sql`${table}->>'price' BETWEEN ${minPrice} AND ${maxPrice}`);
|
||||
// }
|
||||
|
||||
// async findByState(state: string, table: typeof businesses | typeof commercials): Promise<BusinessesJson[]> {
|
||||
// return this.conn.select().from(table).where(sql`${table}->>'state' = ${state}`);
|
||||
// }
|
||||
|
||||
async findByUserId(userId: string, table: typeof businesses | typeof commercials): Promise<BusinessListing[] | CommercialPropertyListing[]> {
|
||||
return await this.conn.select().from(table).where(eq(table.userId, userId)) as BusinessListing[] | CommercialPropertyListing[]
|
||||
}
|
||||
// async findByTitleContains(title: string, table: typeof businesses | typeof commercials): Promise<BusinessesJson[]> {
|
||||
// return this.conn.select().from(table).where(sql`${table}->>'title' ILIKE '%' || ${title} || '%'`);
|
||||
// }
|
||||
|
||||
async createListing(data: BusinessListing | CommercialPropertyListing, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> {
|
||||
const newListing = { data, created: data.created, updated: data.updated, visits: 0, last_visit: null }
|
||||
const [createdListing] = await this.conn.insert(table).values(newListing).returning();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
listingsCategory?: string;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../common-models/src/main.model.ts
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
import { BusinessListing, CommercialPropertyListing } from "./db.model";
|
||||
|
||||
|
||||
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 interface ListingCriteria {
|
||||
start:number,
|
||||
length:number,
|
||||
page:number,
|
||||
pageCount:number,
|
||||
type:number,
|
||||
state:string,
|
||||
minPrice:number,
|
||||
maxPrice:number,
|
||||
realEstateChecked:boolean,
|
||||
title:string,
|
||||
listingsCategory:'business' | 'commercialProperty',
|
||||
category:'professional|broker'
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
export interface Sender {
|
||||
name?: string;
|
||||
email?: string;
|
||||
phoneNumber?: string;
|
||||
state?: string;
|
||||
comments?: string;
|
||||
}
|
||||
export interface ImageProperty {
|
||||
id:string;
|
||||
code:string;
|
||||
name:string;
|
||||
}
|
||||
|
|
@ -2,8 +2,7 @@ import { Body, Controller, Get, Inject, Param, Post, Put } from '@nestjs/common'
|
|||
import { UserService } from './user.service.js';
|
||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||
import { Logger } from 'winston';
|
||||
import { User } from 'src/drizzle/schema.js';
|
||||
|
||||
import { User } from 'src/models/db.model.js';
|
||||
|
||||
@Controller('user')
|
||||
export class UserController {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ import { UserController } from './user.controller.js';
|
|||
import { UserService } from './user.service.js';
|
||||
import { RedisModule } from '../redis/redis.module.js';
|
||||
import { FileService } from '../file/file.service.js';
|
||||
import { DrizzleModule } from '../drizzle/drizzle.module.js';
|
||||
|
||||
@Module({
|
||||
imports: [RedisModule],
|
||||
imports: [DrizzleModule],
|
||||
controllers: [UserController],
|
||||
providers: [UserService,FileService]
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,42 +4,45 @@ import { Entity, Repository, Schema } from 'redis-om';
|
|||
import { ListingCriteria } from '../models/main.model.js';
|
||||
import { REDIS_CLIENT } from '../redis/redis.module.js';
|
||||
import { FileService } from '../file/file.service.js';
|
||||
import { User } from 'src/drizzle/schema.js';
|
||||
import { User } from 'src/models/db.model.js';
|
||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||
import { Logger } from 'winston';
|
||||
import { PG_CONNECTION } from 'src/drizzle/schema.js';
|
||||
import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver.js';
|
||||
import * as schema from '../drizzle/schema.js';
|
||||
import { eq, sql,and } from 'drizzle-orm';
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
userRepository:Repository;
|
||||
userSchema = new Schema('user',{
|
||||
id: { type: 'string' },
|
||||
firstname: { type: 'string' },
|
||||
lastname: { type: 'string' },
|
||||
email: { type: 'string' },
|
||||
phoneNumber: { type: 'string' },
|
||||
companyOverview:{ type: 'string' },
|
||||
companyWebsite:{ type: 'string' },
|
||||
companyLocation:{ type: 'string' },
|
||||
offeredServices:{ type: 'string' },
|
||||
areasServed:{ type: 'string[]' },
|
||||
names:{ type: 'string[]', path:'$.licensedIn.name' },
|
||||
values:{ type: 'string[]', path:'$.licensedIn.value' }
|
||||
}, {
|
||||
dataStructure: 'JSON'
|
||||
})
|
||||
constructor(private fileService:FileService){
|
||||
// this.userRepository = new Repository(this.userSchema, redis)
|
||||
// this.userRepository.createIndex();
|
||||
constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||
@Inject(PG_CONNECTION) private conn: NodePgDatabase<typeof schema>,private fileService:FileService) {
|
||||
}
|
||||
private getConditions(criteria: ListingCriteria): any[] {
|
||||
const conditions = [];
|
||||
if (criteria.state) {
|
||||
conditions.push();
|
||||
}
|
||||
return conditions;
|
||||
}
|
||||
async getUserById( id:string){
|
||||
const user = await this.userRepository.fetch(id) as User;
|
||||
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>{
|
||||
return await this.userRepository.save(user.id,user) as 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();
|
||||
const users = await this.conn.execute(sql`SELECT * FROM users WHERE EXISTS (SELECT 1 FROM unnest(users."areasServed") AS area WHERE area LIKE '%' || ${criteria.state} || '%')`)
|
||||
return users.rows
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
|
|
@ -16,7 +16,8 @@
|
|||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
"esModuleInterop":true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { KeycloakEventType } from './models/keycloak-event';
|
|||
import { createGenericObject } from './utils/utils';
|
||||
import onChange from 'on-change';
|
||||
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({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
|
|
@ -24,7 +24,6 @@ import {ListingCriteria, User} from '../../../common-models/src/main.model'
|
|||
export class AppComponent {
|
||||
title = 'bizmatch';
|
||||
actualRoute ='';
|
||||
user:User;
|
||||
listingCriteria:ListingCriteria = onChange(createGenericObject<ListingCriteria>(),(path, value, previousValue, applyData)=>{
|
||||
sessionStorage.setItem('criteria',JSON.stringify(value));
|
||||
});
|
||||
|
|
@ -41,7 +40,6 @@ export class AppComponent {
|
|||
});
|
||||
}
|
||||
ngOnInit(){
|
||||
this.user = this.userService.getUser();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,18 +11,32 @@ import { authGuard } from './guards/auth.guard';
|
|||
import { PricingComponent } from './pages/pricing/pricing.component';
|
||||
import { LogoutComponent } from './components/logout/logout.component';
|
||||
import { DetailsUserComponent } from './pages/details/details-user/details-user.component';
|
||||
import { BusinessListingsComponent } from './pages/listings/business-listings/business-listings.component';
|
||||
import { CommercialPropertyListingsComponent } from './pages/listings/commercial-property-listings/commercial-property-listings.component';
|
||||
import { BrokerListingsComponent } from './pages/listings/broker-listings/broker-listings.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';
|
||||
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: 'listings/:type',
|
||||
component: ListingsComponent,
|
||||
},
|
||||
// {
|
||||
// path: 'listings/:type',
|
||||
// component: ListingsComponent,
|
||||
// },
|
||||
// Umleitung von /listing zu /listing/business
|
||||
{
|
||||
path: 'listings',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'listings/business',
|
||||
path: 'businessListings',
|
||||
component: BusinessListingsComponent,
|
||||
runGuardsAndResolvers:'always'
|
||||
},
|
||||
{
|
||||
path: 'commercialPropertyListings',
|
||||
component: CommercialPropertyListingsComponent,
|
||||
runGuardsAndResolvers:'always'
|
||||
},
|
||||
{
|
||||
path: 'brokerListings',
|
||||
component: BrokerListingsComponent,
|
||||
runGuardsAndResolvers:'always'
|
||||
},
|
||||
{
|
||||
|
|
@ -47,13 +61,23 @@ export const routes: Routes = [
|
|||
canActivate: [authGuard],
|
||||
},
|
||||
{
|
||||
path: 'editListing/:id',
|
||||
component: EditListingComponent,
|
||||
path: 'editBusinessListing/:id',
|
||||
component: EditBusinessListingComponent,
|
||||
canActivate: [authGuard],
|
||||
},
|
||||
{
|
||||
path: 'createListing',
|
||||
component: EditListingComponent,
|
||||
path: 'createBusinessListing',
|
||||
component: EditBusinessListingComponent,
|
||||
canActivate: [authGuard],
|
||||
},
|
||||
{
|
||||
path: 'editCommercialPropertyListing/:id',
|
||||
component: EditCommercialPropertyListingComponent,
|
||||
canActivate: [authGuard],
|
||||
},
|
||||
{
|
||||
path: 'createCommercialPropertyListing',
|
||||
component: EditCommercialPropertyListingComponent,
|
||||
canActivate: [authGuard],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ import { TabMenuModule } from 'primeng/tabmenu';
|
|||
import { Observable } from 'rxjs';
|
||||
import { faUserGear } from '@fortawesome/free-solid-svg-icons';
|
||||
import { Router } from '@angular/router';
|
||||
import { User } from '../../../../../common-models/src/main.model';
|
||||
|
||||
import {User} from '../../../../../bizmatch-server/src/models/db.model'
|
||||
@Component({
|
||||
selector: 'header',
|
||||
standalone: true,
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import { HttpEventType } from '@angular/common/http';
|
|||
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
|
||||
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
|
||||
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 { SelectButtonModule } from 'primeng/selectbutton';
|
||||
import { KeyValueRatio } from '../../../../../bizmatch-server/src/models/main.model';
|
||||
export const stateOptions:KeyValueRatio[]=[
|
||||
{label:'16/9',value:16/9},
|
||||
{label:'1/1',value:1},
|
||||
|
|
|
|||
|
|
@ -18,13 +18,14 @@ 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 { ImageProperty, ListingCriteria, ListingType, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
@Component({
|
||||
selector: 'app-details-listing',
|
||||
standalone: true,
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@
|
|||
">
|
||||
<div class="text-500 w-full md:w-2 font-medium">Licensed In</div>
|
||||
<div class="text-900 w-full md:w-10">
|
||||
@for (license of user.licensedIn; track license) {
|
||||
@for (license of userLicensedIn; track license) {
|
||||
<div>{{license.name}} : {{license.value}}</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Component } from '@angular/core';
|
|||
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||
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 { UserService } from '../../../services/user.service';
|
||||
|
|
@ -11,6 +11,8 @@ import { ListingsService } from '../../../services/listings.service';
|
|||
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||
import { ImageService } from '../../../services/image.service';
|
||||
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
import { KeyValue, ListingCriteria } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-details-user',
|
||||
|
|
@ -29,6 +31,7 @@ export class DetailsUserComponent {
|
|||
userListings:BusinessListing[]
|
||||
companyOverview:SafeHtml;
|
||||
offeredServices:SafeHtml;
|
||||
userLicensedIn :KeyValue[]
|
||||
constructor(private activatedRoute: ActivatedRoute,
|
||||
private router: Router,
|
||||
private userService: UserService,
|
||||
|
|
@ -41,7 +44,7 @@ export class DetailsUserComponent {
|
|||
|
||||
async ngOnInit() {
|
||||
this.user = await this.userService.getById(this.id);
|
||||
|
||||
this.userLicensedIn = this.user.licensedIn.map(l=>{return {name:l.split('|')[0],value:l.split('|')[1]}})
|
||||
this.userListings = await this.listingsService.getListingByUserId(this.id);
|
||||
this.user$ = this.userService.getUserObservable();
|
||||
this.companyOverview=this.sanitizer.bypassSecurityTrustHtml(this.user.companyOverview);
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ import { SelectOptionsService } from '../../services/select-options.service';
|
|||
import { UserService } from '../../services/user.service';
|
||||
import onChange from 'on-change';
|
||||
import { getCriteriaStateObject, getSessionStorageHandler } from '../../utils/utils';
|
||||
import { ListingCriteria, User } from '../../../../../common-models/src/main.model';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
|
||||
import { User } from '../../../../../bizmatch-server/src/models/db.model';
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
standalone: true,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
<div id="sky-line" class="hidden-lg-down">
|
||||
</div>
|
||||
<div class="search">
|
||||
<div class="wrapper">
|
||||
<div class="grid p-4 align-items-center">
|
||||
<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 (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>
|
||||
|
|
@ -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,115 @@
|
|||
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 { 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 { Observable, lastValueFrom } from 'rxjs';
|
||||
import { PaginatorModule } from 'primeng/paginator';
|
||||
import onChange from 'on-change';
|
||||
import { InitEditableRow } from 'primeng/table';
|
||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||
import { ListingsService } from '../../../services/listings.service';
|
||||
import { UserService } from '../../../services/user.service';
|
||||
import { ImageService } from '../../../services/image.service';
|
||||
import { createGenericObject, getCriteriaStateObject, getListingType, getSessionStorageHandler } from '../../../utils/utils';
|
||||
import { ListingCriteria, ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-broker-listings',
|
||||
standalone: true,
|
||||
imports: [CommonModule, StyleClassModule, ButtonModule, CheckboxModule, InputTextModule, DropdownModule, FormsModule, StyleClassModule, ToggleButtonModule, RouterModule, PaginatorModule],
|
||||
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;
|
||||
// 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.init()
|
||||
})
|
||||
|
||||
}
|
||||
async ngOnInit(){
|
||||
}
|
||||
async init(){
|
||||
|
||||
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,'professionals_brokers');
|
||||
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.criteria.start=event.first;
|
||||
this.criteria.length=event.rows;
|
||||
this.criteria.page=event.page;
|
||||
this.criteria.pageCount=event.pageCount;
|
||||
}
|
||||
imageErrorHandler(listing: ListingType) {
|
||||
// listing.hideImage = true; // Bild ausblenden, wenn es nicht geladen werden kann
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<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>
|
||||
}
|
||||
<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 listings; 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 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>
|
||||
}
|
||||
|
||||
|
||||
</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,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,114 @@
|
|||
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 { 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 { Observable, lastValueFrom } from 'rxjs';
|
||||
import { PaginatorModule } from 'primeng/paginator';
|
||||
import onChange from 'on-change';
|
||||
import { InitEditableRow } from 'primeng/table';
|
||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||
import { ListingsService } from '../../../services/listings.service';
|
||||
import { UserService } from '../../../services/user.service';
|
||||
import { ImageService } from '../../../services/image.service';
|
||||
import { createGenericObject, getCriteriaStateObject, getSessionStorageHandler } from '../../../utils/utils';
|
||||
import { ListingCriteria, ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-business-listings',
|
||||
standalone: true,
|
||||
imports: [CommonModule, StyleClassModule, ButtonModule, CheckboxModule, InputTextModule, DropdownModule, FormsModule, StyleClassModule, ToggleButtonModule, RouterModule, PaginatorModule],
|
||||
templateUrl: './business-listings.component.html',
|
||||
styleUrl: './business-listings.component.scss'
|
||||
})
|
||||
export class BusinessListingsComponent {
|
||||
environment=environment;
|
||||
listings: Array<BusinessListing>;
|
||||
users: Array<User>
|
||||
filteredListings: Array<BusinessListing>;
|
||||
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.init()
|
||||
})
|
||||
|
||||
}
|
||||
async ngOnInit(){
|
||||
}
|
||||
async init(){
|
||||
this.users=[]
|
||||
this.listings=await this.listingsService.getListings(this.criteria,'business');
|
||||
|
||||
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();
|
||||
|
||||
|
||||
}
|
||||
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,'business');
|
||||
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.criteria.start=event.first;
|
||||
this.criteria.length=event.rows;
|
||||
this.criteria.page=event.page;
|
||||
this.criteria.pageCount=event.pageCount;
|
||||
}
|
||||
imageErrorHandler(listing: ListingType) {
|
||||
// listing.hideImage = true; // Bild ausblenden, wenn es nicht geladen werden kann
|
||||
}
|
||||
}
|
||||
|
|
@ -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 [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 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]}}"
|
||||
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>
|
||||
|
||||
}
|
||||
</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,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,113 @@
|
|||
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 { 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 { Observable, lastValueFrom } from 'rxjs';
|
||||
import { PaginatorModule } from 'primeng/paginator';
|
||||
import onChange from 'on-change';
|
||||
import { InitEditableRow } from 'primeng/table';
|
||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||
import { ListingsService } from '../../../services/listings.service';
|
||||
import { UserService } from '../../../services/user.service';
|
||||
import { ImageService } from '../../../services/image.service';
|
||||
import { createGenericObject, getCriteriaStateObject, getSessionStorageHandler } from '../../../utils/utils';
|
||||
import { ListingCriteria, ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-commercial-property-listings',
|
||||
standalone: true,
|
||||
imports: [CommonModule, StyleClassModule, ButtonModule, CheckboxModule, InputTextModule, DropdownModule, FormsModule, StyleClassModule, ToggleButtonModule, RouterModule, PaginatorModule],
|
||||
templateUrl: './commercial-property-listings.component.html',
|
||||
styleUrl: './commercial-property-listings.component.scss'
|
||||
})
|
||||
export class CommercialPropertyListingsComponent {
|
||||
environment=environment;
|
||||
listings: Array<CommercialPropertyListing>;
|
||||
users: Array<User>
|
||||
filteredListings: Array<CommercialPropertyListing>;
|
||||
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()
|
||||
|
||||
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.init()
|
||||
})
|
||||
|
||||
}
|
||||
async ngOnInit(){
|
||||
}
|
||||
async init(){
|
||||
this.users=[]
|
||||
this.listings=await this.listingsService.getListings(this.criteria,'commercialProperty');
|
||||
|
||||
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();
|
||||
|
||||
|
||||
}
|
||||
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,'commercialProperty');
|
||||
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);
|
||||
this.criteria.start=event.first;
|
||||
this.criteria.length=event.rows;
|
||||
this.criteria.page=event.page;
|
||||
this.criteria.pageCount=event.pageCount;
|
||||
}
|
||||
imageErrorHandler(listing: ListingType) {
|
||||
// listing.hideImage = true; // Bild ausblenden, wenn es nicht geladen werden kann
|
||||
}
|
||||
}
|
||||
|
|
@ -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 listings; 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>
|
||||
|
|
@ -16,9 +16,11 @@ 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';
|
||||
import { ListingCriteria, ListingType } from '../../../../../bizmatch-server/src/models/main.model';
|
||||
import { User } from '../../../../../bizmatch-server/src/models/db.model';
|
||||
@Component({
|
||||
selector: 'app-listings',
|
||||
standalone: true,
|
||||
|
|
@ -27,102 +29,96 @@ import { ImageService } from '../../services/image.service';
|
|||
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;
|
||||
// environment=environment;
|
||||
// listings: Array<ListingType>;
|
||||
// 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) {
|
||||
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()
|
||||
})
|
||||
// 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);
|
||||
// }
|
||||
// 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();
|
||||
}
|
||||
// this.setStates();
|
||||
// this.totalRecords=this.listings.length
|
||||
// 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);
|
||||
this.criteria.start=event.first;
|
||||
this.criteria.length=event.rows;
|
||||
this.criteria.page=event.page;
|
||||
this.criteria.pageCount=event.pageCount;
|
||||
}
|
||||
imageErrorHandler(listing: ListingType) {
|
||||
listing.hideImage = true; // Bild ausblenden, wenn es nicht geladen werden kann
|
||||
}
|
||||
// }
|
||||
// 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.criteria.start=event.first;
|
||||
// this.criteria.length=event.rows;
|
||||
// this.criteria.page=event.page;
|
||||
// this.criteria.pageCount=event.pageCount;
|
||||
// }
|
||||
// imageErrorHandler(listing: ListingType) {
|
||||
// listing.hideImage = true; // Bild ausblenden, wenn es nicht geladen werden kann
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<label for="companyOverview" class="block font-medium text-900 mb-2">Licensed In</label>
|
||||
@for (licensedIn of user.licensedIn; track licensedIn.value){
|
||||
@for (licensedIn of userLicensedIn; track licensedIn.value){
|
||||
<div class="grid">
|
||||
<div class="flex col-12 md:col-6">
|
||||
<p-dropdown id="states" [options]="selectOptions?.states" [(ngModel)]="licensedIn.name"
|
||||
|
|
@ -192,21 +192,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <p-dialog header="Edit Image" [visible]="imageUrl" [modal]="true" [style]="{ width: '50vw' }" [draggable]="false" [resizable]="false">
|
||||
<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>
|
||||
} @else {
|
||||
<div></div>
|
||||
}
|
||||
<div>
|
||||
<p-button icon="pi" (click)="cancelUpload()" label="Cancel" [outlined]="true"></p-button>
|
||||
<p-button icon="pi pi-check" (click)="sendImage()" label="Finish" pAutoFocus [autofocus]="true"></p-button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</p-dialog> -->
|
||||
|
|
@ -23,7 +23,6 @@ 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';
|
||||
|
|
@ -36,6 +35,8 @@ import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dy
|
|||
import { ImageCropperComponent, stateOptions } from '../../../components/image-cropper/image-cropper.component';
|
||||
import Quill from 'quill'
|
||||
import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
import { AutoCompleteCompleteEvent, Invoice, KeyValue, Subscription } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
@Component({
|
||||
selector: 'app-account',
|
||||
standalone: true,
|
||||
|
|
@ -58,6 +59,7 @@ export class AccountComponent {
|
|||
dialogRef: DynamicDialogRef | undefined;
|
||||
environment = environment
|
||||
editorModules = TOOLBAR_OPTIONS
|
||||
userLicensedIn :KeyValue[]
|
||||
constructor(public userService: UserService,
|
||||
private subscriptionService: SubscriptionsService,
|
||||
private messageService: MessageService,
|
||||
|
|
@ -70,9 +72,10 @@ export class AccountComponent {
|
|||
public dialogService: DialogService) {}
|
||||
async ngOnInit() {
|
||||
this.user = await this.userService.getById(this.id);
|
||||
this.userLicensedIn = this.user.licensedIn.map(l=>{return {name:l.split('|')[0],value:l.split('|')[1]}})
|
||||
this.userSubscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions());
|
||||
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 ? `${environment.apiBaseUrl}/profile/${this.user.id}.avif` : `/assets/images/placeholder.png`
|
||||
|
|
@ -105,10 +108,10 @@ export class AccountComponent {
|
|||
this.suggestions = result.map(r => `${r.city} - ${r.state_code}`).slice(0, 5);
|
||||
}
|
||||
addLicence() {
|
||||
this.user.licensedIn.push({ name: '', value: '' });
|
||||
this.userLicensedIn.push({ name: '', value: '' });
|
||||
}
|
||||
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') {
|
||||
|
|
@ -141,7 +144,7 @@ export class AccountComponent {
|
|||
if (event.type === HttpEventType.Response) {
|
||||
this.loadingService.stopLoading('uploadImage');
|
||||
if (this.type==='company'){
|
||||
this.user.hasCompanyLogo=true;
|
||||
this.user.hasCompanyLogo=true;//
|
||||
this.companyLogoUrl=`${environment.apiBaseUrl}/logo/${this.user.id}.avif?_ts=${new Date().getTime()}`
|
||||
} else {
|
||||
this.user.hasProfile=true;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,155 @@
|
|||
<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"
|
||||
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]="selectOptions?.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,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 */
|
||||
}
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
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 { 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, getListingType } from '../../../utils/utils';
|
||||
import { ListingsService } from '../../../services/listings.service';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
|
||||
import { UserService } from '../../../services/user.service';
|
||||
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||
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 { 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';
|
||||
import { AutoCompleteCompleteEvent, ImageProperty, ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
@Component({
|
||||
selector: 'create-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 = 'commercialProperty'
|
||||
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: ImageProperty[]
|
||||
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;
|
||||
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) {
|
||||
this.user = this.userService.getUser();
|
||||
// Abonniere Router-Events, um den aktiven Link zu ermitteln
|
||||
this.router.events.subscribe(event => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
this.mode = event.url === '/createListing' ? 'create' : 'edit';
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
async ngOnInit() {
|
||||
if (this.mode === 'edit') {
|
||||
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id));
|
||||
} else {
|
||||
const uuid = sessionStorage.getItem('uuid') ? sessionStorage.getItem('uuid') : uuidv4();
|
||||
sessionStorage.setItem('uuid', uuid);
|
||||
this.listing = createGenericObject<BusinessListing>();
|
||||
this.listing.id = uuid
|
||||
this.listing.userId = this.user.id
|
||||
}
|
||||
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`;
|
||||
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
|
||||
}
|
||||
|
||||
async save() {
|
||||
sessionStorage.removeItem('uuid')
|
||||
await this.listingsService.save(this.listing, getListingType(this.listing));
|
||||
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);
|
||||
}
|
||||
|
||||
select(event: any) {
|
||||
const imageUrl = URL.createObjectURL(event.files[0]);
|
||||
this.dialogRef = this.dialogService.open(ImageCropperComponent, {
|
||||
data: {
|
||||
imageUrl: imageUrl,
|
||||
fileUpload: this.fileUpload,
|
||||
ratioVariable: false
|
||||
},
|
||||
header: 'Edit Image',
|
||||
width: '50vw',
|
||||
modal: true,
|
||||
closeOnEscape: true,
|
||||
keepInViewport: true,
|
||||
closable: false,
|
||||
breakpoints: {
|
||||
'960px': '75vw',
|
||||
'640px': '90vw'
|
||||
},
|
||||
});
|
||||
this.dialogRef.onClose.subscribe(cropper => {
|
||||
if (cropper){
|
||||
this.loadingService.startLoading('uploadImage');
|
||||
cropper.getCroppedCanvas().toBlob(async (blob) => {
|
||||
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));
|
||||
}, 'image/jpg');
|
||||
cropper.destroy();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deleteConfirm(imageName: string) {
|
||||
this.confirmationService.confirm({
|
||||
target: event.target as EventTarget,
|
||||
message: `Do you want to delete this image ${imageName}?`,
|
||||
header: 'Delete Confirmation',
|
||||
icon: 'pi pi-info-circle',
|
||||
acceptButtonStyleClass: "p-button-danger p-button-text",
|
||||
rejectButtonStyleClass: "p-button-text p-button-text",
|
||||
acceptIcon: "none",
|
||||
rejectIcon: "none",
|
||||
|
||||
accept: async () => {
|
||||
await this.imageService.deleteListingImage(this.listing.id, imageName);
|
||||
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Image deleted' });
|
||||
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
|
||||
|
||||
},
|
||||
reject: () => {
|
||||
// this.messageService.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected' });
|
||||
console.log('deny')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDrop(event: { previousIndex: number; currentIndex: number }) {
|
||||
moveItemInArray(this.propertyImages, event.previousIndex, event.currentIndex);
|
||||
this.listingsService.changeImageOrder(this.listing.id, this.propertyImages)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
<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"
|
||||
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]="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="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>
|
||||
<!-- <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>
|
||||
|
||||
<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 */
|
||||
}
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
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 { 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, getListingType } from '../../../utils/utils';
|
||||
import { ListingsService } from '../../../services/listings.service';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
|
||||
import { UserService } from '../../../services/user.service';
|
||||
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||
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 { 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';
|
||||
import { AutoCompleteCompleteEvent, ImageProperty, ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
@Component({
|
||||
selector: 'create-listing',
|
||||
standalone: true,
|
||||
imports: [SharedModule, ArrayToStringPipe, InputNumberModule, CarouselModule,
|
||||
DialogModule, AngularCropperjsModule, FileUploadModule, EditorModule, DynamicDialogModule, DragDropModule,
|
||||
ConfirmDialogModule, MixedCdkDragDropModule],
|
||||
providers: [MessageService, DialogService, ConfirmationService],
|
||||
templateUrl: './edit-commercial-property-listing.component.html',
|
||||
styleUrl: './edit-commercial-property-listing.component.scss'
|
||||
})
|
||||
export class EditCommercialPropertyListingComponent {
|
||||
@ViewChild(FileUpload) public fileUpload: FileUpload;
|
||||
listingsCategory = 'commercialProperty'
|
||||
category: string;
|
||||
location: string;
|
||||
mode: 'edit' | 'create';
|
||||
separator: '\n\n'
|
||||
listing: CommercialPropertyListing
|
||||
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
||||
user: User;
|
||||
maxFileSize = 3000000;
|
||||
uploadUrl: string;
|
||||
environment = environment;
|
||||
propertyImages: ImageProperty[]
|
||||
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;
|
||||
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) {
|
||||
this.user = this.userService.getUser();
|
||||
// Abonniere Router-Events, um den aktiven Link zu ermitteln
|
||||
this.router.events.subscribe(event => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
this.mode = event.url === '/createListing' ? 'create' : 'edit';
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
async ngOnInit() {
|
||||
if (this.mode === 'edit') {
|
||||
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id));
|
||||
} else {
|
||||
const uuid = sessionStorage.getItem('uuid') ? sessionStorage.getItem('uuid') : uuidv4();
|
||||
sessionStorage.setItem('uuid', uuid);
|
||||
this.listing = createGenericObject<CommercialPropertyListing>();
|
||||
this.listing.id = uuid
|
||||
this.listing.userId = this.user.id
|
||||
}
|
||||
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`;
|
||||
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
|
||||
}
|
||||
|
||||
async save() {
|
||||
sessionStorage.removeItem('uuid')
|
||||
await this.listingsService.save(this.listing, getListingType(this.listing));
|
||||
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);
|
||||
}
|
||||
|
||||
select(event: any) {
|
||||
const imageUrl = URL.createObjectURL(event.files[0]);
|
||||
this.dialogRef = this.dialogService.open(ImageCropperComponent, {
|
||||
data: {
|
||||
imageUrl: imageUrl,
|
||||
fileUpload: this.fileUpload,
|
||||
ratioVariable: false
|
||||
},
|
||||
header: 'Edit Image',
|
||||
width: '50vw',
|
||||
modal: true,
|
||||
closeOnEscape: true,
|
||||
keepInViewport: true,
|
||||
closable: false,
|
||||
breakpoints: {
|
||||
'960px': '75vw',
|
||||
'640px': '90vw'
|
||||
},
|
||||
});
|
||||
this.dialogRef.onClose.subscribe(cropper => {
|
||||
if (cropper){
|
||||
this.loadingService.startLoading('uploadImage');
|
||||
cropper.getCroppedCanvas().toBlob(async (blob) => {
|
||||
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));
|
||||
}, 'image/jpg');
|
||||
cropper.destroy();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deleteConfirm(imageName: string) {
|
||||
this.confirmationService.confirm({
|
||||
target: event.target as EventTarget,
|
||||
message: `Do you want to delete this image ${imageName}?`,
|
||||
header: 'Delete Confirmation',
|
||||
icon: 'pi pi-info-circle',
|
||||
acceptButtonStyleClass: "p-button-danger p-button-text",
|
||||
rejectButtonStyleClass: "p-button-text p-button-text",
|
||||
acceptIcon: "none",
|
||||
rejectIcon: "none",
|
||||
|
||||
accept: async () => {
|
||||
await this.imageService.deleteListingImage(this.listing.id, imageName);
|
||||
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Image deleted' });
|
||||
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
|
||||
|
||||
},
|
||||
reject: () => {
|
||||
// this.messageService.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected' });
|
||||
console.log('deny')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDrop(event: { previousIndex: number; currentIndex: number }) {
|
||||
moveItemInArray(this.propertyImages, event.previousIndex, event.currentIndex);
|
||||
this.listingsService.changeImageOrder(this.listing.id, this.propertyImages)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -17,7 +17,7 @@ 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 { createGenericObject, getListingType } from '../../../utils/utils';
|
||||
import { ListingsService } from '../../../services/listings.service';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
|
|
@ -25,7 +25,6 @@ import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
|
|||
import { UserService } from '../../../services/user.service';
|
||||
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';
|
||||
|
|
@ -45,6 +44,8 @@ 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';
|
||||
import { AutoCompleteCompleteEvent, ImageProperty, ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
@Component({
|
||||
selector: 'create-listing',
|
||||
standalone: true,
|
||||
|
|
@ -120,7 +121,6 @@ export class EditListingComponent {
|
|||
this.listing = createGenericObject<BusinessListing>();
|
||||
this.listing.id = uuid
|
||||
this.listing.userId = this.user.id
|
||||
this.listing.listingsCategory = 'business';
|
||||
}
|
||||
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`;
|
||||
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
|
||||
|
|
@ -128,7 +128,7 @@ export class EditListingComponent {
|
|||
|
||||
async save() {
|
||||
sessionStorage.removeItem('uuid')
|
||||
await this.listingsService.save(this.listing, this.listing.listingsCategory);
|
||||
await this.listingsService.save(this.listing, getListingType(this.listing));
|
||||
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 });
|
||||
}
|
||||
|
||||
|
|
@ -173,20 +173,6 @@ export class EditListingComponent {
|
|||
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) {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import { UserService } from '../../../services/user.service';
|
|||
import { lastValueFrom } from 'rxjs';
|
||||
import { ListingsService } from '../../../services/listings.service';
|
||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||
import { BusinessListing, ListingType, User } from '../../../../../../common-models/src/main.model';
|
||||
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
import { ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-favorites',
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ import { ListingsService } from '../../../services/listings.service';
|
|||
import { lastValueFrom } from 'rxjs';
|
||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||
import { BusinessListing, ListingType, User } from '../../../../../../common-models/src/main.model';
|
||||
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
import { ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
import { getListingType } from '../../../utils/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-my-listing',
|
||||
standalone: true,
|
||||
|
|
@ -30,7 +33,7 @@ export class MyListingComponent {
|
|||
}
|
||||
|
||||
async deleteListing(listing:ListingType){
|
||||
await this.listingsService.deleteListing(listing.id,listing.listingsCategory);
|
||||
await this.listingsService.deleteListing(listing.id,getListingType(listing));
|
||||
// this.listings=await lastValueFrom(this.listingsService.getAllListings());
|
||||
this.myListings=this.listings.filter(l=>l.userId===this.user.id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http';
|
|||
import { Injectable } from '@angular/core';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { ImageType } from '../../../../common-models/src/main.model';
|
||||
import { ImageType } from '../../../../bizmatch-server/src/models/main.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ import { HttpClient } from '@angular/common/http';
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Observable, lastValueFrom } from 'rxjs';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { BusinessListing, CommercialPropertyListing, ImageProperty, ListingCriteria, ListingType, ResponseBusinessListing, ResponseBusinessListingArray, ResponseCommercialPropertyListing, ResponseCommercialPropertyListingArray } from '../../../../common-models/src/main.model';
|
||||
import onChange from 'on-change';
|
||||
import { getSessionStorageHandler } from '../utils/utils';
|
||||
import { getListingType, getSessionStorageHandler } from '../utils/utils';
|
||||
import { ImageProperty, ListingCriteria, ListingType, ResponseBusinessListingArray, ResponseCommercialPropertyListingArray } from '../../../../bizmatch-server/src/models/main.model';
|
||||
import { BusinessListing } from '../../../../bizmatch-server/src/models/db.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
|
@ -17,8 +18,8 @@ export class ListingsService {
|
|||
// getAllListings():Observable<ListingType[]>{
|
||||
// return this.http.get<ListingType[]>(`${this.apiBaseUrl}/bizmatch/business-listings`);
|
||||
// }
|
||||
async getListings(criteria:ListingCriteria):Promise<ListingType[]>{
|
||||
const result = await lastValueFrom(this.http.post<ResponseBusinessListingArray|ResponseCommercialPropertyListingArray>(`${this.apiBaseUrl}/bizmatch/listings/${criteria.listingsCategory}/search`,criteria));
|
||||
async getListings(criteria:ListingCriteria,listingsCategory:'business'|'professionals_brokers'|'commercialProperty'):Promise<ListingType[]>{
|
||||
const result = await lastValueFrom(this.http.post<ResponseBusinessListingArray>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/search`,criteria));
|
||||
return result.data;
|
||||
}
|
||||
getListingById(id:string,listingsCategory?:'business'|'commercialProperty'):Observable<ListingType>{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { MailInfo } from '../../../../common-models/src/main.model';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { MailInfo } from '../../../../bizmatch-server/src/models/main.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { HttpClient } from '@angular/common/http';
|
|||
import { InitEditableRow } from 'primeng/table';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { KeyValue, KeyValueStyle } from '../../../../common-models/src/main.model';
|
||||
import { KeyValue, KeyValueStyle } from '../../../../bizmatch-server/src/models/main.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
|
|
@ -38,12 +38,11 @@ export class SelectOptionsService {
|
|||
getState(value:string):string{
|
||||
return this.states.find(l=>l.value===value)?.name
|
||||
}
|
||||
|
||||
getBusiness(value:string):string{
|
||||
return this.typesOfBusiness.find(t=>t.value===value)?.name
|
||||
getBusiness(value:number):string{
|
||||
return this.typesOfBusiness.find(t=>t.value===String(value))?.name
|
||||
}
|
||||
getCommercialProperty(value:string):string{
|
||||
return this.typesOfCommercialProperty.find(t=>t.value===value)?.name
|
||||
getCommercialProperty(value:number):string{
|
||||
return this.typesOfCommercialProperty.find(t=>t.value===String(value))?.name
|
||||
}
|
||||
getListingsCategory(value:string):string{
|
||||
return this.listingCategories.find(l=>l.value===value)?.name
|
||||
|
|
@ -70,11 +69,11 @@ export class SelectOptionsService {
|
|||
getTextColorType(value:string):string{
|
||||
return this.typesOfBusiness.find(c=>c.value===value)?.textColorClass
|
||||
}
|
||||
getBgColorType(value:string):string{
|
||||
return this.typesOfBusiness.find(c=>c.value===value)?.bgColorClass
|
||||
getBgColorType(value:number):string{
|
||||
return this.typesOfBusiness.find(c=>c.value===String(value))?.bgColorClass
|
||||
}
|
||||
getIconAndTextColorType(value:string):string{
|
||||
const category = this.typesOfBusiness.find(c=>c.value===value)
|
||||
getIconAndTextColorType(value:number):string{
|
||||
const category = this.typesOfBusiness.find(c=>c.value===String(value))
|
||||
return `${category?.icon} ${category?.textColorClass}`
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
|||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Subscription } from '../../../../common-models/src/main.model';
|
||||
import { Subscription } from '../../../../bizmatch-server/src/models/main.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ import { jwtDecode } from 'jwt-decode';
|
|||
import { Observable, distinctUntilChanged, filter, from, lastValueFrom, map } from 'rxjs';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { KeycloakService } from './keycloak.service';
|
||||
import { JwtToken, ListingCriteria, User } from '../../../../common-models/src/main.model';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { User } from '../../../../bizmatch-server/src/models/db.model';
|
||||
import { JwtToken, ListingCriteria } from '../../../../bizmatch-server/src/models/main.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { INFO, ConsoleFormattedStream, createLogger as _createLogger, stdSerializers } from "browser-bunyan";
|
||||
import { ListingCriteria } from "../../../../common-models/src/main.model";
|
||||
import { ListingCriteria } from "../../../../bizmatch-server/src/models/main.model";
|
||||
import { BusinessListing, CommercialPropertyListing } from "../../../../bizmatch-server/src/models/db.model";
|
||||
|
||||
export function createGenericObject<T>(): T {
|
||||
// Ein leeres Objekt vom Typ T erstellen
|
||||
|
|
@ -32,7 +33,10 @@ export function createGenericObject<T>(): T {
|
|||
|
||||
export function getCriteriaStateObject(){
|
||||
const initialState = createGenericObject<ListingCriteria>();
|
||||
initialState.listingsCategory='business';
|
||||
const storedState = sessionStorage.getItem('criteria');
|
||||
return storedState ? JSON.parse(storedState) : initialState;
|
||||
}
|
||||
|
||||
export function getListingType(listing:BusinessListing|CommercialPropertyListing){
|
||||
return listing.type<100?'business':'commercialProperty';
|
||||
}
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
import { BusinessListing, CommercialPropertyListing } from "src/drizzle/schema.js";
|
||||
|
||||
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 interface Listing {
|
||||
// id: string;
|
||||
// userId: string;
|
||||
// type: string; //enum
|
||||
// title: string;
|
||||
// description: string;
|
||||
// city: string,
|
||||
// state: string;//enum
|
||||
// price?: number;
|
||||
// favoritesForUser:Array<string>;
|
||||
// hideImage?:boolean;
|
||||
// draft?:boolean;
|
||||
// created:Date;
|
||||
// updated:Date;
|
||||
// }
|
||||
// export interface BusinessListing extends Listing {
|
||||
// listingsCategory: 'business'; //enum
|
||||
// realEstateIncluded?: boolean;
|
||||
// leasedLocation?:boolean;
|
||||
// franchiseResale?:boolean;
|
||||
// salesRevenue?: number;
|
||||
// cashFlow?: number;
|
||||
// supportAndTraining?: string;
|
||||
// employees?: number;
|
||||
// established?: number;
|
||||
// internalListingNumber?:number;
|
||||
// reasonForSale?: string;
|
||||
// brokerLicencing?: string;
|
||||
// internals?: string;
|
||||
// }
|
||||
// export interface CommercialPropertyListing extends Listing {
|
||||
// listingsCategory: 'commercialProperty'; //enum
|
||||
// zipCode:number;
|
||||
// county:string
|
||||
// email?: string;
|
||||
// website?: string;
|
||||
// phoneNumber?: string;
|
||||
// imageOrder?:ImageProperty[];
|
||||
// }
|
||||
// export interface UserBase {
|
||||
// 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;
|
||||
// }
|
||||
// export interface User extends UserBase {
|
||||
// licensedIn?:KeyValue[];
|
||||
// }
|
||||
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 interface ListingCriteria {
|
||||
start:number,
|
||||
length:number,
|
||||
page:number,
|
||||
pageCount:number,
|
||||
type:string,
|
||||
state:string,
|
||||
minPrice:number,
|
||||
maxPrice:number,
|
||||
realEstateChecked:boolean,
|
||||
title:string,
|
||||
listingsCategory:'business'|'professionals_brokers'|'commercialProperty',
|
||||
category:'professional|broker'
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
export interface Sender {
|
||||
name?: string;
|
||||
email?: string;
|
||||
phoneNumber?: string;
|
||||
state?: string;
|
||||
comments?: string;
|
||||
}
|
||||
export interface ImageProperty {
|
||||
id:string;
|
||||
code:string;
|
||||
name:string;
|
||||
}
|
||||
|
|
@ -41,5 +41,21 @@
|
|||
"${workspaceFolder}/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "generateTypes",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"program": "${workspaceFolder}/generateTypes.js",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/**/*.js"
|
||||
],
|
||||
"args": [
|
||||
"--sourcefile","schema.ts",
|
||||
"--outfile", "model.ts",
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
export interface User {
|
||||
id: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
email: string;
|
||||
phoneNumber?: string;
|
||||
description?: string;
|
||||
created_at: Date;
|
||||
updated_at?: Date;
|
||||
isActive: boolean;
|
||||
score?: number;
|
||||
balance?: number;
|
||||
tags?: string[];
|
||||
}
|
||||
|
|
@ -1,30 +1,30 @@
|
|||
import yargs from 'yargs'
|
||||
import fs from 'fs-extra';
|
||||
import { hideBin } from 'yargs/helpers'
|
||||
import { BusinessListing } from "../common-models/src/main.model"
|
||||
// import yargs from 'yargs'
|
||||
// import fs from 'fs-extra';
|
||||
// import { hideBin } from 'yargs/helpers'
|
||||
// import { BusinessListing } from "../common-models/src/main.model"
|
||||
|
||||
const argv = yargs(hideBin(process.argv)).argv
|
||||
// const argv = yargs(hideBin(process.argv)).argv
|
||||
|
||||
if (!argv.userId){
|
||||
console.log(' --userId [any valid userId]')
|
||||
process.exit(1)
|
||||
}
|
||||
// if (!argv.userId){
|
||||
// console.log(' --userId [any valid userId]')
|
||||
// process.exit(1)
|
||||
// }
|
||||
|
||||
(async () => {
|
||||
console
|
||||
const response = await fetch('http://localhost:3000/bizmatch/listings', {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
const listings:Array<BusinessListing> = await response.json();
|
||||
for (const listing of listings) {
|
||||
listing.userId=argv.userId;
|
||||
listing.created=new Date()
|
||||
listing.updated=new Date()
|
||||
const response = await fetch(`http://localhost:3000/bizmatch/listings/${listing.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(listing),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
})();
|
||||
// (async () => {
|
||||
// console
|
||||
// const response = await fetch('http://localhost:3000/bizmatch/listings', {
|
||||
// method: 'GET',
|
||||
// headers: { 'Content-Type': 'application/json' },
|
||||
// })
|
||||
// const listings:Array<BusinessListing> = await response.json();
|
||||
// for (const listing of listings) {
|
||||
// listing.userId=argv.userId;
|
||||
// listing.created=new Date()
|
||||
// listing.updated=new Date()
|
||||
// const response = await fetch(`http://localhost:3000/bizmatch/listings/${listing.id}`, {
|
||||
// method: 'PUT',
|
||||
// body: JSON.stringify(listing),
|
||||
// headers: { 'Content-Type': 'application/json' },
|
||||
// });
|
||||
// }
|
||||
// })();
|
||||
|
|
|
|||
|
|
@ -2698,5 +2698,161 @@
|
|||
"description": "<p>Acquire this stunning waterfront restaurant and event venue located in the historic district of Annapolis, Maryland, offering breathtaking views of the Chesapeake Bay and Annapolis Harbor. The property features a beautifully restored building, multiple dining areas, and a large outdoor terrace, making it a highly sought-after destination for dining, weddings, and special events.</p><h3>Waterfront restaurant and event venue features:</h3><p>- 10,000 square feet of indoor and outdoor dining and event space<br>- Seating capacity for up to 200 guests<br>- Expansive waterfront terrace with unobstructed views of the Chesapeake Bay<br>- Fully equipped commercial kitchen and bar<br>- Elegantly appointed private dining rooms for intimate gatherings<br>- Prime location in the heart of Annapolis' historic district</p><p>Invest in this exceptional waterfront restaurant and event venue and capitalize on the strong demand for unique dining experiences and memorable event spaces in the charming and historically significant Annapolis market, a popular destination for tourists and locals alike.</p>",
|
||||
"type": "100",
|
||||
"imageOrder": []
|
||||
},
|
||||
{
|
||||
"id": "a1b2c3d4-e5f6-7g8h-9i0j-1k2l3m4n5o6p",
|
||||
"userId": "",
|
||||
"listingsCategory": "commercialProperty",
|
||||
"title": "Scenic Hill Country Ranch",
|
||||
"state": "TX",
|
||||
"hasImages": true,
|
||||
"price": 7500000,
|
||||
"city": "Fredericksburg",
|
||||
"description": "<h2>Stunning Ranch in the Heart of Texas Hill Country</h2><p>Discover the beauty and tranquility of this breathtaking 500-acre ranch nestled in the picturesque Texas Hill Country. With its rolling hills, lush pastures, and crystal-clear streams, this property offers a perfect blend of natural beauty and recreational opportunities.</p><p>Key features include:</p><ul><li>Main residence: 4,500 sq ft luxury home with 5 bedrooms and 4.5 bathrooms</li><li>Guest house: Charming 2-bedroom, 2-bathroom cottage</li><li>Equestrian facilities: 10-stall barn, riding arena, and miles of riding trails</li><li>Hunting and fishing: Abundant wildlife and stocked ponds</li></ul><p>This exceptional ranch is a rare find and presents a unique opportunity for those seeking a luxury retreat or a profitable hunting and recreation property.</p>",
|
||||
"type": "101",
|
||||
"imageOrder": []
|
||||
},
|
||||
{
|
||||
"id": "5e6f7g8h-9i0j-1k2l-3m4n-5o6p7q8r9s0t",
|
||||
"userId": "",
|
||||
"listingsCategory": "commercialProperty",
|
||||
"title": "Thriving Gas Station and Convenience Store",
|
||||
"state": "TX",
|
||||
"hasImages": true,
|
||||
"price": 1750000,
|
||||
"city": "San Antonio",
|
||||
"description": "<p>Well-established gas station and convenience store located in a high-traffic area of San Antonio. This profitable business offers a prime opportunity for investors seeking a stable income stream and growth potential.</p><h3>Property and Business Highlights:</h3><ul><li>Large 1-acre lot with ample parking</li><li>4,000 sq ft convenience store with modern fixtures</li><li>High-volume fuel sales and diverse in-store product mix</li><li>Long-term supplier contracts and loyal customer base</li></ul><p>Take advantage of this turn-key investment opportunity in San Antonio's robust retail market.</p>",
|
||||
"type": "100",
|
||||
"imageOrder": []
|
||||
},
|
||||
{
|
||||
"id": "7g8h9i0j-1k2l-3m4n-5o6p-7q8r9s0t1u2v",
|
||||
"userId": "",
|
||||
"listingsCategory": "commercialProperty",
|
||||
"title": "Full-Service Car Wash in High-Growth Austin Suburb",
|
||||
"state": "TX",
|
||||
"hasImages": true,
|
||||
"price": 3500000,
|
||||
"city": "Round Rock",
|
||||
"description": "<p>Highly profitable, full-service car wash located in the rapidly growing Austin suburb of Round Rock. This well-maintained facility has a strong reputation for quality service and boasts a loyal customer base in a high-income residential area.</p><h3>Business and Property Highlights:</h3><ul><li>1.5-acre site with spacious parking and attractive landscaping</li><li>4 self-serve bays and 2 automatic wash tunnels</li><li>Modern equipment and computerized payment systems</li><li>Consistent year-over-year revenue growth and high margins</li></ul><p>Capitalize on the booming Austin metro market with this established, high-performing car wash business.</p>",
|
||||
"type": "100",
|
||||
"imageOrder": []
|
||||
},
|
||||
{
|
||||
"id": "9i0j1k2l-3m4n-5o6p-7q8r-9s0t1u2v3w4x",
|
||||
"userId": "",
|
||||
"listingsCategory": "commercialProperty",
|
||||
"title": "Historic Church Building in Downtown San Antonio",
|
||||
"state": "TX",
|
||||
"hasImages": true,
|
||||
"price": 2200000,
|
||||
"city": "San Antonio",
|
||||
"description": "<p>Unique opportunity to acquire a beautifully preserved historic church building in the heart of downtown San Antonio. This iconic property offers a blend of old-world charm and modern amenities, making it an ideal space for a variety of commercial or community uses.</p><h3>Property Highlights:</h3><ul><li>10,000 sq ft of versatile interior space with soaring ceilings</li><li>Meticulously maintained original architectural features</li><li>Newly updated electrical, plumbing, and HVAC systems</li><li>Prime location near popular attractions and public transportation</li></ul><p>Embrace the rich history and endless potential of this one-of-a-kind property in San Antonio's vibrant downtown district.</p>",
|
||||
"type": "106",
|
||||
"imageOrder": []
|
||||
},
|
||||
{
|
||||
"id": "0j1k2l3m-4n5o-6p7q-8r9s-0t1u2v3w4x5y",
|
||||
"userId": "",
|
||||
"listingsCategory": "commercialProperty",
|
||||
"title": "Established Bar and Live Music Venue in Austin",
|
||||
"state": "TX",
|
||||
"hasImages": true,
|
||||
"price": 1800000,
|
||||
"city": "Austin",
|
||||
"description": "<h2>Iconic Austin Bar and Music Hotspot</h2><p>Own a piece of Austin's legendary live music scene with this well-known bar and music venue. Located in the heart of the city's entertainment district, this business has a loyal following and a reputation for showcasing top talent.</p><p>Property and business features:</p><ul><li>6,000 sq ft building with a spacious stage and multiple bars</li><li>Fully equipped kitchen and outdoor patio area</li><li>Established relationships with booking agents and promoters</li><li>Consistent revenue and growth potential in Austin's thriving nightlife scene</li></ul><p>Take center stage in Austin's vibrant music community with this turnkey business opportunity.</p>",
|
||||
"type": "100",
|
||||
"imageOrder": []
|
||||
},
|
||||
{
|
||||
"id": "1k2l3m4n-5o6p-7q8r-9s0t-1u2v3w4x5y6z",
|
||||
"userId": "",
|
||||
"listingsCategory": "commercialProperty",
|
||||
"title": "Private Airport and Aviation Community",
|
||||
"state": "TX",
|
||||
"hasImages": true,
|
||||
"price": 15000000,
|
||||
"city": "Fredericksburg",
|
||||
"description": "<p>Rare opportunity to acquire a private airport and aviation community nestled in the scenic Texas Hill Country. This unique property features a well-maintained runway, hangar facilities, and a gated residential airpark.</p><h3>Property Highlights:</h3><ul><li>4,500' x 75' paved and lighted runway</li><li>50 acres of land with room for expansion</li><li>20 aircraft hangars and a private fuel farm</li><li>Gated community with 15 aviation-themed homes</li></ul><p>Elevate your investment portfolio with this one-of-a-kind aviation property in the heart of Texas.</p>",
|
||||
"type": "101",
|
||||
"imageOrder": []
|
||||
},
|
||||
{
|
||||
"id": "0t1u2v3w-4x5y-6z7a-8b9c-0d1e2f3g4h5i",
|
||||
"userId": "",
|
||||
"listingsCategory": "commercialProperty",
|
||||
"title": "Scenic Hill Country RV Resort",
|
||||
"state": "TX",
|
||||
"hasImages": true,
|
||||
"price": 6500000,
|
||||
"city": "New Braunfels",
|
||||
"description": "<h2>Premium RV Resort in the Heart of Texas Hill Country</h2><p>Discover the beauty and tranquility of the Texas Hill Country at this top-rated RV resort in New Braunfels. Nestled along the banks of the Guadalupe River, this immaculately maintained property offers guests a perfect blend of natural beauty, modern amenities, and endless recreational opportunities.</p><p>Resort features and highlights:</p><ul><li>150 spacious RV sites with full hookups and concrete pads</li><li>Sparkling swimming pool, hot tub, and sun deck area</li><li>Private river access with designated swimming and fishing spots</li><li>Clubhouse with game room, fitness center, and meeting spaces</li><li>Close proximity to popular attractions like Schlitterbahn Water Park and Gruene Historic District</li></ul><p>This profitable RV resort presents a unique investment opportunity in one of Texas' most sought-after vacation destinations. With strong occupancy rates, a loyal customer base, and multiple revenue streams, this turn-key property is poised for continued growth and success.</p><p>Capitalize on the booming Texas tourism market with this exceptional RV resort in the picturesque Hill Country region.</p>",
|
||||
"type": "101",
|
||||
"imageOrder": []
|
||||
},
|
||||
{
|
||||
"id": "3m4n5o6p-7q8r-9s0t-1u2v-3w4x5y6z7a8b",
|
||||
"userId": "",
|
||||
"listingsCategory": "commercialProperty",
|
||||
"title": "Turnkey Industrial Manufacturing Facility",
|
||||
"state": "TX",
|
||||
"hasImages": true,
|
||||
"price": 9750000,
|
||||
"city": "Fort Worth",
|
||||
"description": "<p>Fully equipped industrial manufacturing facility located in Fort Worth's thriving Alliance commercial district. This modern property offers a turnkey solution for businesses seeking to expand or relocate their operations to the Dallas-Fort Worth Metroplex.</p><h3>Property Highlights:</h3><ul><li>150,000 sq ft of manufacturing and warehouse space</li><li>State-of-the-art production equipment and utility systems</li><li>Abundant power, water, and natural gas capacity</li><li>Easy access to I-35W, I-820, and Alliance Airport</li></ul><p>Unlock your company's growth potential with this move-in-ready manufacturing facility in one of the nation's top industrial markets.</p>",
|
||||
"type": "102",
|
||||
"imageOrder": []
|
||||
},
|
||||
{
|
||||
"id": "6p7q8r9s-0t1u-2v3w-4x5y-6z7a8b9c0d1e",
|
||||
"userId": "",
|
||||
"listingsCategory": "commercialProperty",
|
||||
"title": "Full-Service Auto Dealership and Service Center",
|
||||
"state": "TX",
|
||||
"hasImages": true,
|
||||
"price": 5500000,
|
||||
"city": "San Marcos",
|
||||
"description": "<p>Well-established, full-service auto dealership and service center located in the rapidly growing city of San Marcos. This successful business benefits from a prime location along the heavily trafficked I-35 corridor between Austin and San Antonio.</p><h3>Property and Business Highlights:</h3><ul><li>5-acre site with modern showroom and service facilities</li><li>Franchise rights for a major domestic auto brand</li><li>Loyal customer base and strong revenue growth</li><li>Highly trained sales and service staff in place</li></ul><p>Take the wheel of this profitable auto dealership in one of Texas' fastest-growing markets.</p>",
|
||||
"type": "100",
|
||||
"imageOrder": []
|
||||
},
|
||||
{
|
||||
"id": "7q8r9s0t-1u2v-3w4x-5y6z-7a8b9c0d1e2f",
|
||||
"userId": "",
|
||||
"listingsCategory": "commercialProperty",
|
||||
"title": "Multi-Tenant Medical Office Complex",
|
||||
"state": "TX",
|
||||
"hasImages": true,
|
||||
"price": 8750000,
|
||||
"city": "Plano",
|
||||
"description": "<p>Newly constructed, multi-tenant medical office complex in the heart of Plano's booming Legacy West district. This state-of-the-art facility offers a prime location and modern amenities, making it an attractive choice for a wide range of healthcare providers.</p><h3>Property Features and Highlights:</h3><ul><li>80,000 sq ft of customizable medical office space</li><li>Abundant parking and easy access from Dallas North Tollway</li><li>On-site surgical center and diagnostic imaging suites</li><li>Close proximity to major hospitals and residential areas</li></ul><p>Capitalize on the growing demand for high-quality medical office space in one of the Dallas area's most affluent and dynamic communities.</p>",
|
||||
"type": "103",
|
||||
"imageOrder": []
|
||||
},
|
||||
{
|
||||
"id": "8r9s0t1u-2v3w-4x5y-6z7a-8b9c0d1e2f3g",
|
||||
"userId": "",
|
||||
"listingsCategory": "commercialProperty",
|
||||
"title": "Upscale Boutique Motel in Trendy East Austin",
|
||||
"state": "TX",
|
||||
"hasImages": true,
|
||||
"price": 4200000,
|
||||
"city": "Austin",
|
||||
"description": "<h2>Chic Motel in the Heart of Austin's Creative Hub</h2><p>Unique opportunity to acquire a beautifully renovated, upscale boutique motel in Austin's vibrant East Side neighborhood. This trendy property offers a perfect blend of retro charm and modern amenities, attracting a hip, creative clientele.</p><p>Property features and highlights:</p><ul><li>25 stylishly appointed guest rooms with premium finishes</li><li>Instagrammable pool area and cozy outdoor lounge spaces</li><li>Artisanal coffee bar and curated retail shop</li><li>Walking distance to popular restaurants, bars, and music venues</li></ul><p>Tap into Austin's booming tourism market with this one-of-a-kind boutique motel in the city's trendiest neighborhood.</p>",
|
||||
"type": "100",
|
||||
"imageOrder": []
|
||||
},
|
||||
{
|
||||
"id": "9s0t1u2v-3w4x-5y6z-7a8b-9c0d1e2f3g4h",
|
||||
"userId": "",
|
||||
"listingsCategory": "commercialProperty",
|
||||
"title": "Warehouse and Showroom Space in Houston's Design District",
|
||||
"state": "TX",
|
||||
"hasImages": true,
|
||||
"price": 3750000,
|
||||
"city": "Houston",
|
||||
"description": "<p>Expansive warehouse and showroom space located in the heart of Houston's thriving Design District. This versatile property offers a prime location and flexible layout, making it ideal for a variety of wholesale, distribution, and creative business uses.</p><h3>Property Highlights:</h3><ul><li>30,000 sq ft of warehouse and showroom space</li><li>High ceilings, ample natural light, and modern finishes</li><li>Loading docks and easy access to I-10 and I-45</li><li>Surrounded by premier design showrooms and art galleries</li></ul><p>Showcase your business in one of Houston's most creative and dynamic commercial districts.</p>",
|
||||
"type": "102",
|
||||
"imageOrder": []
|
||||
}
|
||||
]
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import fs from 'fs-extra';
|
||||
|
||||
(async () => {
|
||||
const listings = await fs.readJson('./users.json');
|
||||
//listings.forEach(element => {
|
||||
for (const listing of listings) {
|
||||
const response = await fetch('http://localhost:3000/bizmatch/user', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(listing),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// import puppeteer, { Browser, ElementHandle, Page } from 'puppeteer-core';
|
||||
import puppeteer, { Browser, ElementHandle, Page } from 'puppeteer';
|
||||
import { BusinessListing } from "../common-models/src/main.model"
|
||||
|
||||
import currency from 'currency.js';
|
||||
import fs from 'fs-extra'
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ async function getParentElementText(elementHandle: ElementHandle<Element> | null
|
|||
return textContent?(textContent.length<2?textContent.join():textContent):null
|
||||
}
|
||||
|
||||
async function extractListingData(page: Page): Promise<BusinessListing | null> {
|
||||
async function extractListingData(page: Page): Promise<any | null> {
|
||||
const labels = {
|
||||
summaryLabel: 'Summary',
|
||||
descriptionLabel: 'Description',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
|
|
@ -11,6 +10,9 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/parser": "^7.24.4",
|
||||
"@babel/traverse": "^7.24.1",
|
||||
"commander": "^12.0.0",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ const { Pool } = pkg;
|
|||
import fsextra from 'fs-extra';
|
||||
const { fstat, readFileSync, writeJsonSync } = fsextra;
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { CommercialPropertyListing, User } from '../common-models/src/main.model';
|
||||
|
||||
// PostgreSQL Verbindungskonfiguration
|
||||
const pool = new Pool({
|
||||
user: 'bizmatch',
|
||||
|
|
@ -70,7 +70,7 @@ async function importBusinesses() {
|
|||
async function importUser() {
|
||||
const filePath = './data/broker.json'
|
||||
const data: string = readFileSync(filePath, 'utf8');
|
||||
const jsonData: User[] = JSON.parse(data); // Erwartet ein Array von Objekten
|
||||
const jsonData: any[] = JSON.parse(data); // Erwartet ein Array von Objekten
|
||||
await pool.query('drop table if exists users');
|
||||
await pool.query(`CREATE TABLE users (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
|
@ -96,7 +96,7 @@ async function importUser() {
|
|||
async function importCommercials() {
|
||||
const filePath = './data/commercials.json'
|
||||
const data: string = readFileSync(filePath, 'utf8');
|
||||
const jsonData: CommercialPropertyListing[]|any = JSON.parse(data); // Erwartet ein Array von Objekten
|
||||
const jsonData: any[]|any = JSON.parse(data); // Erwartet ein Array von Objekten
|
||||
await pool.query('drop table if exists commercials');
|
||||
await pool.query(`CREATE TABLE commercials (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
|
|
|||
|
|
@ -1,80 +1,81 @@
|
|||
import { pgTable, timestamp, integer, jsonb, uuid } from 'drizzle-orm/pg-core';
|
||||
import { InferModel } from 'drizzle-orm';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { Pool } from 'pg';
|
||||
// import { pgTable, timestamp, integer, jsonb, uuid } from 'drizzle-orm/pg-core';
|
||||
// import { InferModel } from 'drizzle-orm';
|
||||
// import { sql } from 'drizzle-orm';
|
||||
// import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
// import { Pool } from 'pg';
|
||||
|
||||
// Definiere den benutzerdefinierten Typ für das JSON-Objekt
|
||||
type BusinessData = {
|
||||
id?: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
email: string;
|
||||
phoneNumber: string;
|
||||
companyLocation: string;
|
||||
hasCompanyLogo: boolean;
|
||||
hasProfile: boolean;
|
||||
};
|
||||
// // Definiere den benutzerdefinierten Typ für das JSON-Objekt
|
||||
// type BusinessData = {
|
||||
// id?: string;
|
||||
// firstname: string;
|
||||
// lastname: string;
|
||||
// email: string;
|
||||
// phoneNumber: string;
|
||||
// companyLocation: string;
|
||||
// hasCompanyLogo: boolean;
|
||||
// hasProfile: boolean;
|
||||
// };
|
||||
|
||||
// Definiere die Tabelle "businesses"
|
||||
const businesses = pgTable('businesses', {
|
||||
id: uuid('id').primaryKey(),
|
||||
created: timestamp('created'),
|
||||
updated: timestamp('updated'),
|
||||
visits: integer('visits'),
|
||||
lastVisit: timestamp('last_visit'),
|
||||
data: jsonb('data'),
|
||||
});
|
||||
// // Definiere die Tabelle "businesses"
|
||||
// const businesses = pgTable('businesses', {
|
||||
// id: uuid('id').primaryKey(),
|
||||
// created: timestamp('created'),
|
||||
// updated: timestamp('updated'),
|
||||
// visits: integer('visits'),
|
||||
// lastVisit: timestamp('last_visit'),
|
||||
// data: jsonb('data'),
|
||||
// });
|
||||
|
||||
// Definiere den Typ für das Modell
|
||||
type Business = InferModel<typeof businesses, 'select'>;
|
||||
// // Definiere den Typ für das Modell
|
||||
// type Business = InferModel<typeof businesses, 'select'>;
|
||||
|
||||
// Erstelle eine Verbindung zur Datenbank
|
||||
const pool = new Pool({
|
||||
// Konfiguriere die Verbindungsoptionen
|
||||
});
|
||||
// // Erstelle eine Verbindung zur Datenbank
|
||||
// const pool = new Pool({
|
||||
// // Konfiguriere die Verbindungsoptionen
|
||||
// });
|
||||
|
||||
const db = drizzle(pool);
|
||||
// const db = drizzle(pool);
|
||||
|
||||
// Beispiel für das Einfügen eines neuen Datensatzes
|
||||
const insertBusiness = async () => {
|
||||
const businessData: BusinessData = {
|
||||
firstname: 'Robert',
|
||||
lastname: 'Jackson',
|
||||
email: 'robert.jackson@texasbizbrokers.com',
|
||||
phoneNumber: '(214) 555-7890',
|
||||
companyLocation: 'Dallas - TX',
|
||||
hasCompanyLogo: true,
|
||||
hasProfile: true,
|
||||
};
|
||||
// // Beispiel für das Einfügen eines neuen Datensatzes
|
||||
// const insertBusiness = async () => {
|
||||
// const businessData: BusinessData = {
|
||||
// firstname: 'Robert',
|
||||
// lastname: 'Jackson',
|
||||
// email: 'robert.jackson@texasbizbrokers.com',
|
||||
// phoneNumber: '(214) 555-7890',
|
||||
// companyLocation: 'Dallas - TX',
|
||||
// hasCompanyLogo: true,
|
||||
// hasProfile: true,
|
||||
// };
|
||||
|
||||
const [insertedBusiness] = await db
|
||||
.with({
|
||||
new_business: sql<{ generated_id: string; created: Date; updated: Date; visits: number; last_visit: Date; data: BusinessData }>`(${(qb) => {
|
||||
return qb
|
||||
.select({
|
||||
generated_id: sql`uuid_generate_v4()`,
|
||||
created: sql`NOW()`,
|
||||
updated: sql`NOW()`,
|
||||
visits: sql`0`,
|
||||
last_visit: sql`NOW()`,
|
||||
data: sql`jsonb_set(${JSON.stringify(businessData)}::jsonb, '{id}', to_jsonb(uuid_generate_v4()))`,
|
||||
});
|
||||
}})`
|
||||
})
|
||||
.insert(businesses)
|
||||
.values((eb) => ({
|
||||
id: eb.generated_id,
|
||||
created: eb.created,
|
||||
updated: eb.updated,
|
||||
visits: eb.visits,
|
||||
lastVisit: eb.last_visit,
|
||||
data: sql`jsonb_set(${eb.data}::jsonb, '{id}', to_jsonb(${eb.generated_id}))`,
|
||||
}))
|
||||
.returning({ generatedId: businesses.id, jsonData: businesses.data });
|
||||
// const [insertedBusiness] = await db
|
||||
// .$with('sq')
|
||||
// .as({
|
||||
// new_business: sql<{ generated_id: string; created: Date; updated: Date; visits: number; last_visit: Date; data: BusinessData }>`(${(qb) => {
|
||||
// return qb
|
||||
// .select({
|
||||
// generated_id: sql`uuid_generate_v4()`,
|
||||
// created: sql`NOW()`,
|
||||
// updated: sql`NOW()`,
|
||||
// visits: sql`0`,
|
||||
// last_visit: sql`NOW()`,
|
||||
// data: sql`jsonb_set(${JSON.stringify(businessData)}::jsonb, '{id}', to_jsonb(uuid_generate_v4()))`,
|
||||
// });
|
||||
// }})`
|
||||
// })
|
||||
// .insert(businesses)
|
||||
// .values((eb) => ({
|
||||
// id: eb.generated_id,
|
||||
// created: eb.created,
|
||||
// updated: eb.updated,
|
||||
// visits: eb.visits,
|
||||
// lastVisit: eb.last_visit,
|
||||
// data: sql`jsonb_set(${eb.data}::jsonb, '{id}', to_jsonb(${eb.generated_id}))`,
|
||||
// }))
|
||||
// .returning({ generatedId: businesses.id, jsonData: businesses.data });
|
||||
|
||||
console.log('Generated ID:', insertedBusiness.generatedId);
|
||||
console.log('JSON Data:', insertedBusiness.jsonData);
|
||||
};
|
||||
// console.log('Generated ID:', insertedBusiness.generatedId);
|
||||
// console.log('JSON Data:', insertedBusiness.jsonData);
|
||||
// };
|
||||
|
||||
insertBusiness();
|
||||
// insertBusiness();
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
import { integer, serial, text, pgTable, timestamp, jsonb, varchar, char, numeric, boolean, uuid, real, doublePrecision } from 'drizzle-orm/pg-core';
|
||||
import { InferInsertModel, InferModel, InferModelFromColumns, InferSelectModel, relations, sql } from 'drizzle-orm';
|
||||
|
||||
export const PG_CONNECTION = 'PG_CONNECTION';
|
||||
|
||||
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(),
|
||||
});
|
||||
|
||||
export const businesses = pgTable('businesses', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('userId').references(()=>users.id),
|
||||
type: varchar('type', { length: 255 }),
|
||||
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: varchar('type', { length: 255 }),
|
||||
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(),
|
||||
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:30}).array(),
|
||||
imagePath:varchar('imagePath',{length:30}).array(),
|
||||
created: timestamp('created'),
|
||||
updated: timestamp('updated'),
|
||||
visits: integer('visits'),
|
||||
lastVisit: timestamp('lastVisit'),
|
||||
});
|
||||
|
||||
|
|
@ -25,9 +25,9 @@
|
|||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "ESNext", /* Specify what module code is generated. */
|
||||
// "module": "Node16", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "moduleResolution": "Node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import yargs from 'yargs'
|
||||
import fs from 'fs-extra';
|
||||
import { hideBin } from 'yargs/helpers'
|
||||
import { BusinessListing } from "../common-models/src/main.model"
|
||||
|
||||
//const argv = yargs(hideBin(process.argv)).argv
|
||||
|
||||
// if (!argv.userId){
|
||||
|
|
@ -19,7 +19,7 @@ import { BusinessListing } from "../common-models/src/main.model"
|
|||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
const listings:Array<BusinessListing> = await response.json();
|
||||
const listings:Array<any> = await response.json();
|
||||
for (const listing of listings) {
|
||||
const option = selectOptions.locations.find(l=>l.name.toLowerCase()===listing.state.toLowerCase());
|
||||
if (option){
|
||||
|
|
|
|||
Loading…
Reference in New Issue