Image Upload, spinner aktivirt, listings & details überarbeitet
This commit is contained in:
parent
2b27ab8ba5
commit
fd91adda57
|
|
@ -42,6 +42,7 @@
|
||||||
"redis-om": "^0.4.3",
|
"redis-om": "^0.4.3",
|
||||||
"reflect-metadata": "^0.2.0",
|
"reflect-metadata": "^0.2.0",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
|
"sharp": "^0.33.2",
|
||||||
"urlcat": "^3.1.0",
|
"urlcat": "^3.1.0",
|
||||||
"winston": "^3.11.0"
|
"winston": "^3.11.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import { Controller, Param, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
|
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
|
||||||
import { FileService } from '../file/file.service.js';
|
|
||||||
|
|
||||||
@Controller('account')
|
|
||||||
export class AccountController {
|
|
||||||
constructor(private fileService:FileService){}
|
|
||||||
|
|
||||||
@Post('uploadProfile/:id')
|
|
||||||
@UseInterceptors(FileInterceptor('file'),)
|
|
||||||
uploadProfile(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
|
|
||||||
this.fileService.storeProfilePicture(file,id);
|
|
||||||
}
|
|
||||||
@Post('uploadCompanyLogo/:id')
|
|
||||||
@UseInterceptors(FileInterceptor('file'),)
|
|
||||||
uploadCompanyLogo(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
|
|
||||||
this.fileService.storeCompanyLogo(file,id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { AccountController } from './account.controller.js';
|
|
||||||
import { AccountService } from './account.service.js';
|
|
||||||
import { FileService } from '../file/file.service.js';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
controllers: [AccountController],
|
|
||||||
providers: [AccountService,FileService]
|
|
||||||
})
|
|
||||||
export class AccountModule {}
|
|
||||||
|
|
@ -10,8 +10,6 @@ import { SelectOptionsService } from './select-options/select-options.service.js
|
||||||
import { SubscriptionsController } from './subscriptions/subscriptions.controller.js';
|
import { SubscriptionsController } from './subscriptions/subscriptions.controller.js';
|
||||||
import { RedisModule } from './redis/redis.module.js';
|
import { RedisModule } from './redis/redis.module.js';
|
||||||
import { ListingsService } from './listings/listings.service.js';
|
import { ListingsService } from './listings/listings.service.js';
|
||||||
import { AccountController } from './account/account.controller.js';
|
|
||||||
import { AccountService } from './account/account.service.js';
|
|
||||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||||
import path, { join } from 'path';
|
import path, { join } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
@ -22,9 +20,9 @@ import { AuthModule } from './auth/auth.module.js';
|
||||||
import { GeoModule } from './geo/geo.module.js';
|
import { GeoModule } from './geo/geo.module.js';
|
||||||
import { UserModule } from './user/user.module.js';
|
import { UserModule } from './user/user.module.js';
|
||||||
import { ListingsModule } from './listings/listings.module.js';
|
import { ListingsModule } from './listings/listings.module.js';
|
||||||
import { AccountModule } from './account/account.module.js';
|
|
||||||
import { SelectOptionsModule } from './select-options/select-options.module.js';
|
import { SelectOptionsModule } from './select-options/select-options.module.js';
|
||||||
import { CommercialPropertyListingsController } from './listings/commercial-property-listings.controller.js';
|
import { CommercialPropertyListingsController } from './listings/commercial-property-listings.controller.js';
|
||||||
|
import { ImageModule } from './image/image.module.js';
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
|
@ -52,9 +50,9 @@ const __dirname = path.dirname(__filename);
|
||||||
GeoModule,
|
GeoModule,
|
||||||
UserModule,
|
UserModule,
|
||||||
ListingsModule,
|
ListingsModule,
|
||||||
AccountModule,
|
|
||||||
SelectOptionsModule,
|
SelectOptionsModule,
|
||||||
RedisModule
|
RedisModule,
|
||||||
|
ImageModule
|
||||||
],
|
],
|
||||||
controllers: [AppController, SubscriptionsController],
|
controllers: [AppController, SubscriptionsController],
|
||||||
providers: [AppService, FileService],
|
providers: [AppService, FileService],
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { fstat, readFileSync } from 'fs';
|
import { fstat, readFileSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { ImageProperty } from 'src/models/main.model.js';
|
import { ImageProperty } from '../models/main.model.js';
|
||||||
|
import sharp from 'sharp';
|
||||||
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
|
import { Logger } from 'winston';
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FileService {
|
export class FileService {
|
||||||
private subscriptions: any;
|
private subscriptions: any;
|
||||||
constructor() {
|
constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
|
||||||
this.loadSubscriptions();
|
this.loadSubscriptions();
|
||||||
fs.ensureDirSync(`./pictures`);
|
fs.ensureDirSync(`./pictures`);
|
||||||
fs.ensureDirSync(`./pictures/profile`);
|
fs.ensureDirSync(`./pictures/profile`);
|
||||||
|
|
@ -31,10 +33,18 @@ export class FileService {
|
||||||
const suffix = file.mimetype.includes('png') ? 'png' : 'jpg'
|
const suffix = file.mimetype.includes('png') ? 'png' : 'jpg'
|
||||||
await fs.outputFile(`./pictures/profile/${userId}`, file.buffer);
|
await fs.outputFile(`./pictures/profile/${userId}`, file.buffer);
|
||||||
}
|
}
|
||||||
|
hasProfile(userId: string){
|
||||||
|
return fs.existsSync(`./pictures/profile/${userId}`)
|
||||||
|
}
|
||||||
|
|
||||||
async storeCompanyLogo(file: Express.Multer.File, userId: string) {
|
async storeCompanyLogo(file: Express.Multer.File, userId: string) {
|
||||||
const suffix = file.mimetype.includes('png') ? 'png' : 'jpg'
|
const suffix = file.mimetype.includes('png') ? 'png' : 'jpg'
|
||||||
await fs.outputFile(`./pictures/logo/${userId}`, file.buffer);
|
await fs.outputFile(`./pictures/logo/${userId}`, file.buffer);
|
||||||
}
|
}
|
||||||
|
hasCompanyLogo(userId: string){
|
||||||
|
return fs.existsSync(`./pictures/logo/${userId}`)
|
||||||
|
}
|
||||||
|
|
||||||
async getPropertyImages(listingId: string): Promise<ImageProperty[]> {
|
async getPropertyImages(listingId: string): Promise<ImageProperty[]> {
|
||||||
const result: ImageProperty[] = []
|
const result: ImageProperty[] = []
|
||||||
const directory = `./pictures/property/${listingId}`
|
const directory = `./pictures/property/${listingId}`
|
||||||
|
|
@ -48,14 +58,24 @@ export class FileService {
|
||||||
} else {
|
} else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
async hasPropertyImages(listingId: string): Promise<boolean> {
|
||||||
|
const result: ImageProperty[] = []
|
||||||
|
const directory = `./pictures/property/${listingId}`
|
||||||
|
if (fs.existsSync(directory)) {
|
||||||
|
const files = await fs.readdir(directory);
|
||||||
|
return files.length>0
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async storePropertyPicture(file: Express.Multer.File, listingId: string) {
|
async storePropertyPicture(file: Express.Multer.File, listingId: string) {
|
||||||
const suffix = file.mimetype.includes('png') ? 'png' : 'jpg'
|
const suffix = file.mimetype.includes('png') ? 'png' : 'jpg'
|
||||||
const directory = `./pictures/property/${listingId}`
|
const directory = `./pictures/property/${listingId}`
|
||||||
fs.ensureDirSync(`${directory}`);
|
fs.ensureDirSync(`${directory}`);
|
||||||
const imageName = await this.getNextImageName(directory);
|
const imageName = await this.getNextImageName(directory);
|
||||||
await fs.outputFile(`${directory}/${imageName}`,file.buffer);
|
//await fs.outputFile(`${directory}/${imageName}`, file.buffer);
|
||||||
|
await this.resizeImageToAVIF(file.buffer,150 * 1024,imageName,directory);
|
||||||
}
|
}
|
||||||
async getNextImageName(directory) {
|
async getNextImageName(directory) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -72,4 +92,24 @@ export class FileService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async resizeImageToAVIF(buffer: Buffer, maxSize: number,imageName:string,directory:string) {
|
||||||
|
let quality = 50; // AVIF kann mit niedrigeren Qualitätsstufen gute Ergebnisse erzielen
|
||||||
|
let output;
|
||||||
|
let start = Date.now();
|
||||||
|
do {
|
||||||
|
output = await sharp(buffer)
|
||||||
|
.resize({ width: 1500 })
|
||||||
|
.avif({ quality }) // Verwende AVIF
|
||||||
|
//.webp({ quality }) // Verwende Webp
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
if (output.byteLength > maxSize) {
|
||||||
|
quality -= 5; // Justiere Qualität in feineren Schritten
|
||||||
|
}
|
||||||
|
} while (output.byteLength > maxSize && quality > 0);
|
||||||
|
let timeTaken = Date.now() - start;
|
||||||
|
this.logger.info(`Quality: ${quality} - Time: ${timeTaken} milliseconds`)
|
||||||
|
await sharp(output).toFile(`${directory}/${imageName}.avif`); // Ersetze Dateierweiterung
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { Body, Controller, Delete, Get, Inject, Param, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
|
||||||
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
|
import { Logger } from 'winston';
|
||||||
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
|
import { FileService } from '../file/file.service.js';
|
||||||
|
|
||||||
|
@Controller('image')
|
||||||
|
export class ImageController {
|
||||||
|
|
||||||
|
constructor(private fileService:FileService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('uploadPropertyPicture/:id')
|
||||||
|
@UseInterceptors(FileInterceptor('file'),)
|
||||||
|
async uploadFile(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
|
||||||
|
await this.fileService.storePropertyPicture(file,id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
async getPropertyImagesById(@Param('id') id:string): Promise<any> {
|
||||||
|
return await this.fileService.getPropertyImages(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('uploadProfile/:id')
|
||||||
|
@UseInterceptors(FileInterceptor('file'),)
|
||||||
|
uploadProfile(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
|
||||||
|
this.fileService.storeProfilePicture(file,id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('uploadCompanyLogo/:id')
|
||||||
|
@UseInterceptors(FileInterceptor('file'),)
|
||||||
|
uploadCompanyLogo(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
|
||||||
|
this.fileService.storeCompanyLogo(file,id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ImageController } from './image.controller.js';
|
||||||
|
import { ImageService } from './image.service.js';
|
||||||
|
import { FileService } from '../file/file.service.js';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [ImageController],
|
||||||
|
providers: [ImageService,FileService]
|
||||||
|
})
|
||||||
|
export class ImageModule {}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccountService {}
|
export class ImageService {}
|
||||||
|
|
@ -11,10 +11,6 @@ export class CommercialPropertyListingsController {
|
||||||
constructor(private readonly listingsService:ListingsService,private fileService:FileService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
|
constructor(private readonly listingsService:ListingsService,private fileService:FileService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
|
||||||
findAll(): any {
|
|
||||||
return this.listingsService.getAllCommercialListings();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
findById(@Param('id') id:string): any {
|
findById(@Param('id') id:string): any {
|
||||||
|
|
@ -43,14 +39,5 @@ export class CommercialPropertyListingsController {
|
||||||
this.listingsService.deleteCommercialPropertyListing(id)
|
this.listingsService.deleteCommercialPropertyListing(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('uploadPropertyPicture/:id')
|
|
||||||
@UseInterceptors(FileInterceptor('file'),)
|
|
||||||
uploadFile(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
|
|
||||||
this.fileService.storePropertyPicture(file,id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('images/:id')
|
|
||||||
getPropertyImagesById(@Param('id') id:string): any {
|
|
||||||
return this.fileService.getPropertyImages(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,6 @@ export class ListingsService {
|
||||||
async getAllCommercialListings(start?: number, end?: number) {
|
async getAllCommercialListings(start?: number, end?: number) {
|
||||||
return await this.commercialPropertyListingRepository.search().return.all()
|
return await this.commercialPropertyListingRepository.search().return.all()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async findBusinessListings(criteria:ListingCriteria): Promise<any> {
|
async findBusinessListings(criteria:ListingCriteria): Promise<any> {
|
||||||
let listings = await this.getAllBusinessListings();
|
let listings = await this.getAllBusinessListings();
|
||||||
return this.find(criteria,listings);
|
return this.find(criteria,listings);
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ export class UserController {
|
||||||
return this.userService.saveUser(user);
|
return this.userService.saveUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Put()
|
@Post('search')
|
||||||
// update(@Body() user: any):Promise<UserEntity>{
|
find(@Body() criteria: any): any {
|
||||||
// this.logger.info(`update User`);
|
return this.userService.findUser(criteria);
|
||||||
// return this.userService.saveUser(user);
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ import { Module } from '@nestjs/common';
|
||||||
import { UserController } from './user.controller.js';
|
import { UserController } from './user.controller.js';
|
||||||
import { UserService } from './user.service.js';
|
import { UserService } from './user.service.js';
|
||||||
import { RedisModule } from '../redis/redis.module.js';
|
import { RedisModule } from '../redis/redis.module.js';
|
||||||
|
import { FileService } from '../file/file.service.js';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [RedisModule],
|
imports: [RedisModule],
|
||||||
controllers: [UserController],
|
controllers: [UserController],
|
||||||
providers: [UserService]
|
providers: [UserService,FileService]
|
||||||
})
|
})
|
||||||
export class UserModule {
|
export class UserModule {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { Entity, Repository, Schema } from 'redis-om';
|
||||||
import { ListingCriteria, User } from '../models/main.model.js';
|
import { ListingCriteria, User } from '../models/main.model.js';
|
||||||
import { REDIS_CLIENT } from '../redis/redis.module.js';
|
import { REDIS_CLIENT } from '../redis/redis.module.js';
|
||||||
import { UserEntity } from '../models/server.model.js';
|
import { UserEntity } from '../models/server.model.js';
|
||||||
|
import { FileService } from '../file/file.service.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
|
|
@ -24,12 +25,15 @@ export class UserService {
|
||||||
}, {
|
}, {
|
||||||
dataStructure: 'JSON'
|
dataStructure: 'JSON'
|
||||||
})
|
})
|
||||||
constructor(@Inject(REDIS_CLIENT) private readonly redis: any){
|
constructor(@Inject(REDIS_CLIENT) private readonly redis: any,private fileService:FileService){
|
||||||
this.userRepository = new Repository(this.userSchema, redis)
|
this.userRepository = new Repository(this.userSchema, redis)
|
||||||
this.userRepository.createIndex();
|
this.userRepository.createIndex();
|
||||||
}
|
}
|
||||||
async getUserById( id:string){
|
async getUserById( id:string){
|
||||||
return await this.userRepository.fetch(id);
|
const user = await this.userRepository.fetch(id) as UserEntity;
|
||||||
|
user.hasCompanyLogo=this.fileService.hasCompanyLogo(id);
|
||||||
|
user.hasProfile=this.fileService.hasProfile(id);
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
async saveUser(user:any):Promise<UserEntity>{
|
async saveUser(user:any):Promise<UserEntity>{
|
||||||
return await this.userRepository.save(user.id,user) as UserEntity
|
return await this.userRepository.save(user.id,user) as UserEntity
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,20 @@
|
||||||
<header></header>
|
<header></header>
|
||||||
}
|
}
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
@if (loadingService.isLoading$ | async) {
|
|
||||||
<div class="progress-spinner flex h-full align-items-center justify-content-center">
|
|
||||||
<p-progressSpinner></p-progressSpinner>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<footer></footer>
|
<footer></footer>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- @if (loadingService.isLoading$ | async) { -->
|
||||||
|
<!-- <div class="progress-spinner flex h-full align-items-center justify-content-center">
|
||||||
|
<div class="spinner-text">Please wait - we're processing your image...</div>
|
||||||
|
<p-progressSpinner></p-progressSpinner>
|
||||||
|
</div> -->
|
||||||
|
<!-- } -->
|
||||||
|
@if (loadingService.isLoading$ | async) {
|
||||||
|
<div class="spinner-overlay">
|
||||||
|
<div class="spinner-container">
|
||||||
|
<p-progressSpinner></p-progressSpinner>
|
||||||
|
<div class="spinner-text" *ngIf="loadingService.loadingText$ | async as loadingText">{{loadingText}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
@ -8,3 +8,57 @@
|
||||||
/* Optional: Padding für den Inhalt, um sicherzustellen, dass er nicht direkt am Footer klebt */
|
/* Optional: Padding für den Inhalt, um sicherzustellen, dass er nicht direkt am Footer klebt */
|
||||||
// padding-bottom: 20px;
|
// padding-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
.progress-spinner {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 999;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-spinner:before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
.spinner-text {
|
||||||
|
margin-top: 20px; /* Abstand zwischen Spinner und Text anpassen */
|
||||||
|
font-size: 20px; /* Schriftgröße nach Bedarf anpassen */
|
||||||
|
color: #FFF;
|
||||||
|
text-shadow: 0 0 8px rgba(255, 255, 255, 0.6); /* Hinzufügen eines leichten Glows */
|
||||||
|
font-weight: bold; /* Macht den Text fett */
|
||||||
|
}
|
||||||
|
.spinner-overlay {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: fixed; /* oder 'absolute', abhängig vom Kontext */
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
z-index: 1000; /* Stellt sicher, dass der Overlay über anderen Elementen liegt */
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
/* Keine Hintergrundfarbe hier, um Transparenz nur im Overlay zu haben */
|
||||||
|
}
|
||||||
|
|
||||||
|
// .spinner-text {
|
||||||
|
// margin-top: 10px; /* Abstand zwischen Spinner und Text anpassen */
|
||||||
|
// font-size: 16px; /* Schriftgröße nach Bedarf anpassen */
|
||||||
|
// color: #FFF; /* Schriftfarbe für bessere Lesbarkeit auf dunklem Hintergrund */
|
||||||
|
// }
|
||||||
|
|
@ -39,17 +39,6 @@ export class AppComponent {
|
||||||
// Hier haben Sie Zugriff auf den aktuellen Route-Pfad
|
// Hier haben Sie Zugriff auf den aktuellen Route-Pfad
|
||||||
this.actualRoute=currentRoute.snapshot.url[0].path
|
this.actualRoute=currentRoute.snapshot.url[0].path
|
||||||
});
|
});
|
||||||
|
|
||||||
// keycloakService.keycloakEvents$.subscribe({
|
|
||||||
// next(event) {
|
|
||||||
// if (event.type == KeycloakEventType.OnTokenExpired) {
|
|
||||||
// keycloakService.updateToken(20);
|
|
||||||
// }
|
|
||||||
// if (event.type == KeycloakEventType.OnActionUpdate) {
|
|
||||||
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
ngOnInit(){
|
ngOnInit(){
|
||||||
this.user = this.userService.getUser();
|
this.user = this.userService.getUser();
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@ import { provideRouter } from '@angular/router';
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
import { provideClientHydration } from '@angular/platform-browser';
|
import { provideClientHydration } from '@angular/platform-browser';
|
||||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||||
import { HTTP_INTERCEPTORS, provideHttpClient, withFetch, withInterceptorsFromDi } from '@angular/common/http';
|
import { HTTP_INTERCEPTORS, provideHttpClient, withFetch, withInterceptors, withInterceptorsFromDi } from '@angular/common/http';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { SelectOptionsService } from './services/select-options.service';
|
import { SelectOptionsService } from './services/select-options.service';
|
||||||
import { KeycloakService } from './services/keycloak.service';
|
import { KeycloakService } from './services/keycloak.service';
|
||||||
import { UserService } from './services/user.service';
|
import { UserService } from './services/user.service';
|
||||||
|
import { LoadingInterceptor } from './interceptors/loading.interceptor';
|
||||||
// provideClientHydration()
|
// provideClientHydration()
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
|
|
@ -26,6 +27,11 @@ export const appConfig: ApplicationConfig = {
|
||||||
multi: true,
|
multi: true,
|
||||||
deps: [SelectOptionsService],
|
deps: [SelectOptionsService],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide:HTTP_INTERCEPTORS,
|
||||||
|
useClass:LoadingInterceptor,
|
||||||
|
multi:true
|
||||||
|
},
|
||||||
provideRouter(routes),provideAnimations(),
|
provideRouter(routes),provideAnimations(),
|
||||||
// {provide: LOCALE_ID, useValue: 'en-US' }
|
// {provide: LOCALE_ID, useValue: 'en-US' }
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,39 @@
|
||||||
import { HttpInterceptorFn } from '@angular/common/http';
|
import { HttpEvent, HttpHandler, HttpInterceptor, HttpInterceptorFn, HttpRequest } from '@angular/common/http';
|
||||||
import { inject } from '@angular/core';
|
import { Injectable, inject } from '@angular/core';
|
||||||
import { tap } from 'rxjs';
|
import { Observable, tap } from 'rxjs';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import { LoadingService } from '../services/loading.service';
|
import { LoadingService } from '../services/loading.service';
|
||||||
|
|
||||||
export const loadingInterceptor: HttpInterceptorFn = (req, next) => {
|
// export const loadingInterceptor: HttpInterceptorFn = (req, next) => {
|
||||||
const loadingService = inject(LoadingService);
|
// const loadingService = inject(LoadingService);
|
||||||
|
|
||||||
|
// const requestId = `HTTP-${v4()}`;
|
||||||
|
|
||||||
|
// loadingService.startLoading(requestId);
|
||||||
|
|
||||||
|
// return next(req).pipe(
|
||||||
|
// tap({
|
||||||
|
// finalize: () => loadingService.stopLoading(requestId),
|
||||||
|
// error: () => loadingService.stopLoading(requestId),
|
||||||
|
// complete: () => loadingService.stopLoading(requestId),
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
@Injectable()
|
||||||
|
export class LoadingInterceptor implements HttpInterceptor {
|
||||||
|
constructor(private loadingService:LoadingService) { }
|
||||||
|
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||||
|
console.log("Intercepting Requests")
|
||||||
const requestId = `HTTP-${v4()}`;
|
const requestId = `HTTP-${v4()}`;
|
||||||
|
this.loadingService.startLoading(requestId,request.url);
|
||||||
|
|
||||||
loadingService.startLoading(requestId);
|
// return next.handle(request);
|
||||||
|
return next.handle(request).pipe(
|
||||||
return next(req).pipe(
|
|
||||||
tap({
|
tap({
|
||||||
finalize: () => loadingService.stopLoading(requestId),
|
finalize: () => this.loadingService.stopLoading(requestId), // Stoppt den Ladevorgang, wenn die Anfrage abgeschlossen ist
|
||||||
error: () => loadingService.stopLoading(requestId),
|
// Beachte, dass 'error' und 'complete' hier entfernt wurden, da 'finalize' in allen Fällen aufgerufen wird,
|
||||||
complete: () => loadingService.stopLoading(requestId),
|
// egal ob die Anfrage erfolgreich war, einen Fehler geworfen hat oder abgeschlossen wurde.
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,22 +7,11 @@
|
||||||
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="back()"></p-button>
|
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="back()"></p-button>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="text-500 mb-5">Egestas sed tempus urna et pharetra pharetra massa massa ultricies.</div> -->
|
<!-- <div class="text-500 mb-5">Egestas sed tempus urna et pharetra pharetra massa massa ultricies.</div> -->
|
||||||
|
@if (listing){
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<ul class="list-none p-0 m-0 border-top-1 border-300">
|
<ul class="list-none p-0 m-0 border-top-1 border-300">
|
||||||
<!-- @if (listing && (listing.listingsCategory==='business')){
|
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Summary</div>
|
|
||||||
<div class="w-full md:w-10">
|
|
||||||
@for (summary of listing.summary; track summary; let idx = $index; let last = $last) {
|
|
||||||
<div class="text-900">{{summary}}</div>
|
|
||||||
@if (!last) {
|
|
||||||
<br/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
} -->
|
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Description</div>
|
<div class="text-500 w-full md:w-2 font-medium">Description</div>
|
||||||
<div class="text-900 w-full md:w-10 line-height-3">{{listing?.description}}</div>
|
<div class="text-900 w-full md:w-10 line-height-3">{{listing?.description}}</div>
|
||||||
|
|
@ -63,47 +52,43 @@
|
||||||
<div class="text-900 w-full md:w-10">{{listing.brokerLicencing}}</div>
|
<div class="text-900 w-full md:w-10">{{listing.brokerLicencing}}</div>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
<!-- @if (listing && (listing.listingsCategory==='professionals_brokers')){
|
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Located in</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{selectOptions.getLocation(listing.location)}}</div>
|
|
||||||
</li>
|
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap ">
|
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Address</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{listing.address}}</div>
|
|
||||||
</li>
|
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
|
||||||
<div class="text-500 w-full md:w-2 font-medium">EMail</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{listing.email}}</div>
|
|
||||||
</li>
|
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap ">
|
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Website</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{listing.website}}</div>
|
|
||||||
</li>
|
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap ">
|
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Category</div>
|
|
||||||
<div class="text-900 w-full md:w-10">{{listing.category}}</div>
|
|
||||||
</li>
|
|
||||||
} -->
|
|
||||||
@if (listing && (listing.listingsCategory==='commercialProperty')){
|
@if (listing && (listing.listingsCategory==='commercialProperty')){
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
||||||
|
<div class="text-500 w-full md:w-2 font-medium">Property Category</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{selectOptions.getCommercialProperty(listing.type)}}</div>
|
||||||
|
</li>
|
||||||
|
<li class="flex align-items-center py-3 px-2 flex-wrap ">
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Located in</div>
|
<div class="text-500 w-full md:w-2 font-medium">Located in</div>
|
||||||
<div class="text-900 w-full md:w-10">{{selectOptions.getState(listing.state)}}</div>
|
<div class="text-900 w-full md:w-10">{{selectOptions.getState(listing.state)}}</div>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
||||||
|
<div class="text-500 w-full md:w-2 font-medium">City</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{listing.city}}</div>
|
||||||
|
</li>
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
<div class="text-500 w-full md:w-2 font-medium">EMail</div>
|
<div class="text-500 w-full md:w-2 font-medium">Zip Code</div>
|
||||||
<div class="text-900 w-full md:w-10">{{listing.email}}</div>
|
<div class="text-900 w-full md:w-10">{{listing.zipCode}}</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Website</div>
|
<div class="text-500 w-full md:w-2 font-medium">County</div>
|
||||||
<div class="text-900 w-full md:w-10">{{listing.website}}</div>
|
<div class="text-900 w-full md:w-10">{{listing.county}}</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Phone Number</div>
|
<div class="text-500 w-full md:w-2 font-medium">Asking Price:</div>
|
||||||
<div class="text-900 w-full md:w-10">{{listing.phoneNumber}}</div>
|
<div class="text-900 w-full md:w-10">{{listing.price | currency}}</div>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
<p-galleria [value]="propertyImages" [showIndicators]="true" [showThumbnails]="false" [responsiveOptions]="responsiveOptions" [containerStyle]="{ 'max-width': '640px' }" [numVisible]="5">
|
||||||
|
<ng-template pTemplate="item" let-item>
|
||||||
|
<img src="{{environment.apiBaseUrl}}/property/{{listing.id}}/{{item.name}}" style="width: 100%;" />
|
||||||
|
</ng-template>
|
||||||
|
<!-- <ng-template pTemplate="thumbnail" let-item>
|
||||||
|
<div class="grid grid-nogutter justify-content-center">
|
||||||
|
<img src="{{environment.apiBaseUrl}}/property/{{listing.id}}/{{item.name}}" />
|
||||||
|
</div>
|
||||||
|
</ng-template> -->
|
||||||
|
</p-galleria>
|
||||||
@if(listing && user && (user.id===listing?.userId || isAdmin())){
|
@if(listing && user && (user.id===listing?.userId || isAdmin())){
|
||||||
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/editListing',listing.id]"></button>
|
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/editListing',listing.id]"></button>
|
||||||
}
|
}
|
||||||
|
|
@ -141,6 +126,118 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
} @else {
|
||||||
|
<div class="surface-section px-6 py-5">
|
||||||
|
<div class="flex align-items-start flex-column lg:flex-row lg:justify-content-between">
|
||||||
|
<div class="flex align-items-start flex-column md:flex-row">
|
||||||
|
<img src="{{environment.apiBaseUrl}}/profile/{{user.id}}" class="mr-5 mb-3 lg:mb-0" style="width:90px;height:90px" />
|
||||||
|
<div>
|
||||||
|
<span class="text-900 font-medium text-3xl">{{user.firstname}} {{user.lastname}}</span>
|
||||||
|
<i class="pi pi-star text-2xl ml-4 text-yellow-500"></i>
|
||||||
|
<div class="flex align-items-center flex-wrap text-sm">
|
||||||
|
<div class="mr-5 mt-3">
|
||||||
|
<span class="font-medium text-500">Company</span>
|
||||||
|
<div class="text-700 mt-2">{{user.companyName}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mr-5 mt-3">
|
||||||
|
<span class="font-medium text-500">For Sale</span>
|
||||||
|
<div class="text-700 mt-2">12</div>
|
||||||
|
</div>
|
||||||
|
<div class="mr-5 mt-3">
|
||||||
|
<span class="font-medium text-500">Sold</span>
|
||||||
|
<div class="text-700 mt-2">8</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<span class="font-medium text-500">Logo</span>
|
||||||
|
<div class="mt-2"><img src="{{environment.apiBaseUrl}}/logo/{{user.id}}" class="mr-5 mb-3 lg:mb-0" style="width:100px;height:30px" /></div>
|
||||||
|
<!-- <div class="text-700 mt-2">130</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="mt-3 lg:mt-0">
|
||||||
|
<button pButton pRipple icon="pi pi-bookmark" class="p-button-rounded mr-2"></button>
|
||||||
|
<button pButton pRipple icon="pi pi-heart" class="p-button-rounded p-button-success mr-2"></button>
|
||||||
|
<button pButton pRipple icon="pi pi-list" class="p-button-rounded p-button-help"></button>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-6 py-5">
|
||||||
|
<div class="surface-card p-4 shadow-2 border-round">
|
||||||
|
<div class="font-medium text-3xl text-900 mb-3">Company Profile</div>
|
||||||
|
<div class="text-500 mb-5">{{user.companyOverview}}</div>
|
||||||
|
<ul class="list-none p-0 m-0 border-top-1 border-300">
|
||||||
|
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
||||||
|
<div class="text-500 w-full md:w-2 font-medium">Name</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{user.firstname}} {{user.lastname}}</div>
|
||||||
|
</li>
|
||||||
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
|
<div class="text-500 w-full md:w-2 font-medium">Description</div>
|
||||||
|
<div class="text-900 w-full md:w-10 line-height-3">{{user.description}}</div>
|
||||||
|
</li>
|
||||||
|
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
||||||
|
<div class="text-500 w-full md:w-2 font-medium">Services we offer</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{user.offeredServices}}</div>
|
||||||
|
</li>
|
||||||
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
|
<div class="text-500 w-full md:w-2 font-medium">Areas we serve</div>
|
||||||
|
<div class="text-900 w-full md:w-10">
|
||||||
|
@for (area of user.areasServed; track area) {
|
||||||
|
<p-tag styleClass="mr-2" [value]="area" [rounded]="true"></p-tag>
|
||||||
|
}
|
||||||
|
<!-- <p-tag styleClass="mr-2" severity="success" value="Javascript" [rounded]="true"></p-tag>
|
||||||
|
<p-tag styleClass="mr-2" severity="danger" value="Python" [rounded]="true"></p-tag>
|
||||||
|
<p-tag severity="warning" value="SQL" [rounded]="true"></p-tag> -->
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
||||||
|
<div class="text-500 w-full md:w-2 font-medium">My Listings For Sale</div>
|
||||||
|
<div class="text-900 w-full md:w-10">
|
||||||
|
<div class="grid mt-0 mr-0">
|
||||||
|
<div class="col-12 md:col-6">
|
||||||
|
<div class="p-3 border-1 surface-border border-round surface-card">
|
||||||
|
<div class="text-900 mb-2">
|
||||||
|
<i class="pi pi-github mr-2"></i>
|
||||||
|
<span class="font-medium">PrimeFaces</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-700">Ultimate UI Component Suite for JavaServer Faces</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 md:col-6">
|
||||||
|
<div class="p-3 border-1 surface-border border-round surface-card">
|
||||||
|
<div class="text-900 mb-2">
|
||||||
|
<i class="pi pi-github mr-2"></i>
|
||||||
|
<span class="font-medium">PrimeNG</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-700">The Most Complete Angular UI Component Library</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 md:col-6">
|
||||||
|
<div class="p-3 border-1 surface-border border-round surface-card">
|
||||||
|
<div class="text-900 mb-2">
|
||||||
|
<i class="pi pi-github mr-2"></i>
|
||||||
|
<span class="font-medium">PrimeReact</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-700">Advanced UI Components for ReactJS</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 md:col-6">
|
||||||
|
<div class="p-3 border-1 surface-border border-round surface-card">
|
||||||
|
<div class="text-900 mb-2">
|
||||||
|
<i class="pi pi-github mr-2"></i>
|
||||||
|
<span class="font-medium">PrimeVue</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-700">The Most Powerful Vue UI Component Library</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -18,27 +18,46 @@ import { ListingsService } from '../../services/listings.service';
|
||||||
import { UserService } from '../../services/user.service';
|
import { UserService } from '../../services/user.service';
|
||||||
import onChange from 'on-change';
|
import onChange from 'on-change';
|
||||||
import { createGenericObject, getCriteriaStateObject, getSessionStorageHandler } from '../../utils/utils';
|
import { createGenericObject, getCriteriaStateObject, getSessionStorageHandler } from '../../utils/utils';
|
||||||
import { ListingCriteria, ListingType, MailInfo, User } from '../../../../../common-models/src/main.model';
|
import { ImageProperty, ListingCriteria, ListingType, MailInfo, User } from '../../../../../common-models/src/main.model';
|
||||||
import { MailService } from '../../services/mail.service';
|
import { MailService } from '../../services/mail.service';
|
||||||
import { MessageService } from 'primeng/api';
|
import { MessageService } from 'primeng/api';
|
||||||
import { SharedModule } from '../../shared/shared/shared.module';
|
import { SharedModule } from '../../shared/shared/shared.module';
|
||||||
|
import { GalleriaModule } from 'primeng/galleria';
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-details',
|
selector: 'app-details',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SharedModule],
|
imports: [SharedModule,GalleriaModule],
|
||||||
providers:[MessageService],
|
providers:[MessageService],
|
||||||
templateUrl: './details.component.html',
|
templateUrl: './details.component.html',
|
||||||
styleUrl: './details.component.scss'
|
styleUrl: './details.component.scss'
|
||||||
})
|
})
|
||||||
export class DetailsComponent {
|
export class DetailsComponent {
|
||||||
// listings: Array<BusinessListing>;
|
// listings: Array<BusinessListing>;
|
||||||
|
responsiveOptions = [
|
||||||
|
{
|
||||||
|
breakpoint: '1199px',
|
||||||
|
numVisible: 1,
|
||||||
|
numScroll: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '991px',
|
||||||
|
numVisible: 2,
|
||||||
|
numScroll: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '767px',
|
||||||
|
numVisible: 1,
|
||||||
|
numScroll: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
||||||
listing: ListingType;
|
listing: ListingType;
|
||||||
user:User;
|
user:User;
|
||||||
criteria:ListingCriteria
|
criteria:ListingCriteria
|
||||||
mailinfo: MailInfo;
|
mailinfo: MailInfo;
|
||||||
|
propertyImages:ImageProperty[]=[]
|
||||||
|
environment=environment;
|
||||||
constructor(private activatedRoute: ActivatedRoute,
|
constructor(private activatedRoute: ActivatedRoute,
|
||||||
private listingsService:ListingsService,
|
private listingsService:ListingsService,
|
||||||
private router:Router,
|
private router:Router,
|
||||||
|
|
@ -55,6 +74,7 @@ export class DetailsComponent {
|
||||||
this.user=user
|
this.user=user
|
||||||
});
|
});
|
||||||
this.listing=await lastValueFrom(this.listingsService.getListingById(this.id,this.criteria.listingsCategory));
|
this.listing=await lastValueFrom(this.listingsService.getListingById(this.id,this.criteria.listingsCategory));
|
||||||
|
this.propertyImages=await this.listingsService.getPropertyImages(this.listing.id)
|
||||||
}
|
}
|
||||||
back(){
|
back(){
|
||||||
this.router.navigate(['listings',this.criteria.listingsCategory])
|
this.router.navigate(['listings',this.criteria.listingsCategory])
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="grid p-4 align-items-center">
|
<div class="grid p-4 align-items-center">
|
||||||
@if (listingCategory==='business'){
|
@if (category==='business'){
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<p-dropdown [options]="selectOptions.typesOfBusiness" [(ngModel)]="criteria.type" optionLabel="name"
|
<p-dropdown [options]="selectOptions.typesOfBusiness" [(ngModel)]="criteria.type" optionLabel="name"
|
||||||
optionValue="value" [showClear]="true" placeholder="Categorie of Business"
|
optionValue="value" [showClear]="true" placeholder="Categorie of Business"
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
offLabel="Real Estate included"></p-toggleButton>
|
offLabel="Real Estate included"></p-toggleButton>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (listingCategory==='commercialProperty'){
|
@if (category==='commercialProperty'){
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<p-dropdown [options]="states" [(ngModel)]="criteria.state" optionLabel="name" optionValue="value"
|
<p-dropdown [options]="states" [(ngModel)]="criteria.state" optionLabel="name" optionValue="value"
|
||||||
[showClear]="true" placeholder="Location" [style]="{ width: '100%'}"></p-dropdown>
|
[showClear]="true" placeholder="Location" [style]="{ width: '100%'}"></p-dropdown>
|
||||||
|
|
@ -57,10 +57,9 @@
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
@for (listing of filteredListings; track listing.id) {
|
@for (listing of filteredListings; track listing.id) {
|
||||||
<div class="col-12 lg:col-3 p-3">
|
<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="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="p-4 h-full flex flex-column">
|
||||||
@if (listing.listingsCategory==='business'){
|
|
||||||
<div class="flex align-items-center">
|
<div class="flex align-items-center">
|
||||||
<span [class]="selectOptions.getBgColorType(listing.type)"
|
<span [class]="selectOptions.getBgColorType(listing.type)"
|
||||||
class="inline-flex border-circle align-items-center justify-content-center mr-3"
|
class="inline-flex border-circle align-items-center justify-content-center mr-3"
|
||||||
|
|
@ -69,42 +68,83 @@
|
||||||
</span>
|
</span>
|
||||||
<span class="text-900 font-medium text-2xl">{{selectOptions.getBusiness(listing.type)}}</span>
|
<span class="text-900 font-medium text-2xl">{{selectOptions.getBusiness(listing.type)}}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
@if (listing.listingsCategory==='commercialProperty'){
|
|
||||||
<!-- <div class="flex align-items-center">
|
|
||||||
<span
|
|
||||||
class="inline-flex border-circle align-items-center justify-content-center bg-green-100 mr-3"
|
|
||||||
style="width:38px;height:38px">
|
|
||||||
<i class="pi pi-globe text-xl text-green-600"></i>
|
|
||||||
</span>
|
|
||||||
<span class="text-900 font-medium text-2xl">Investment</span>
|
|
||||||
</div> -->
|
|
||||||
}
|
|
||||||
<div class="text-900 my-3 text-xl font-medium">{{listing.title}}</div>
|
<div class="text-900 my-3 text-xl font-medium">{{listing.title}}</div>
|
||||||
@if (listing.listingsCategory==='business'){
|
|
||||||
<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">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">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">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">Location: {{selectOptions.getState(listing.state)}}</p>
|
||||||
<p class="mt-0 mb-1 text-700 line-height-3">Established: {{listing.established}}</p>
|
<p class="mt-0 mb-1 text-700 line-height-3">Established: {{listing.established}}</p>
|
||||||
}
|
|
||||||
@if (listing.listingsCategory==='commercialProperty'){
|
|
||||||
<!-- <p class="mt-0 mb-1 text-700 line-height-3">Location: {{selectOptions.getState(listing.state)}}</p>
|
|
||||||
<p class="mt-0 mb-1 text-700 line-height-3">EMail: {{listing.email}}</p>
|
|
||||||
<p class="mt-0 mb-1 text-700 line-height-3">Website: {{listing.website}}</p>
|
|
||||||
<p class="mt-0 mb-1 text-700 line-height-3">Phone Number: {{listing.phoneNumber}}</p> -->
|
|
||||||
}
|
|
||||||
<div class="mt-auto ml-auto">
|
<div class="mt-auto ml-auto">
|
||||||
<img *ngIf="!listing.hideImage" src="{{environment.apiBaseUrl}}/profile/{{listing.userId}}" (error)="imageErrorHandler(listing)" class="rounded-image"/>
|
<img *ngIf="!listing.hideImage" src="{{environment.apiBaseUrl}}/logo/{{listing.userId}}" (error)="imageErrorHandler(listing)" class="rounded-image"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 py-3 surface-100 text-right">
|
<div class="px-4 py-3 surface-100 text-left">
|
||||||
<button pButton pRipple icon="pi pi-arrow-right" iconPos="right" label="View Full Listing"
|
<button pButton pRipple icon="pi pi-arrow-right" iconPos="right" label="View Full Listing"
|
||||||
class="p-button-rounded p-button-success" [routerLink]="['/details',listing.id]"></button>
|
class="p-button-rounded p-button-success" [routerLink]="['/details',listing.id]"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@for (listing of filteredListings; track listing.id) {
|
||||||
|
<div *ngIf="listing.listingsCategory==='commercialProperty'" class="col-12 xl:col-4">
|
||||||
|
<div class="surface-card p-2" style="border-radius: 10px">
|
||||||
|
<article class="flex flex-column md:flex-row w-full gap-3 p-3 surface-card">
|
||||||
|
<div class="relative">
|
||||||
|
<img src="{{environment.apiBaseUrl}}/property/{{listing.id}}/1.avif" alt="Image" class="border-round w-full h-full md:w-12rem md:h-12rem">
|
||||||
|
<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: blur(10px); 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>
|
||||||
|
<!-- <p class="text-base flex align-items-center text-900 mt-0 mb-1">
|
||||||
|
<i class="pi pi-check-circle mr-2"></i>
|
||||||
|
<span class="font-medium">Verified</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.id]"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
@for (user of users; track user.id) {
|
||||||
|
<div class="col-12 lg:col-6 xl:col-4 p-4">
|
||||||
|
<div class="surface-card shadow-2 p-2" 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>
|
||||||
|
<img src="{{environment.apiBaseUrl}}/profile/{{user.id}}" 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">
|
||||||
|
<img *ngIf="user.hasCompanyLogo" src="{{environment.apiBaseUrl}}/logo/{{user.id}}" 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.id]"></button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- <div class="mt-auto ml-auto">
|
||||||
|
<img *ngIf="user.hasCompanyLogo" src="{{environment.apiBaseUrl}}/logo/{{user.id}}" class="rounded-image"/>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2 surface-200 flex align-items-center justify-content-center">
|
<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>
|
<div class="mx-1 text-color">Total number of Listings: {{totalRecords}}</div>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ import onChange from 'on-change';
|
||||||
import { createGenericObject, getCriteriaStateObject, getSessionStorageHandler } from '../../utils/utils';
|
import { createGenericObject, getCriteriaStateObject, getSessionStorageHandler } from '../../utils/utils';
|
||||||
import { InitEditableRow } from 'primeng/table';
|
import { InitEditableRow } from 'primeng/table';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { ListingCriteria, ListingType } from '../../../../../common-models/src/main.model';
|
import { ListingCriteria, ListingType, User } from '../../../../../common-models/src/main.model';
|
||||||
|
import { UserService } from '../../services/user.service';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-listings',
|
selector: 'app-listings',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
|
|
@ -27,10 +28,11 @@ import { ListingCriteria, ListingType } from '../../../../../common-models/src/m
|
||||||
export class ListingsComponent {
|
export class ListingsComponent {
|
||||||
environment=environment;
|
environment=environment;
|
||||||
listings: Array<ListingType>;
|
listings: Array<ListingType>;
|
||||||
|
users: Array<User>
|
||||||
filteredListings: Array<ListingType>;
|
filteredListings: Array<ListingType>;
|
||||||
criteria:ListingCriteria;
|
criteria:ListingCriteria;
|
||||||
realEstateChecked: boolean;
|
realEstateChecked: boolean;
|
||||||
category: string;
|
// category: string;
|
||||||
maxPrice: string;
|
maxPrice: string;
|
||||||
minPrice: string;
|
minPrice: string;
|
||||||
type:string;
|
type:string;
|
||||||
|
|
@ -40,9 +42,14 @@ export class ListingsComponent {
|
||||||
first: number = 0;
|
first: number = 0;
|
||||||
rows: number = 12;
|
rows: number = 12;
|
||||||
totalRecords:number = 0;
|
totalRecords:number = 0;
|
||||||
public listingCategory: 'business' | 'commercialProperty' | undefined;
|
public category: 'business' | 'commercialProperty' | 'professionals_brokers' | undefined;
|
||||||
|
|
||||||
constructor(public selectOptions: SelectOptionsService, private listingsService:ListingsService,private activatedRoute: ActivatedRoute, private router:Router, private cdRef:ChangeDetectorRef) {
|
constructor(public selectOptions: SelectOptionsService,
|
||||||
|
private listingsService:ListingsService,
|
||||||
|
private userService:UserService,
|
||||||
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private router:Router,
|
||||||
|
private cdRef:ChangeDetectorRef) {
|
||||||
this.criteria = onChange(getCriteriaStateObject(),getSessionStorageHandler);
|
this.criteria = onChange(getCriteriaStateObject(),getSessionStorageHandler);
|
||||||
this.router.getCurrentNavigation()
|
this.router.getCurrentNavigation()
|
||||||
this.activatedRoute.snapshot
|
this.activatedRoute.snapshot
|
||||||
|
|
@ -51,8 +58,8 @@ export class ListingsComponent {
|
||||||
this.criteria = onChange(createGenericObject<ListingCriteria>(),getSessionStorageHandler)
|
this.criteria = onChange(createGenericObject<ListingCriteria>(),getSessionStorageHandler)
|
||||||
this.first=0;
|
this.first=0;
|
||||||
}
|
}
|
||||||
this.listingCategory = (<any>params).type;
|
this.category = (<any>params).type;
|
||||||
this.criteria.listingsCategory=this.listingCategory;
|
this.criteria.listingsCategory=this.category;
|
||||||
this.init()
|
this.init()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -60,6 +67,8 @@ export class ListingsComponent {
|
||||||
async ngOnInit(){
|
async ngOnInit(){
|
||||||
}
|
}
|
||||||
async init(){
|
async init(){
|
||||||
|
if (this.category==='business' || this.category==='commercialProperty'){
|
||||||
|
this.users=[]
|
||||||
this.listings=await this.listingsService.getListings(this.criteria);
|
this.listings=await this.listingsService.getListings(this.criteria);
|
||||||
this.setStates();
|
this.setStates();
|
||||||
this.filteredListings=[...this.listings];
|
this.filteredListings=[...this.listings];
|
||||||
|
|
@ -67,6 +76,14 @@ export class ListingsComponent {
|
||||||
this.filteredListings=[...this.listings].splice(this.first,this.rows);
|
this.filteredListings=[...this.listings].splice(this.first,this.rows);
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
this.cdRef.detectChanges();
|
this.cdRef.detectChanges();
|
||||||
|
} else {
|
||||||
|
this.listings=[]
|
||||||
|
this.filteredListings=[];
|
||||||
|
this.users=await this.userService.search(this.criteria);
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
this.cdRef.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
setStates(){
|
setStates(){
|
||||||
this.statesSet=new Set();
|
this.statesSet=new Set();
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,26 @@
|
||||||
<input id="state" type="text" [disabled]="true" pInputText [(ngModel)]="user.email">
|
<input id="state" type="text" [disabled]="true" pInputText [(ngModel)]="user.email">
|
||||||
<p class="font-italic text-sm line-height-1">You can only modify your email by contacting us at emailchange@bizmatch.net</p>
|
<p class="font-italic text-sm line-height-1">You can only modify your email by contacting us at emailchange@bizmatch.net</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="grid">
|
||||||
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<label for="firstname" class="block font-medium text-900 mb-2">First Name</label>
|
<label for="firstname" class="block font-medium text-900 mb-2">First Name</label>
|
||||||
<input id="firstname" type="text" pInputText [(ngModel)]="user.firstname">
|
<input id="firstname" type="text" pInputText [(ngModel)]="user.firstname">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<label for="lastname" class="block font-medium text-900 mb-2">Last Name</label>
|
<label for="lastname" class="block font-medium text-900 mb-2">Last Name</label>
|
||||||
<input id="lastname" type="text" pInputText [(ngModel)]="user.lastname">
|
<input id="lastname" type="text" pInputText [(ngModel)]="user.lastname">
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="mb-4 col-12 md:col-6">
|
||||||
|
<label for="firstname" class="block font-medium text-900 mb-2">Company Name</label>
|
||||||
|
<input id="firstname" type="text" pInputText [(ngModel)]="user.companyName">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4 col-12 md:col-6">
|
||||||
|
<label for="lastname" class="block font-medium text-900 mb-2">Describe yourself</label>
|
||||||
|
<input id="lastname" type="text" pInputText [(ngModel)]="user.description">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="mb-4 col-12 md:col-4">
|
<div class="mb-4 col-12 md:col-4">
|
||||||
<label for="phoneNumber" class="block font-medium text-900 mb-2">Your Phone Number</label>
|
<label for="phoneNumber" class="block font-medium text-900 mb-2">Your Phone Number</label>
|
||||||
|
|
@ -67,13 +79,6 @@
|
||||||
<span class="text-xs"> (Add more licenses or remove existing ones.)</span>
|
<span class="text-xs"> (Add more licenses or remove existing ones.)</span>
|
||||||
<!-- <button pButton pRipple label="Add Licence" class="w-auto" (click)="addLicence()"></button> -->
|
<!-- <button pButton pRipple label="Add Licence" class="w-auto" (click)="addLicence()"></button> -->
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="mb-4">
|
|
||||||
<label for="state" class="block font-medium text-900 mb-2">New Password</label>
|
|
||||||
<p class="font-italic text-sm line-height-1">If you would like to change the password type a new one. Otherwise leave this blank.</p>
|
|
||||||
<input id="state" type="text" pInputText>
|
|
||||||
<p class="font-italic text-sm line-height-1">Password repetition</p>
|
|
||||||
<input id="state" type="text" pInputText>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button pButton pRipple label="Update Profile" class="w-auto" (click)="updateProfile(user)"></button>
|
<button pButton pRipple label="Update Profile" class="w-auto" (click)="updateProfile(user)"></button>
|
||||||
|
|
@ -83,14 +88,14 @@
|
||||||
<div class="flex flex-column align-items-center flex-or mb-8">
|
<div class="flex flex-column align-items-center flex-or mb-8">
|
||||||
<span class="font-medium text-900 mb-2">Company Logo</span>
|
<span class="font-medium text-900 mb-2">Company Logo</span>
|
||||||
<span class="font-medium text-xs mb-2">(is shown in every offer)</span>
|
<span class="font-medium text-xs mb-2">(is shown in every offer)</span>
|
||||||
<img [src]="companyLogoUrl" (error)="setImageToFallback($event)" class="rounded-image"/>
|
<img [src]="companyLogoUrl" class="rounded-logo"/>
|
||||||
<p-fileUpload mode="basic" chooseLabel="Upload" name="file" [url]="uploadUrl" accept="image/*" [maxFileSize]="maxFileSize" (onUpload)="onUploadCompanyLogo($event)" [auto]="true" styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"></p-fileUpload>
|
<p-fileUpload mode="basic" chooseLabel="Upload" name="file" [url]="uploadCompanyUrl" accept="image/*" [maxFileSize]="maxFileSize" (onUpload)="onUploadCompanyLogo($event)" [auto]="true" styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"></p-fileUpload>
|
||||||
</div>
|
</div>
|
||||||
<p-divider></p-divider>
|
<p-divider></p-divider>
|
||||||
<div class="flex flex-column align-items-center flex-or">
|
<div class="flex flex-column align-items-center flex-or">
|
||||||
<span class="font-medium text-900 mb-2">Your Profile Picture</span>
|
<span class="font-medium text-900 mb-2">Your Profile Picture</span>
|
||||||
<img [src]="profileUrl" (error)="setImageToFallback($event)" class="rounded-image"/>
|
<img [src]="profileUrl" class="rounded-profile"/>
|
||||||
<p-fileUpload mode="basic" chooseLabel="Upload" name="file" [url]="uploadUrl" accept="image/*" [maxFileSize]="maxFileSize" (onUpload)="onUploadProfilePicture($event)" [auto]="true" styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"></p-fileUpload>
|
<p-fileUpload mode="basic" chooseLabel="Upload" name="file" [url]="uploadProfileUrl" accept="image/*" [maxFileSize]="maxFileSize" (onUpload)="onUploadProfilePicture($event)" [auto]="true" styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"></p-fileUpload>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
.rounded-image {
|
.rounded-logo {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
|
@ -6,7 +6,15 @@
|
||||||
padding: 1px 1px;
|
padding: 1px 1px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
.rounded-profile {
|
||||||
|
// @extend .rounded-logo;
|
||||||
|
max-width: 100px;
|
||||||
|
max-height: 120px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #6b7280;
|
||||||
|
padding: 1px 1px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
.wfull{
|
.wfull{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
@ -41,7 +41,8 @@ export class AccountComponent {
|
||||||
user:User;
|
user:User;
|
||||||
subscriptions:Array<Subscription>;
|
subscriptions:Array<Subscription>;
|
||||||
userSubscriptions:Array<Subscription>=[];
|
userSubscriptions:Array<Subscription>=[];
|
||||||
uploadUrl:string;
|
uploadProfileUrl:string;
|
||||||
|
uploadCompanyUrl:string;
|
||||||
maxFileSize=1000000;
|
maxFileSize=1000000;
|
||||||
companyLogoUrl:string;
|
companyLogoUrl:string;
|
||||||
profileUrl:string;
|
profileUrl:string;
|
||||||
|
|
@ -52,17 +53,18 @@ export class AccountComponent {
|
||||||
public selectOptions:SelectOptionsService,
|
public selectOptions:SelectOptionsService,
|
||||||
private cdref:ChangeDetectorRef) {
|
private cdref:ChangeDetectorRef) {
|
||||||
this.user=this.userService.getUser()
|
this.user=this.userService.getUser()
|
||||||
|
|
||||||
}
|
}
|
||||||
async ngOnInit(){
|
async ngOnInit(){
|
||||||
this.profileUrl = `${environment.apiBaseUrl}/profile/${this.user.id}`
|
|
||||||
this.companyLogoUrl = `${environment.apiBaseUrl}/logo/${this.user.id}`
|
|
||||||
this.userSubscriptions=await lastValueFrom(this.subscriptionService.getAllSubscriptions());
|
this.userSubscriptions=await lastValueFrom(this.subscriptionService.getAllSubscriptions());
|
||||||
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/account/uploadPhoto/${this.user.id}`;
|
this.uploadProfileUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadProfile/${this.user.id}`;
|
||||||
|
this.uploadCompanyUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadCompanyLogo/${this.user.id}`;
|
||||||
if (!this.user.licensedIn || this.user.licensedIn?.length===0){
|
if (!this.user.licensedIn || this.user.licensedIn?.length===0){
|
||||||
this.user.licensedIn = [{name:'',value:''}]
|
this.user.licensedIn = [{name:'',value:''}]
|
||||||
}
|
}
|
||||||
this.user=await this.userService.getById(this.user.id);
|
this.user=await this.userService.getById(this.user.id);
|
||||||
|
this.profileUrl = this.user.hasProfile?`${environment.apiBaseUrl}/profile/${this.user.id}`:`/assets/images/placeholder.png`
|
||||||
|
this.companyLogoUrl = this.user.hasCompanyLogo?`${environment.apiBaseUrl}/logo/${this.user.id}`:`/assets/images/placeholder.png`
|
||||||
}
|
}
|
||||||
printInvoice(invoice:Invoice){}
|
printInvoice(invoice:Invoice){}
|
||||||
|
|
||||||
|
|
@ -74,7 +76,7 @@ export class AccountComponent {
|
||||||
|
|
||||||
onUploadCompanyLogo(event:any){
|
onUploadCompanyLogo(event:any){
|
||||||
const uniqueSuffix = '?_ts=' + new Date().getTime();
|
const uniqueSuffix = '?_ts=' + new Date().getTime();
|
||||||
this.companyLogoUrl = `${environment.apiBaseUrl}/company/${this.user.id}${uniqueSuffix}` //`http://IhrServer:Port/${newImagePath}${uniqueSuffix}`;
|
this.companyLogoUrl = `${environment.apiBaseUrl}/logo/${this.user.id}${uniqueSuffix}` //`http://IhrServer:Port/${newImagePath}${uniqueSuffix}`;
|
||||||
}
|
}
|
||||||
onUploadProfilePicture(event:any){
|
onUploadProfilePicture(event:any){
|
||||||
const uniqueSuffix = '?_ts=' + new Date().getTime();
|
const uniqueSuffix = '?_ts=' + new Date().getTime();
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ export class EditListingComponent {
|
||||||
this.listing.userId=this.user.id
|
this.listing.userId=this.user.id
|
||||||
this.listing.listingsCategory='business';
|
this.listing.listingsCategory='business';
|
||||||
}
|
}
|
||||||
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/${this.listing.listingsCategory}/uploadPropertyPicture/${this.listing.id}`;
|
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`;
|
||||||
this.propertyImages=await this.listingsService.getPropertyImages(this.listing.id)
|
this.propertyImages=await this.listingsService.getPropertyImages(this.listing.id)
|
||||||
}
|
}
|
||||||
async save(){
|
async save(){
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,6 @@ export class ListingsService {
|
||||||
await lastValueFrom(this.http.delete<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/${id}`));
|
await lastValueFrom(this.http.delete<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/${id}`));
|
||||||
}
|
}
|
||||||
async getPropertyImages(id:string):Promise<ImageProperty[]>{
|
async getPropertyImages(id:string):Promise<ImageProperty[]>{
|
||||||
return await lastValueFrom(this.http.get<ImageProperty[]>(`${this.apiBaseUrl}/bizmatch/listings/commercialProperty/images/${id}`));
|
return await lastValueFrom(this.http.get<ImageProperty[]>(`${this.apiBaseUrl}/bizmatch/image/${id}`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { BehaviorSubject, debounceTime, distinctUntilChanged, map, shareReplay } from 'rxjs';
|
import { BehaviorSubject, Observable, debounceTime, distinctUntilChanged, map, shareReplay } from 'rxjs';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
|
|
@ -7,6 +7,9 @@ import { BehaviorSubject, debounceTime, distinctUntilChanged, map, shareReplay }
|
||||||
export class LoadingService {
|
export class LoadingService {
|
||||||
public loading$ = new BehaviorSubject<string[]>([]);
|
public loading$ = new BehaviorSubject<string[]>([]);
|
||||||
|
|
||||||
|
private loadingTextSubject = new BehaviorSubject<string | null>(null);
|
||||||
|
loadingText$: Observable<string | null> = this.loadingTextSubject.asObservable();
|
||||||
|
|
||||||
public isLoading$ = this.loading$.asObservable().pipe(
|
public isLoading$ = this.loading$.asObservable().pipe(
|
||||||
map((loading) => loading.length > 0),
|
map((loading) => loading.length > 0),
|
||||||
debounceTime(200),
|
debounceTime(200),
|
||||||
|
|
@ -14,15 +17,21 @@ export class LoadingService {
|
||||||
shareReplay(1)
|
shareReplay(1)
|
||||||
);
|
);
|
||||||
|
|
||||||
public startLoading(type: string): void {
|
public startLoading(type: string,request:string): void {
|
||||||
if (!this.loading$.value.includes(type)) {
|
if (!this.loading$.value.includes(type)) {
|
||||||
this.loading$.next(this.loading$.value.concat(type));
|
this.loading$.next(this.loading$.value.concat(type));
|
||||||
|
if (request.includes('uploadPropertyPicture')) {
|
||||||
|
this.loadingTextSubject.next("Please wait - we're processing your image...");
|
||||||
|
} else {
|
||||||
|
this.loadingTextSubject.next(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public stopLoading(type: string): void {
|
public stopLoading(type: string): void {
|
||||||
if (this.loading$.value.includes(type)) {
|
if (this.loading$.value.includes(type)) {
|
||||||
this.loading$.next(this.loading$.value.filter((t) => t !== type));
|
this.loading$.next(this.loading$.value.filter((t) => t !== type));
|
||||||
|
this.loadingTextSubject.next(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +42,9 @@ export class SelectOptionsService {
|
||||||
getBusiness(value:string):string{
|
getBusiness(value:string):string{
|
||||||
return this.typesOfBusiness.find(t=>t.value===value)?.name
|
return this.typesOfBusiness.find(t=>t.value===value)?.name
|
||||||
}
|
}
|
||||||
|
getCommercialProperty(value:string):string{
|
||||||
|
return this.typesOfCommercialProperty.find(t=>t.value===value)?.name
|
||||||
|
}
|
||||||
getListingsCategory(value:string):string{
|
getListingsCategory(value:string):string{
|
||||||
return this.listingCategories.find(l=>l.value===value)?.name
|
return this.listingCategories.find(l=>l.value===value)?.name
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { jwtDecode } from 'jwt-decode';
|
||||||
import { Observable, distinctUntilChanged, filter, from, lastValueFrom, map } from 'rxjs';
|
import { Observable, distinctUntilChanged, filter, from, lastValueFrom, map } from 'rxjs';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { KeycloakService } from './keycloak.service';
|
import { KeycloakService } from './keycloak.service';
|
||||||
import { JwtToken, User } from '../../../../common-models/src/main.model';
|
import { JwtToken, ListingCriteria, User } from '../../../../common-models/src/main.model';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
|
@ -103,4 +103,7 @@ export class UserService {
|
||||||
async getById(id:string):Promise<User>{
|
async getById(id:string):Promise<User>{
|
||||||
return await lastValueFrom(this.http.get<User>(`${this.apiBaseUrl}/bizmatch/user/${id}`));
|
return await lastValueFrom(this.http.get<User>(`${this.apiBaseUrl}/bizmatch/user/${id}`));
|
||||||
}
|
}
|
||||||
|
async search(criteria?:ListingCriteria){
|
||||||
|
return await lastValueFrom(this.http.post<User[]>(`${this.apiBaseUrl}/bizmatch/user/search`,criteria));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,11 @@ export interface Listing {
|
||||||
}
|
}
|
||||||
export interface BusinessListing extends Listing {
|
export interface BusinessListing extends Listing {
|
||||||
listingsCategory: 'business'; //enum
|
listingsCategory: 'business'; //enum
|
||||||
// summary: Array<string>;
|
|
||||||
realEstateIncluded?: boolean;
|
realEstateIncluded?: boolean;
|
||||||
leasedLocation?:boolean;
|
leasedLocation?:boolean;
|
||||||
franchiseResale?:boolean;
|
franchiseResale?:boolean;
|
||||||
salesRevenue?: number;
|
salesRevenue?: number;
|
||||||
cashFlow?: number;
|
cashFlow?: number;
|
||||||
// netProfit?: number;
|
|
||||||
supportAndTraining?: string;
|
supportAndTraining?: string;
|
||||||
employees?: number;
|
employees?: number;
|
||||||
established?: number;
|
established?: number;
|
||||||
|
|
@ -49,24 +47,17 @@ export interface BusinessListing extends Listing {
|
||||||
}
|
}
|
||||||
export interface CommercialPropertyListing extends Listing {
|
export interface CommercialPropertyListing extends Listing {
|
||||||
listingsCategory: 'commercialProperty'; //enum
|
listingsCategory: 'commercialProperty'; //enum
|
||||||
images:string[];
|
|
||||||
zipCode:number;
|
zipCode:number;
|
||||||
county:string
|
county:string
|
||||||
email?: string;
|
email?: string;
|
||||||
website?: string;
|
website?: string;
|
||||||
phoneNumber?: string;
|
phoneNumber?: string;
|
||||||
|
hasImages:boolean;
|
||||||
}
|
}
|
||||||
export type ListingType =
|
export type ListingType =
|
||||||
| BusinessListing
|
| BusinessListing
|
||||||
| CommercialPropertyListing;
|
| CommercialPropertyListing;
|
||||||
// export interface ProfessionalsBrokersListing extends Listing {
|
|
||||||
// listingsCategory: 'professionals_brokers'; //enum
|
|
||||||
// summary: string;
|
|
||||||
// address?: string;
|
|
||||||
// email?: string;
|
|
||||||
// website?: string;
|
|
||||||
// category?: 'Professionals' | 'Broker';
|
|
||||||
// }
|
|
||||||
export interface ListingCriteria {
|
export interface ListingCriteria {
|
||||||
type:string,
|
type:string,
|
||||||
state:string,
|
state:string,
|
||||||
|
|
@ -82,11 +73,15 @@ export interface UserBase {
|
||||||
lastname: string;
|
lastname: string;
|
||||||
email: string;
|
email: string;
|
||||||
phoneNumber?: string;
|
phoneNumber?: string;
|
||||||
|
description?:string;
|
||||||
|
companyName?:string;
|
||||||
companyOverview?:string;
|
companyOverview?:string;
|
||||||
companyWebsite?:string;
|
companyWebsite?:string;
|
||||||
companyLocation?:string;
|
companyLocation?:string;
|
||||||
offeredServices?:string;
|
offeredServices?:string;
|
||||||
areasServed?:string;
|
areasServed?:string;
|
||||||
|
hasProfile?:boolean;
|
||||||
|
hasCompanyLogo?:boolean;
|
||||||
}
|
}
|
||||||
export interface User extends UserBase {
|
export interface User extends UserBase {
|
||||||
licensedIn?:KeyValue[];
|
licensedIn?:KeyValue[];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue