diff --git a/bizmatch-server/package.json b/bizmatch-server/package.json index 3e2dfa8..dc37ce7 100644 --- a/bizmatch-server/package.json +++ b/bizmatch-server/package.json @@ -42,6 +42,7 @@ "redis-om": "^0.4.3", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", + "sharp": "^0.33.2", "urlcat": "^3.1.0", "winston": "^3.11.0" }, diff --git a/bizmatch-server/src/account/account.controller.ts b/bizmatch-server/src/account/account.controller.ts deleted file mode 100644 index 9c3e070..0000000 --- a/bizmatch-server/src/account/account.controller.ts +++ /dev/null @@ -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); - } -} diff --git a/bizmatch-server/src/account/account.module.ts b/bizmatch-server/src/account/account.module.ts deleted file mode 100644 index 1157e45..0000000 --- a/bizmatch-server/src/account/account.module.ts +++ /dev/null @@ -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 {} diff --git a/bizmatch-server/src/app.module.ts b/bizmatch-server/src/app.module.ts index 47b5c4e..966f4a3 100644 --- a/bizmatch-server/src/app.module.ts +++ b/bizmatch-server/src/app.module.ts @@ -10,8 +10,6 @@ import { SelectOptionsService } from './select-options/select-options.service.js import { SubscriptionsController } from './subscriptions/subscriptions.controller.js'; import { RedisModule } from './redis/redis.module.js'; import { ListingsService } from './listings/listings.service.js'; -import { AccountController } from './account/account.controller.js'; -import { AccountService } from './account/account.service.js'; import { ServeStaticModule } from '@nestjs/serve-static'; import path, { join } from 'path'; import { fileURLToPath } from 'url'; @@ -22,9 +20,9 @@ import { AuthModule } from './auth/auth.module.js'; import { GeoModule } from './geo/geo.module.js'; import { UserModule } from './user/user.module.js'; import { ListingsModule } from './listings/listings.module.js'; -import { AccountModule } from './account/account.module.js'; import { SelectOptionsModule } from './select-options/select-options.module.js'; import { CommercialPropertyListingsController } from './listings/commercial-property-listings.controller.js'; +import { ImageModule } from './image/image.module.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -52,9 +50,9 @@ const __dirname = path.dirname(__filename); GeoModule, UserModule, ListingsModule, - AccountModule, SelectOptionsModule, - RedisModule + RedisModule, + ImageModule ], controllers: [AppController, SubscriptionsController], providers: [AppService, FileService], diff --git a/bizmatch-server/src/file/file.service.ts b/bizmatch-server/src/file/file.service.ts index 4e00371..36433b0 100644 --- a/bizmatch-server/src/file/file.service.ts +++ b/bizmatch-server/src/file/file.service.ts @@ -1,18 +1,20 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { fstat, readFileSync } from 'fs'; import { join } from 'path'; import { fileURLToPath } from 'url'; import path from 'path'; 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 __dirname = path.dirname(__filename); @Injectable() export class FileService { private subscriptions: any; - constructor() { + constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) { this.loadSubscriptions(); fs.ensureDirSync(`./pictures`); fs.ensureDirSync(`./pictures/profile`); @@ -20,42 +22,60 @@ export class FileService { fs.ensureDirSync(`./pictures/property`); } private loadSubscriptions(): void { - const filePath = join(__dirname,'..', 'assets', 'subscriptions.json'); + const filePath = join(__dirname, '..', 'assets', 'subscriptions.json'); const rawData = readFileSync(filePath, 'utf8'); this.subscriptions = JSON.parse(rawData); } getSubscriptions() { return this.subscriptions } - async storeProfilePicture(file: Express.Multer.File,userId: string){ - const suffix = file.mimetype.includes('png')?'png':'jpg' - await fs.outputFile(`./pictures/profile/${userId}`,file.buffer); + async storeProfilePicture(file: Express.Multer.File, userId: string) { + const suffix = file.mimetype.includes('png') ? 'png' : 'jpg' + await fs.outputFile(`./pictures/profile/${userId}`, file.buffer); } - async storeCompanyLogo(file: Express.Multer.File,userId: string){ - const suffix = file.mimetype.includes('png')?'png':'jpg' - await fs.outputFile(`./pictures/logo/${userId}`,file.buffer); + hasProfile(userId: string){ + return fs.existsSync(`./pictures/profile/${userId}`) } - async getPropertyImages(listingId: string):Promise{ - const result:ImageProperty[]=[] + + async storeCompanyLogo(file: Express.Multer.File, userId: string) { + const suffix = file.mimetype.includes('png') ? 'png' : 'jpg' + await fs.outputFile(`./pictures/logo/${userId}`, file.buffer); + } + hasCompanyLogo(userId: string){ + return fs.existsSync(`./pictures/logo/${userId}`) + } + + async getPropertyImages(listingId: string): Promise { + const result: ImageProperty[] = [] const directory = `./pictures/property/${listingId}` - if (fs.existsSync(directory)){ + if (fs.existsSync(directory)) { const files = await fs.readdir(directory); - files.forEach(f=>{ - const image:ImageProperty={name:f,id:'',code:''}; + files.forEach(f => { + const image: ImageProperty = { name: f, id: '', code: '' }; result.push(image) }) return result; } else { return [] } - } - async storePropertyPicture(file: Express.Multer.File,listingId: string){ - const suffix = file.mimetype.includes('png')?'png':'jpg' + async hasPropertyImages(listingId: string): Promise { + 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) { + const suffix = file.mimetype.includes('png') ? 'png' : 'jpg' const directory = `./pictures/property/${listingId}` fs.ensureDirSync(`${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) { try { @@ -64,7 +84,7 @@ export class FileService { .map(file => parseInt(file, 10)) // Dateinamen direkt in Zahlen umwandeln .filter(number => !isNaN(number)) // Sicherstellen, dass die Konvertierung gültig ist .sort((a, b) => a - b); // Aufsteigend sortieren - + const nextNumber = imageNumbers.length > 0 ? Math.max(...imageNumbers) + 1 : 1; return `${nextNumber}`; // Keine Endung für den Dateinamen } catch (error) { @@ -72,4 +92,24 @@ export class FileService { 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 + } + } diff --git a/bizmatch-server/src/image/image.controller.ts b/bizmatch-server/src/image/image.controller.ts new file mode 100644 index 0000000..4cfeba1 --- /dev/null +++ b/bizmatch-server/src/image/image.controller.ts @@ -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 { + 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); + } +} diff --git a/bizmatch-server/src/image/image.module.ts b/bizmatch-server/src/image/image.module.ts new file mode 100644 index 0000000..a202fb3 --- /dev/null +++ b/bizmatch-server/src/image/image.module.ts @@ -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 {} diff --git a/bizmatch-server/src/account/account.service.ts b/bizmatch-server/src/image/image.service.ts similarity index 65% rename from bizmatch-server/src/account/account.service.ts rename to bizmatch-server/src/image/image.service.ts index 682f9a4..366cd50 100644 --- a/bizmatch-server/src/account/account.service.ts +++ b/bizmatch-server/src/image/image.service.ts @@ -1,4 +1,4 @@ import { Injectable } from '@nestjs/common'; @Injectable() -export class AccountService {} +export class ImageService {} diff --git a/bizmatch-server/src/listings/commercial-property-listings.controller.ts b/bizmatch-server/src/listings/commercial-property-listings.controller.ts index 9ce561f..61be7e5 100644 --- a/bizmatch-server/src/listings/commercial-property-listings.controller.ts +++ b/bizmatch-server/src/listings/commercial-property-listings.controller.ts @@ -11,11 +11,7 @@ export class CommercialPropertyListingsController { 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') findById(@Param('id') id:string): any { return this.listingsService.getCommercialPropertyListingById(id); @@ -43,14 +39,5 @@ export class CommercialPropertyListingsController { 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); - } } diff --git a/bizmatch-server/src/listings/listings.service.ts b/bizmatch-server/src/listings/listings.service.ts index 6943693..5122622 100644 --- a/bizmatch-server/src/listings/listings.service.ts +++ b/bizmatch-server/src/listings/listings.service.ts @@ -95,8 +95,6 @@ export class ListingsService { async getAllCommercialListings(start?: number, end?: number) { return await this.commercialPropertyListingRepository.search().return.all() } - - async findBusinessListings(criteria:ListingCriteria): Promise { let listings = await this.getAllBusinessListings(); return this.find(criteria,listings); diff --git a/bizmatch-server/src/user/user.controller.ts b/bizmatch-server/src/user/user.controller.ts index 78f77ed..46c8d58 100644 --- a/bizmatch-server/src/user/user.controller.ts +++ b/bizmatch-server/src/user/user.controller.ts @@ -20,9 +20,9 @@ export class UserController { return this.userService.saveUser(user); } - // @Put() - // update(@Body() user: any):Promise{ - // this.logger.info(`update User`); - // return this.userService.saveUser(user); - // } + @Post('search') + find(@Body() criteria: any): any { + return this.userService.findUser(criteria); + } + } diff --git a/bizmatch-server/src/user/user.module.ts b/bizmatch-server/src/user/user.module.ts index 8030287..71cbc11 100644 --- a/bizmatch-server/src/user/user.module.ts +++ b/bizmatch-server/src/user/user.module.ts @@ -2,11 +2,12 @@ import { Module } from '@nestjs/common'; import { UserController } from './user.controller.js'; import { UserService } from './user.service.js'; import { RedisModule } from '../redis/redis.module.js'; +import { FileService } from '../file/file.service.js'; @Module({ imports: [RedisModule], controllers: [UserController], - providers: [UserService] + providers: [UserService,FileService] }) export class UserModule { } diff --git a/bizmatch-server/src/user/user.service.ts b/bizmatch-server/src/user/user.service.ts index 1ef5a58..7d7cf30 100644 --- a/bizmatch-server/src/user/user.service.ts +++ b/bizmatch-server/src/user/user.service.ts @@ -4,6 +4,7 @@ import { Entity, Repository, Schema } from 'redis-om'; import { ListingCriteria, User } from '../models/main.model.js'; import { REDIS_CLIENT } from '../redis/redis.module.js'; import { UserEntity } from '../models/server.model.js'; +import { FileService } from '../file/file.service.js'; @Injectable() export class UserService { @@ -24,12 +25,15 @@ export class UserService { }, { 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.createIndex(); } 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{ return await this.userRepository.save(user.id,user) as UserEntity diff --git a/bizmatch/src/app/app.component.html b/bizmatch/src/app/app.component.html index 1ad28e6..8f8d7fe 100644 --- a/bizmatch/src/app/app.component.html +++ b/bizmatch/src/app/app.component.html @@ -4,11 +4,20 @@
} - @if (loadingService.isLoading$ | async) { -
- -
- }
- \ No newline at end of file + + + + +@if (loadingService.isLoading$ | async) { +
+
+ +
{{loadingText}}
+
+
+} \ No newline at end of file diff --git a/bizmatch/src/app/app.component.scss b/bizmatch/src/app/app.component.scss index 944105f..4c93062 100644 --- a/bizmatch/src/app/app.component.scss +++ b/bizmatch/src/app/app.component.scss @@ -7,4 +7,58 @@ flex: 1; /* Optional: Padding für den Inhalt, um sicherzustellen, dass er nicht direkt am Footer klebt */ // padding-bottom: 20px; -} \ No newline at end of file +} +.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 */ +// } \ No newline at end of file diff --git a/bizmatch/src/app/app.component.ts b/bizmatch/src/app/app.component.ts index 861729a..b6577de 100644 --- a/bizmatch/src/app/app.component.ts +++ b/bizmatch/src/app/app.component.ts @@ -39,17 +39,6 @@ export class AppComponent { // Hier haben Sie Zugriff auf den aktuellen Route-Pfad 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(){ this.user = this.userService.getUser(); diff --git a/bizmatch/src/app/app.config.ts b/bizmatch/src/app/app.config.ts index 2059a99..7e0a0cf 100644 --- a/bizmatch/src/app/app.config.ts +++ b/bizmatch/src/app/app.config.ts @@ -4,11 +4,12 @@ import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; import { provideClientHydration } from '@angular/platform-browser'; 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 { SelectOptionsService } from './services/select-options.service'; import { KeycloakService } from './services/keycloak.service'; import { UserService } from './services/user.service'; +import { LoadingInterceptor } from './interceptors/loading.interceptor'; // provideClientHydration() export const appConfig: ApplicationConfig = { providers: [ @@ -26,6 +27,11 @@ export const appConfig: ApplicationConfig = { multi: true, deps: [SelectOptionsService], }, + { + provide:HTTP_INTERCEPTORS, + useClass:LoadingInterceptor, + multi:true + }, provideRouter(routes),provideAnimations(), // {provide: LOCALE_ID, useValue: 'en-US' } ] diff --git a/bizmatch/src/app/interceptors/loading.interceptor.ts b/bizmatch/src/app/interceptors/loading.interceptor.ts index 874c09e..bbf87e3 100644 --- a/bizmatch/src/app/interceptors/loading.interceptor.ts +++ b/bizmatch/src/app/interceptors/loading.interceptor.ts @@ -1,21 +1,39 @@ -import { HttpInterceptorFn } from '@angular/common/http'; -import { inject } from '@angular/core'; -import { tap } from 'rxjs'; +import { HttpEvent, HttpHandler, HttpInterceptor, HttpInterceptorFn, HttpRequest } from '@angular/common/http'; +import { Injectable, inject } from '@angular/core'; +import { Observable, tap } from 'rxjs'; import { v4 } from 'uuid'; import { LoadingService } from '../services/loading.service'; -export const loadingInterceptor: HttpInterceptorFn = (req, next) => { - const loadingService = inject(LoadingService); +// export const loadingInterceptor: HttpInterceptorFn = (req, next) => { +// const loadingService = inject(LoadingService); - const requestId = `HTTP-${v4()}`; +// const requestId = `HTTP-${v4()}`; - loadingService.startLoading(requestId); +// loadingService.startLoading(requestId); - return next(req).pipe( - tap({ - finalize: () => loadingService.stopLoading(requestId), - error: () => loadingService.stopLoading(requestId), - complete: () => loadingService.stopLoading(requestId), - }) - ); -}; \ No newline at end of file +// 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, next: HttpHandler): Observable> { + console.log("Intercepting Requests") + const requestId = `HTTP-${v4()}`; + this.loadingService.startLoading(requestId,request.url); + + // return next.handle(request); + return next.handle(request).pipe( + tap({ + finalize: () => this.loadingService.stopLoading(requestId), // Stoppt den Ladevorgang, wenn die Anfrage abgeschlossen ist + // Beachte, dass 'error' und 'complete' hier entfernt wurden, da 'finalize' in allen Fällen aufgerufen wird, + // egal ob die Anfrage erfolgreich war, einen Fehler geworfen hat oder abgeschlossen wurde. + }) + ); + } +} \ No newline at end of file diff --git a/bizmatch/src/app/pages/details/details.component.html b/bizmatch/src/app/pages/details/details.component.html index 134f38c..0591d44 100644 --- a/bizmatch/src/app/pages/details/details.component.html +++ b/bizmatch/src/app/pages/details/details.component.html @@ -7,22 +7,11 @@ + @if (listing){
    - +
  • Description
    {{listing?.description}}
    @@ -63,47 +52,43 @@
    {{listing.brokerLicencing}}
  • } - @if (listing && (listing.listingsCategory==='commercialProperty')){
  • +
    Property Category
    +
    {{selectOptions.getCommercialProperty(listing.type)}}
    +
  • +
  • Located in
    {{selectOptions.getState(listing.state)}}
  • -
  • -
    EMail
    -
    {{listing.email}}
    -
  • -
    Website
    -
    {{listing.website}}
    +
    City
    +
    {{listing.city}}
  • -
    Phone Number
    -
    {{listing.phoneNumber}}
    -
  • +
    Zip Code
    +
    {{listing.zipCode}}
    + +
  • +
    County
    +
    {{listing.county}}
    +
  • +
  • +
    Asking Price:
    +
    {{listing.price | currency}}
    +
  • }
+ + + + + + @if(listing && user && (user.id===listing?.userId || isAdmin())){ } @@ -141,6 +126,118 @@
+ } @else { +
+
+
+ +
+ {{user.firstname}} {{user.lastname}} + +
+
+ Company +
{{user.companyName}}
+
+
+ For Sale +
12
+
+
+ Sold +
8
+
+
+ Logo +
+ +
+
+
+
+ +
+
+
+
+
Company Profile
+
{{user.companyOverview}}
+
    +
  • +
    Name
    +
    {{user.firstname}} {{user.lastname}}
    +
  • +
  • +
    Description
    +
    {{user.description}}
    +
  • +
  • +
    Services we offer
    +
    {{user.offeredServices}}
    +
  • +
  • +
    Areas we serve
    +
    + @for (area of user.areasServed; track area) { + + } + +
    +
  • +
  • +
    My Listings For Sale
    +
    +
    +
    +
    +
    + + PrimeFaces +
    +
    Ultimate UI Component Suite for JavaServer Faces
    +
    +
    +
    +
    +
    + + PrimeNG +
    +
    The Most Complete Angular UI Component Library
    +
    +
    +
    +
    +
    + + PrimeReact +
    +
    Advanced UI Components for ReactJS
    +
    +
    +
    +
    +
    + + PrimeVue +
    +
    The Most Powerful Vue UI Component Library
    +
    +
    +
    +
    +
  • +
+
+
+ } + \ No newline at end of file diff --git a/bizmatch/src/app/pages/details/details.component.ts b/bizmatch/src/app/pages/details/details.component.ts index c5adaa7..256aafb 100644 --- a/bizmatch/src/app/pages/details/details.component.ts +++ b/bizmatch/src/app/pages/details/details.component.ts @@ -18,27 +18,46 @@ 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 { 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 { MessageService } from 'primeng/api'; import { SharedModule } from '../../shared/shared/shared.module'; +import { GalleriaModule } from 'primeng/galleria'; +import { environment } from '../../../environments/environment'; @Component({ selector: 'app-details', standalone: true, - imports: [SharedModule], + imports: [SharedModule,GalleriaModule], providers:[MessageService], templateUrl: './details.component.html', styleUrl: './details.component.scss' }) export class DetailsComponent { // listings: Array; - + responsiveOptions = [ + { + breakpoint: '1199px', + numVisible: 1, + numScroll: 1 + }, + { + breakpoint: '991px', + numVisible: 2, + numScroll: 1 + }, + { + breakpoint: '767px', + numVisible: 1, + numScroll: 1 + } +]; private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined; listing: ListingType; user:User; criteria:ListingCriteria mailinfo: MailInfo; - + propertyImages:ImageProperty[]=[] + environment=environment; constructor(private activatedRoute: ActivatedRoute, private listingsService:ListingsService, private router:Router, @@ -55,6 +74,7 @@ export class DetailsComponent { this.user=user }); this.listing=await lastValueFrom(this.listingsService.getListingById(this.id,this.criteria.listingsCategory)); + this.propertyImages=await this.listingsService.getPropertyImages(this.listing.id) } back(){ this.router.navigate(['listings',this.criteria.listingsCategory]) diff --git a/bizmatch/src/app/pages/listings/listings.component.html b/bizmatch/src/app/pages/listings/listings.component.html index 5c2ec19..06172ef 100644 --- a/bizmatch/src/app/pages/listings/listings.component.html +++ b/bizmatch/src/app/pages/listings/listings.component.html @@ -3,7 +3,7 @@