Rework of major pages

This commit is contained in:
Andreas Knuth 2024-04-24 14:31:32 +02:00
parent 9e03620be7
commit 4230867608
17 changed files with 995 additions and 837 deletions

View File

@ -22,19 +22,19 @@ export class CommercialPropertyListingsController {
// return this.listingsService.findByUserId(userid,commercials); // return this.listingsService.findByUserId(userid,commercials);
// } // }
@Post('search') @Post('search')
find(@Body() criteria: ListingCriteria): any { async find(@Body() criteria: ListingCriteria): Promise<any> {
return this.listingsService.findListingsByCriteria(criteria,commercials); return await this.listingsService.findListingsByCriteria(criteria,commercials);
} }
@Post() @Post()
create(@Body() listing: any){ async create(@Body() listing: any){
this.logger.info(`Save Listing`); this.logger.info(`Save Listing`);
this.listingsService.createListing(listing,commercials) return await this.listingsService.createListing(listing,commercials)
} }
@Put() @Put()
update(@Body() listing: any){ async update(@Body() listing: any){
this.logger.info(`Save Listing`); this.logger.info(`Save Listing`);
this.listingsService.updateListing(listing.id,listing,commercials) return await this.listingsService.updateListing(listing.id,listing,commercials)
} }
@Delete(':id') @Delete(':id')
deleteById(@Param('id') id:string){ deleteById(@Param('id') id:string){

View File

@ -71,12 +71,16 @@ export class ListingsService {
} }
async createListing(data: BusinessListing | CommercialPropertyListing, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> { async createListing(data: BusinessListing | CommercialPropertyListing, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> {
const newListing = { data, created: data.created, updated: data.updated, visits: 0, last_visit: null } data.created=new Date()
const [createdListing] = await this.conn.insert(table).values(newListing).returning(); data.updated=new Date()
data.visits=0;
data.lastVisit=null
const [createdListing] = await this.conn.insert(table).values(data).returning();
return createdListing as BusinessListing | CommercialPropertyListing; return createdListing as BusinessListing | CommercialPropertyListing;
} }
async updateListing(id: string, data: BusinessListing | CommercialPropertyListing, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> { async updateListing(id: string, data: BusinessListing | CommercialPropertyListing, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> {
data.updated=new Date();
const [updateListing] = await this.conn.update(table).set(data).where(eq(table.id, id)).returning(); const [updateListing] = await this.conn.update(table).set(data).where(eq(table.id, id)).returning();
return updateListing as BusinessListing | CommercialPropertyListing; return updateListing as BusinessListing | CommercialPropertyListing;
} }
@ -107,95 +111,5 @@ export class ListingsService {
listing.imageOrder.push(imagename); listing.imageOrder.push(imagename);
await this.updateListing(listing.id, listing, commercials) await this.updateListing(listing.id, listing, commercials)
} }
// async getCommercialPropertyListingById(id: string): Promise<CommercialPropertyListing>{
// return await this.commercialPropertyListingRepository.fetch(id) as unknown as CommercialPropertyListing;
// }
// async getBusinessListingById(id: string) {
// return await this.businessListingRepository.fetch(id)
// }
// async getBusinessListingByUserId(userid:string){
// return await this.businessListingRepository.search().where('userId').equals(userid).return.all()
// }
// async deleteBusinessListing(id: string){
// return await this.businessListingRepository.remove(id);
// }
// async deleteCommercialPropertyListing(id: string){
// return await this.commercialPropertyListingRepository.remove(id);
// }
// async getAllBusinessListings(start?: number, end?: number) {
// return await this.businessListingRepository.search().return.all()
// }
// async getAllCommercialListings(start?: number, end?: number) {
// return await this.commercialPropertyListingRepository.search().return.all()
// }
// async findBusinessListings(criteria:ListingCriteria): Promise<any> {
// // let listings = await this.getAllBusinessListings();
// // return this.find(criteria,listings);
// const from=criteria.start?criteria.start:0
// const size=criteria.length?criteria.length:24
// this.logger.info(`start findBusinessListings: ${JSON.stringify(criteria)}`);
// const result = await this.redis.ft.search('business:index','*',{LIMIT:{from,size}});
// this.logger.info(`start findBusinessListings: ${JSON.stringify(criteria)}`);
// return result
// }
// async findCommercialPropertyListings(criteria:ListingCriteria): Promise<any> {
// let listings = await this.getAllCommercialListings();
// return this.find(criteria,listings);
// }
// async deleteAllBusinessListings(){
// const ids = await this.getIdsForRepo(this.schemaNameBusiness.name);
// this.businessListingRepository.remove(ids);
// }
// async deleteAllcommercialListings(){
// const ids = await this.getIdsForRepo(this.schemaNameCommercial.name);
// this.commercialPropertyListingRepository.remove(ids);
// }
// async getIdsForRepo(repoName:string, maxcount=100000){
// let cursor = 0;
// let ids = [];
// do {
// const reply = await this.redis.scan(cursor, {
// MATCH: `${repoName}:*`,
// COUNT: maxcount
// });
// cursor = reply.cursor;
// // Extrahiere die ID aus jedem Schlüssel und füge sie zur Liste hinzu
// ids = ids.concat(reply.keys.map(key => key.split(':')[1]).filter(id=>id!='index'));
// } while (cursor !== 0);
// return ids;
// }
// async find(criteria:ListingCriteria, listings: any[]): Promise<any> {
// listings=listings.filter(l=>l.listingsCategory===criteria.listingsCategory);
// if (convertStringToNullUndefined(criteria.type)){
// console.log(criteria.type);
// listings=listings.filter(l=>l.type===criteria.type);
// }
// if (convertStringToNullUndefined(criteria.state)){
// console.log(criteria.state);
// listings=listings.filter(l=>l.state===criteria.state);
// }
// if (convertStringToNullUndefined(criteria.minPrice)){
// console.log(criteria.minPrice);
// listings=listings.filter(l=>l.price>=Number(criteria.minPrice));
// }
// if (convertStringToNullUndefined(criteria.maxPrice)){
// console.log(criteria.maxPrice);
// listings=listings.filter(l=>l.price<=Number(criteria.maxPrice));
// }
// if (convertStringToNullUndefined(criteria.realEstateChecked)){
// console.log(criteria.realEstateChecked);
// listings=listings.filter(l=>l.realEstateIncluded);
// }
// if (convertStringToNullUndefined(criteria.category)){
// console.log(criteria.category);
// listings=listings.filter(l=>l.category===criteria.category);
// }
// return listings
// }
} }

View File

@ -36,6 +36,8 @@ export const routes: Routes = [
path: 'home', path: 'home',
component: HomeComponent, component: HomeComponent,
}, },
// #########
// Listings Details
{ {
path: 'details-business-listing/:id', path: 'details-business-listing/:id',
component: DetailsBusinessListingComponent, component: DetailsBusinessListingComponent,
@ -44,15 +46,21 @@ export const routes: Routes = [
path: 'details-commercial-property-listing/:id', path: 'details-commercial-property-listing/:id',
component: DetailsCommercialPropertyListingComponent, component: DetailsCommercialPropertyListingComponent,
}, },
// #########
// User Details
{ {
path: 'details-user/:id', path: 'details-user/:id',
component: DetailsUserComponent, component: DetailsUserComponent,
}, },
// #########
// User edit
{ {
path: 'account', path: 'account',
component: AccountComponent, component: AccountComponent,
canActivate: [authGuard], canActivate: [authGuard],
}, },
// #########
// Create, Update Listings
{ {
path: 'editBusinessListing/:id', path: 'editBusinessListing/:id',
component: EditBusinessListingComponent, component: EditBusinessListingComponent,
@ -73,26 +81,36 @@ export const routes: Routes = [
component: EditCommercialPropertyListingComponent, component: EditCommercialPropertyListingComponent,
canActivate: [authGuard], canActivate: [authGuard],
}, },
// #########
// My Listings
{ {
path: 'myListings', path: 'myListings',
component: MyListingComponent, component: MyListingComponent,
canActivate: [authGuard], canActivate: [authGuard],
}, },
// #########
// My Favorites
{ {
path: 'myFavorites', path: 'myFavorites',
component: FavoritesComponent, component: FavoritesComponent,
canActivate: [authGuard], canActivate: [authGuard],
}, },
// #########
// EMAil Us
{ {
path: 'emailUs', path: 'emailUs',
component: EmailUsComponent, component: EmailUsComponent,
canActivate: [authGuard], canActivate: [authGuard],
}, },
// #########
// Logout
{ {
path: 'logout', path: 'logout',
component: LogoutComponent, component: LogoutComponent,
canActivate: [authGuard], canActivate: [authGuard],
}, },
// #########
// Pricing
{ {
path: 'pricing', path: 'pricing',
component: PricingComponent, component: PricingComponent,

View File

@ -46,7 +46,7 @@ export class HeaderComponent {
{ {
label: 'Create Listing', label: 'Create Listing',
icon: 'pi pi-plus-circle', icon: 'pi pi-plus-circle',
routerLink: '/createListing', routerLink: '/createBusinessListing',
visible: this.isUserLogedIn(), visible: this.isUserLogedIn(),
}, },
{ {
@ -90,13 +90,13 @@ export class HeaderComponent {
fragment: '', fragment: '',
}, },
{ {
label: 'Professionals/Brokers Directory', label: 'Commercial Property',
routerLink: '/brokerListings', routerLink: '/commercialPropertyListings',
fragment: '', fragment: '',
}, },
{ {
label: 'Commercial Property', label: 'Professionals/Brokers Directory',
routerLink: '/commercialPropertyListings', routerLink: '/brokerListings',
fragment: '', fragment: '',
}, },
]; ];

View File

@ -95,7 +95,7 @@
<div class="text-900 w-full md:w-10"> <div class="text-900 w-full md:w-10">
<div class="grid mt-0 mr-0"> <div class="grid mt-0 mr-0">
@for (listing of userListings; track listing) { @for (listing of userListings; track listing) {
<div class="col-12 md:col-6 cursor-pointer" [routerLink]="['/details-listing/business', listing.id]"> <div class="col-12 md:col-6 cursor-pointer" [routerLink]="['/details-business-listing', listing.id]">
<div class="p-3 border-1 surface-border border-round surface-card"> <div class="p-3 border-1 surface-border border-round surface-card">
<div class="text-900 mb-2"> <div class="text-900 mb-2">
<span [class]="selectOptions.getBgColorType(listing.type)" class="inline-flex border-circle align-items-center justify-content-center mr-3" style="width: 38px; height: 38px"> <span [class]="selectOptions.getBgColorType(listing.type)" class="inline-flex border-circle align-items-center justify-content-center mr-3" style="width: 38px; height: 38px">

View File

@ -8,19 +8,34 @@
</a> </a>
</li> </li>
<li> <li>
<a routerLink="/createListing" routerLinkActive="text-blue-500" pRipple class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline"> <a
routerLink="/createBusinessListing"
routerLinkActive="text-blue-500"
pRipple
class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline"
>
<i class="pi pi-plus-circle md:mr-2"></i> <i class="pi pi-plus-circle md:mr-2"></i>
<span class="font-medium hidden md:block">Create Listing</span> <span class="font-medium hidden md:block">Create Listing</span>
</a> </a>
</li> </li>
<li> <li>
<a routerLink="/myListings" routerLinkActive="text-blue-500" pRipple class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline"> <a
routerLink="/myListings"
routerLinkActive="text-blue-500"
pRipple
class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline"
>
<i class="pi pi-list md:mr-2"></i> <i class="pi pi-list md:mr-2"></i>
<span class="font-medium hidden md:block">My Listings</span> <span class="font-medium hidden md:block">My Listings</span>
</a> </a>
</li> </li>
<li> <li>
<a routerLink="/myFavorites" routerLinkActive="text-blue-500" pRipple class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline"> <a
routerLink="/myFavorites"
routerLinkActive="text-blue-500"
pRipple
class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline"
>
<i class="pi pi-star md:mr-2"></i> <i class="pi pi-star md:mr-2"></i>
<span class="font-medium hidden md:block">My Favorites</span> <span class="font-medium hidden md:block">My Favorites</span>
</a> </a>
@ -32,7 +47,12 @@
</a> </a>
</li> </li>
<li> <li>
<a (click)="userService.logout()" routerLinkActive="text-blue-500" pRipple class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline"> <a
(click)="userService.logout()"
routerLinkActive="text-blue-500"
pRipple
class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline"
>
<fa-icon [icon]="faRightFromBracket" class="mr-2 flex"></fa-icon> <fa-icon [icon]="faRightFromBracket" class="mr-2 flex"></fa-icon>
<span class="font-medium hidden md:block">Logout</span> <span class="font-medium hidden md:block">Logout</span>
</a> </a>

View File

@ -9,42 +9,61 @@
<div class="flex-auto p-fluid"> <div class="flex-auto p-fluid">
<div class="mb-4"> <div class="mb-4">
<label for="listingCategory" class="block font-medium text-900 mb-2">Listing category</label> <label for="listingCategory" class="block font-medium text-900 mb-2">Listing category</label>
<p-dropdown id="listingCategory" [options]="selectOptions?.listingCategories" <p-dropdown
[(ngModel)]="listingsCategory" optionLabel="name" optionValue="value" id="listingCategory"
placeholder="Listing category" [disabled]="mode==='edit'" [options]="selectOptions?.listingCategories"
[style]="{ width: '100%'}"></p-dropdown> [ngModel]="listingsCategory"
optionLabel="name"
optionValue="value"
(ngModelChange)="changeListingCategory($event)"
placeholder="Listing category"
[disabled]="mode === 'edit'"
[style]="{ width: '100%' }"
></p-dropdown>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label for="email" class="block font-medium text-900 mb-2">Title of Listing</label> <label for="email" class="block font-medium text-900 mb-2">Title of Listing</label>
<input id="email" type="text" pInputText [(ngModel)]="listing.title"> <input id="email" type="text" pInputText [(ngModel)]="listing.title" />
</div> </div>
<div> <div>
<div class="mb-4"> <div class="mb-4">
<label for="description" class="block font-medium text-900 mb-2">Description</label> <label for="description" class="block font-medium text-900 mb-2">Description</label>
<!-- <textarea id="description" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.description"></textarea> --> <!-- <textarea id="description" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.description"></textarea> -->
<p-editor [(ngModel)]="listing.description" [style]="{ height: '320px' }" <p-editor [(ngModel)]="listing.description" [style]="{ height: '320px' }" [modules]="editorModules">
[modules]="editorModules">
<ng-template pTemplate="header"></ng-template> <ng-template pTemplate="header"></ng-template>
</p-editor> </p-editor>
</div> </div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label for="type" class="block font-medium text-900 mb-2">Type of business</label> <label for="type" class="block font-medium text-900 mb-2">Type of business</label>
<p-dropdown id="type" [options]="selectOptions?.typesOfBusiness" [(ngModel)]="listing.type" <p-dropdown
optionLabel="name" optionValue="value" [showClear]="true" placeholder="Type of business" id="type"
[style]="{ width: '100%'}"></p-dropdown> [options]="selectOptions?.typesOfBusiness"
[(ngModel)]="listing.type"
optionLabel="name"
optionValue="value"
[showClear]="true"
placeholder="Type of business"
[style]="{ width: '100%' }"
></p-dropdown>
</div> </div>
<div class="grid"> <div class="grid">
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="listingCategory" class="block font-medium text-900 mb-2">State</label> <label for="listingCategory" class="block font-medium text-900 mb-2">State</label>
<p-dropdown id="listingCategory" [options]="selectOptions?.states" <p-dropdown
[(ngModel)]="listing.state" optionLabel="name" optionValue="value" [showClear]="true" id="listingCategory"
placeholder="State" [style]="{ width: '100%'}"></p-dropdown> [options]="selectOptions?.states"
[(ngModel)]="listing.state"
optionLabel="name"
optionValue="value"
[showClear]="true"
placeholder="State"
[style]="{ width: '100%' }"
></p-dropdown>
</div> </div>
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="listingCategory" class="block font-medium text-900 mb-2">City</label> <label for="listingCategory" class="block font-medium text-900 mb-2">City</label>
<p-autoComplete [(ngModel)]="listing.city" [suggestions]="suggestions" <p-autoComplete [(ngModel)]="listing.city" [suggestions]="suggestions" (completeMethod)="search($event)"></p-autoComplete>
(completeMethod)="search($event)"></p-autoComplete>
</div> </div>
</div> </div>
</div> </div>
@ -56,34 +75,27 @@
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="price" class="block font-medium text-900 mb-2">Price</label> <label for="price" class="block font-medium text-900 mb-2">Price</label>
<!-- <p-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price" ></p-inputNumber> --> <!-- <p-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price" ></p-inputNumber> -->
<app-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" <app-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price"></app-inputNumber>
[(ngModel)]="listing.price"></app-inputNumber>
</div> </div>
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="salesRevenue" class="block font-medium text-900 mb-2">Sales Revenue</label> <label for="salesRevenue" class="block font-medium text-900 mb-2">Sales Revenue</label>
<app-inputNumber mode="currency" currency="USD" inputId="salesRevenue" <app-inputNumber mode="currency" currency="USD" inputId="salesRevenue" [(ngModel)]="listing.salesRevenue"></app-inputNumber>
[(ngModel)]="listing.salesRevenue"></app-inputNumber>
</div> </div>
</div> </div>
<div class="grid"> <div class="grid">
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="cashFlow" class="block font-medium text-900 mb-2">Cash Flow</label> <label for="cashFlow" class="block font-medium text-900 mb-2">Cash Flow</label>
<app-inputNumber mode="currency" currency="USD" inputId="cashFlow" <app-inputNumber mode="currency" currency="USD" inputId="cashFlow" [(ngModel)]="listing.cashFlow"></app-inputNumber>
[(ngModel)]="listing.cashFlow"></app-inputNumber>
</div> </div>
</div> </div>
<div class="grid"> <div class="grid">
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="employees" class="block font-medium text-900 mb-2">Years Established <label for="employees" class="block font-medium text-900 mb-2">Years Established Since</label>
Since</label> <app-inputNumber mode="decimal" inputId="established" [(ngModel)]="listing.established"></app-inputNumber>
<app-inputNumber mode="decimal" inputId="established"
[(ngModel)]="listing.established"></app-inputNumber>
</div> </div>
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="employees" class="block font-medium text-900 mb-2">Employees</label> <label for="employees" class="block font-medium text-900 mb-2">Employees</label>
<app-inputNumber mode="decimal" inputId="employees" <app-inputNumber mode="decimal" inputId="employees" [(ngModel)]="listing.employees"></app-inputNumber>
[(ngModel)]="listing.employees"></app-inputNumber>
</div> </div>
</div> </div>
<div class="grid"> <div class="grid">
@ -101,33 +113,27 @@
</div> </div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label for="supportAndTraining" class="block font-medium text-900 mb-2">Support & <label for="supportAndTraining" class="block font-medium text-900 mb-2">Support & Training</label>
Training</label>
<!-- <textarea id="inventory" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.supportAndTraining"></textarea> --> <!-- <textarea id="inventory" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.supportAndTraining"></textarea> -->
<input id="supportAndTraining" type="text" pInputText [(ngModel)]="listing.supportAndTraining"> <input id="supportAndTraining" type="text" pInputText [(ngModel)]="listing.supportAndTraining" />
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label for="reasonForSale" class="block font-medium text-900 mb-2">Reason for Sale</label> <label for="reasonForSale" class="block font-medium text-900 mb-2">Reason for Sale</label>
<textarea id="reasonForSale" type="text" pInputTextarea rows="5" [autoResize]="true" <textarea id="reasonForSale" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.reasonForSale"></textarea>
[(ngModel)]="listing.reasonForSale"></textarea>
</div> </div>
<div class="grid"> <div class="grid">
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="brokerLicensing" class="block font-medium text-900 mb-2">Broker <label for="brokerLicensing" class="block font-medium text-900 mb-2">Broker Licensing</label>
Licensing</label> <input id="brokerLicensing" type="text" pInputText [(ngModel)]="listing.brokerLicencing" />
<input id="brokerLicensing" type="text" pInputText [(ngModel)]="listing.brokerLicencing">
</div> </div>
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="internalListingNumber" class="block font-medium text-900 mb-2">Internal Listing <label for="internalListingNumber" class="block font-medium text-900 mb-2">Internal Listing Number</label>
Number</label> <app-inputNumber mode="decimal" inputId="internalListingNumber" type="text" [(ngModel)]="listing.internalListingNumber"></app-inputNumber>
<app-inputNumber mode="decimal" inputId="internalListingNumber" type="text"
[(ngModel)]="listing.internalListingNumber"></app-inputNumber>
</div> </div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label for="internalListing" class="block font-medium text-900 mb-2">Internal Notes (Will not be <label for="internalListing" class="block font-medium text-900 mb-2">Internal Notes (Will not be shown on the listing, for your records only.)</label>
shown on the listing, for your records only.)</label> <input id="internalListing" type="text" pInputText [(ngModel)]="listing.internals" />
<input id="internalListing" type="text" pInputText [(ngModel)]="listing.internals">
</div> </div>
<div class="grid"> <div class="grid">
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
@ -135,8 +141,7 @@
<!-- <p-checkbox [binary]="true" [(ngModel)]="listing.draft"></p-checkbox> --> <!-- <p-checkbox [binary]="true" [(ngModel)]="listing.draft"></p-checkbox> -->
<p-inputSwitch inputId="draft" [(ngModel)]="listing.draft"></p-inputSwitch> <p-inputSwitch inputId="draft" [(ngModel)]="listing.draft"></p-inputSwitch>
<!-- <span class="ml-2 text-900">Share my data with contacts</span> --> <!-- <span class="ml-2 text-900">Share my data with contacts</span> -->
<span class="ml-2 text-900 absolute translate-y-5">Draft Mode (Will not be shown as public <span class="ml-2 text-900 absolute translate-y-5">Draft Mode (Will not be shown as public listing)</span>
listing)</span>
</div> </div>
</div> </div>
<div> <div>

View File

@ -1,98 +1,92 @@
import { Component, ViewChild } from '@angular/core'; import { Component, ViewChild } from '@angular/core';
import { ButtonModule } from 'primeng/button';
import { CheckboxModule } from 'primeng/checkbox';
import { InputTextModule } from 'primeng/inputtext';
import { StyleClassModule } from 'primeng/styleclass';
import { SelectOptionsService } from '../../../services/select-options.service';
import { DropdownModule } from 'primeng/dropdown';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ToggleButtonModule } from 'primeng/togglebutton';
import { TagModule } from 'primeng/tag';
import data from '../../../../assets/data/user.json';
import dataListings from '../../../../assets/data/listings.json';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { ChipModule } from 'primeng/chip';
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
import { DividerModule } from 'primeng/divider';
import { TableModule } from 'primeng/table';
import { createGenericObject, getListingType } from '../../../utils/utils';
import { ListingsService } from '../../../services/listings.service';
import { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { ListingsService } from '../../../services/listings.service';
import { SelectOptionsService } from '../../../services/select-options.service';
import { createGenericObject, getListingType, routeListingWithState } from '../../../utils/utils';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { faTrash } from '@fortawesome/free-solid-svg-icons';
import { AngularCropperjsModule } from 'angular-cropperjs';
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
import { ConfirmationService, MessageService } from 'primeng/api';
import { CarouselModule } from 'primeng/carousel';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { DialogModule } from 'primeng/dialog';
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
import { EditorModule } from 'primeng/editor';
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
import { AutoCompleteCompleteEvent, ImageProperty } from '../../../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../../../environments/environment';
import { InputNumberModule } from '../../../components/inputnumber/inputnumber.component';
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe'; import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
import { GeoService } from '../../../services/geo.service';
import { ImageService } from '../../../services/image.service';
import { LoadingService } from '../../../services/loading.service';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module'; import { SharedModule } from '../../../shared/shared/shared.module';
import { ConfirmationService, MessageService } from 'primeng/api';
import { GeoResult, GeoService } from '../../../services/geo.service';
import { InputNumberComponent, InputNumberModule } from '../../../components/inputnumber/inputnumber.component';
import { environment } from '../../../../environments/environment';
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
import { CarouselModule } from 'primeng/carousel';
import { v4 as uuidv4 } from 'uuid';
import { DialogModule } from 'primeng/dialog';
import { AngularCropperjsModule, CropperComponent } from 'angular-cropperjs';
import { HttpClient, HttpEventType } from '@angular/common/http';
import { ImageService } from '../../../services/image.service'
import { LoadingService } from '../../../services/loading.service';
import { TOOLBAR_OPTIONS } from '../../utils/defaults'; import { TOOLBAR_OPTIONS } from '../../utils/defaults';
import { EditorModule } from 'primeng/editor';
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
import { ImageCropperComponent } from '../../../components/image-cropper/image-cropper.component';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { CdkDragDrop, CdkDragEnter, CdkDragExit, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
import { faTrash } from '@fortawesome/free-solid-svg-icons';
import { AutoCompleteCompleteEvent, ImageProperty, ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
@Component({ @Component({
selector: 'create-listing', selector: 'business-listing',
standalone: true, standalone: true,
imports: [SharedModule, ArrayToStringPipe, InputNumberModule, CarouselModule, imports: [
DialogModule, AngularCropperjsModule, FileUploadModule, EditorModule, DynamicDialogModule, DragDropModule, SharedModule,
ConfirmDialogModule, MixedCdkDragDropModule], ArrayToStringPipe,
InputNumberModule,
CarouselModule,
DialogModule,
AngularCropperjsModule,
FileUploadModule,
EditorModule,
DynamicDialogModule,
DragDropModule,
ConfirmDialogModule,
MixedCdkDragDropModule,
],
providers: [MessageService, DialogService, ConfirmationService], providers: [MessageService, DialogService, ConfirmationService],
templateUrl: './edit-business-listing.component.html', templateUrl: './edit-business-listing.component.html',
styleUrl: './edit-business-listing.component.scss' styleUrl: './edit-business-listing.component.scss',
}) })
export class EditBusinessListingComponent { export class EditBusinessListingComponent {
@ViewChild(FileUpload) public fileUpload: FileUpload; @ViewChild(FileUpload) public fileUpload: FileUpload;
listingsCategory = 'commercialProperty' listingsCategory = 'business';
category: string; category: string;
location: string; location: string;
mode: 'edit' | 'create'; mode: 'edit' | 'create';
separator: '\n\n' separator: '\n\n';
listing: BusinessListing listing: BusinessListing;
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined; private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
user: User; user: User;
maxFileSize = 3000000; maxFileSize = 3000000;
uploadUrl: string; uploadUrl: string;
environment = environment; environment = environment;
propertyImages: ImageProperty[] propertyImages: ImageProperty[];
responsiveOptions = [ responsiveOptions = [
{ {
breakpoint: '1199px', breakpoint: '1199px',
numVisible: 1, numVisible: 1,
numScroll: 1 numScroll: 1,
}, },
{ {
breakpoint: '991px', breakpoint: '991px',
numVisible: 2, numVisible: 2,
numScroll: 1 numScroll: 1,
}, },
{ {
breakpoint: '767px', breakpoint: '767px',
numVisible: 1, numVisible: 1,
numScroll: 1 numScroll: 1,
} },
]; ];
config = { aspectRatio: 16 / 9 } config = { aspectRatio: 16 / 9 };
editorModules = TOOLBAR_OPTIONS editorModules = TOOLBAR_OPTIONS;
dialogRef: DynamicDialogRef | undefined; dialogRef: DynamicDialogRef | undefined;
draggedImage: ImageProperty draggedImage: ImageProperty;
faTrash = faTrash; faTrash = faTrash;
constructor(public selectOptions: SelectOptionsService, data: CommercialPropertyListing;
constructor(
public selectOptions: SelectOptionsService,
private router: Router, private router: Router,
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private listingsService: ListingsService, private listingsService: ListingsService,
@ -102,106 +96,48 @@ export class EditBusinessListingComponent {
private imageService: ImageService, private imageService: ImageService,
private loadingService: LoadingService, private loadingService: LoadingService,
public dialogService: DialogService, public dialogService: DialogService,
private confirmationService: ConfirmationService) { private confirmationService: ConfirmationService,
this.user = this.userService.getUser(); private route: ActivatedRoute,
// Abonniere Router-Events, um den aktiven Link zu ermitteln ) {
this.router.events.subscribe(event => { this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) { if (event instanceof NavigationEnd) {
this.mode = event.url === '/createListing' ? 'create' : 'edit'; this.mode = event.url === '/createBusinessListing' ? 'create' : 'edit';
}
});
this.route.data.subscribe(async () => {
if (this.router.getCurrentNavigation().extras.state) {
this.data = this.router.getCurrentNavigation().extras.state['data'];
} }
}); });
} }
async ngOnInit() { async ngOnInit() {
if (this.mode === 'edit') { if (this.mode === 'edit') {
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id)); this.listing = await lastValueFrom(this.listingsService.getListingById(this.id));
} else { } else {
const uuid = sessionStorage.getItem('uuid') ? sessionStorage.getItem('uuid') : uuidv4();
sessionStorage.setItem('uuid', uuid);
this.listing = createGenericObject<BusinessListing>(); this.listing = createGenericObject<BusinessListing>();
this.listing.id = uuid this.listing.listingsCategory = 'business';
this.listing.userId = this.user.id this.listing.userId = await this.userService.getId();
this.listing.title = this.data?.title;
this.listing.description = this.data?.description;
} }
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`; this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`;
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id) this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
} }
async save() { async save() {
sessionStorage.removeItem('uuid') this.listing = await this.listingsService.save(this.listing, getListingType(this.listing));
await this.listingsService.save(this.listing, getListingType(this.listing)); this.router.navigate(['editBusinessListing', this.listing.id]);
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 }); this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 });
} }
suggestions: string[] | undefined; suggestions: string[] | undefined;
async search(event: AutoCompleteCompleteEvent) { async search(event: AutoCompleteCompleteEvent) {
const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query, this.listing.state)) const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query, this.listing.state));
this.suggestions = result.map(r => r.city).slice(0, 5); this.suggestions = result.map(r => r.city).slice(0, 5);
} }
select(event: any) { changeListingCategory(value: 'business' | 'commercialProperty') {
const imageUrl = URL.createObjectURL(event.files[0]); routeListingWithState(this.router, value, this.listing);
this.dialogRef = this.dialogService.open(ImageCropperComponent, {
data: {
imageUrl: imageUrl,
fileUpload: this.fileUpload,
ratioVariable: false
},
header: 'Edit Image',
width: '50vw',
modal: true,
closeOnEscape: true,
keepInViewport: true,
closable: false,
breakpoints: {
'960px': '75vw',
'640px': '90vw'
},
});
this.dialogRef.onClose.subscribe(cropper => {
if (cropper){
this.loadingService.startLoading('uploadImage');
cropper.getCroppedCanvas().toBlob(async (blob) => {
this.imageService.uploadImage(blob, 'uploadPropertyPicture', this.listing.id).subscribe(async (event) => {
if (event.type === HttpEventType.Response) {
console.log('Upload abgeschlossen', event.body);
this.loadingService.stopLoading('uploadImage');
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
} }
}, error => console.error('Fehler beim Upload:', error));
}, 'image/jpg');
cropper.destroy();
}
})
}
deleteConfirm(imageName: string) {
this.confirmationService.confirm({
target: event.target as EventTarget,
message: `Do you want to delete this image ${imageName}?`,
header: 'Delete Confirmation',
icon: 'pi pi-info-circle',
acceptButtonStyleClass: "p-button-danger p-button-text",
rejectButtonStyleClass: "p-button-text p-button-text",
acceptIcon: "none",
rejectIcon: "none",
accept: async () => {
await this.imageService.deleteListingImage(this.listing.id, imageName);
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Image deleted' });
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
},
reject: () => {
// this.messageService.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected' });
console.log('deny')
}
});
}
onDrop(event: { previousIndex: number; currentIndex: number }) {
moveItemInArray(this.propertyImages, event.previousIndex, event.currentIndex);
this.listingsService.changeImageOrder(this.listing.id, this.propertyImages)
}
} }

View File

@ -9,52 +9,62 @@
<div class="flex-auto p-fluid"> <div class="flex-auto p-fluid">
<div class="mb-4"> <div class="mb-4">
<label for="listingCategory" class="block font-medium text-900 mb-2">Listing category</label> <label for="listingCategory" class="block font-medium text-900 mb-2">Listing category</label>
<p-dropdown id="listingCategory" [options]="selectOptions?.listingCategories" <p-dropdown
[(ngModel)]="listingsCategory" optionLabel="name" optionValue="value" id="listingCategory"
placeholder="Listing category" [disabled]="mode==='edit'" [options]="selectOptions?.listingCategories"
[style]="{ width: '100%'}"></p-dropdown> [ngModel]="listingsCategory"
optionLabel="name"
optionValue="value"
(ngModelChange)="changeListingCategory($event)"
placeholder="Listing category"
[disabled]="mode === 'edit'"
[style]="{ width: '100%' }"
></p-dropdown>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label for="email" class="block font-medium text-900 mb-2">Title of Listing</label> <label for="email" class="block font-medium text-900 mb-2">Title of Listing</label>
<input id="email" type="text" pInputText [(ngModel)]="listing.title"> <input id="email" type="text" pInputText [(ngModel)]="listing.title" />
</div> </div>
<div> <div>
<div class="mb-4"> <div class="mb-4">
<label for="description" class="block font-medium text-900 mb-2">Description</label> <label for="description" class="block font-medium text-900 mb-2">Description</label>
<!-- <textarea id="description" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.description"></textarea> --> <!-- <textarea id="description" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.description"></textarea> -->
<p-editor [(ngModel)]="listing.description" [style]="{ height: '320px' }" <p-editor [(ngModel)]="listing.description" [style]="{ height: '320px' }" [modules]="editorModules">
[modules]="editorModules">
<ng-template pTemplate="header"></ng-template> <ng-template pTemplate="header"></ng-template>
</p-editor> </p-editor>
</div> </div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label for="type" class="block font-medium text-900 mb-2">Property Category</label> <label for="type" class="block font-medium text-900 mb-2">Property Category</label>
<p-dropdown id="type" [options]="selectOptions?.typesOfCommercialProperty" <p-dropdown
[(ngModel)]="listing.type" optionLabel="name" optionValue="value" [showClear]="true" id="type"
placeholder="Property Category" [style]="{ width: '100%'}"></p-dropdown> [options]="selectOptions?.typesOfCommercialProperty"
[(ngModel)]="listing.type"
optionLabel="name"
optionValue="value"
[showClear]="true"
placeholder="Property Category"
[style]="{ width: '100%' }"
></p-dropdown>
</div> </div>
<div class="grid"> <div class="grid">
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="states" class="block font-medium text-900 mb-2">State</label> <label for="states" class="block font-medium text-900 mb-2">State</label>
<p-dropdown id="states" [options]="selectOptions?.states" <p-dropdown id="states" [options]="selectOptions?.states" [(ngModel)]="listing.state" optionLabel="name" optionValue="value" [showClear]="true" placeholder="State" [style]="{ width: '100%' }"></p-dropdown>
[(ngModel)]="listing.state" optionLabel="name" optionValue="value" [showClear]="true"
placeholder="State" [style]="{ width: '100%'}"></p-dropdown>
</div> </div>
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="city" class="block font-medium text-900 mb-2">City</label> <label for="city" class="block font-medium text-900 mb-2">City</label>
<p-autoComplete id="city" [(ngModel)]="listing.city" [suggestions]="suggestions" <p-autoComplete id="city" [(ngModel)]="listing.city" [suggestions]="suggestions" (completeMethod)="search($event)"></p-autoComplete>
(completeMethod)="search($event)"></p-autoComplete>
</div> </div>
</div> </div>
<div class="grid"> <div class="grid">
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="zipCode" class="block font-medium text-900 mb-2">Zip Code</label> <label for="zipCode" class="block font-medium text-900 mb-2">Zip Code</label>
<input id="zipCode" type="text" pInputText [(ngModel)]="listing.zipCode"> <input id="zipCode" type="text" pInputText [(ngModel)]="listing.zipCode" />
</div> </div>
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="county" class="block font-medium text-900 mb-2">County</label> <label for="county" class="block font-medium text-900 mb-2">County</label>
<input id="county" type="text" pInputText [(ngModel)]="listing.county"> <input id="county" type="text" pInputText [(ngModel)]="listing.county" />
</div> </div>
</div> </div>
</div> </div>
@ -66,34 +76,40 @@
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<label for="price" class="block font-medium text-900 mb-2">Price</label> <label for="price" class="block font-medium text-900 mb-2">Price</label>
<!-- <p-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price" ></p-inputNumber> --> <!-- <p-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price" ></p-inputNumber> -->
<app-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" <app-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price"></app-inputNumber>
[(ngModel)]="listing.price"></app-inputNumber>
</div> </div>
<div class="mb-4 col-12 md:col-6"> <div class="mb-4 col-12 md:col-6">
<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">Property Pictures</span> <span class="font-medium text-900 mb-2">Property Pictures</span>
<span class="font-light text-sm text-900 mb-2">(Pictures can be uploaded once the listing is posted initially)</span>
<!-- <img [src]="propertyPictureUrl" (error)="setImageToFallback($event)" class="image"/> --> <!-- <img [src]="propertyPictureUrl" (error)="setImageToFallback($event)" class="image"/> -->
<p-fileUpload mode="basic" chooseLabel="Upload" [customUpload]="true" name="file" <p-fileUpload
accept="image/*" [maxFileSize]="maxFileSize" (onSelect)="select($event)" mode="basic"
styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"> chooseLabel="Upload"
[customUpload]="true"
name="file"
accept="image/*"
[maxFileSize]="maxFileSize"
(onSelect)="select($event)"
styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"
[disabled]="true"
>
</p-fileUpload> </p-fileUpload>
</div> </div>
</div> </div>
</div> </div>
<div class="p-2 border-1 surface-border border-round mb-4 image-container" cdkDropListGroup @if (propertyImages?.length>0){
mixedCdkDragDrop (dropped)="onDrop($event)" cdkDropListOrientation="horizontal"> <div class="p-2 border-1 surface-border border-round mb-4 image-container" cdkDropListGroup mixedCdkDragDrop (dropped)="onDrop($event)" cdkDropListOrientation="horizontal">
@for (image of propertyImages; track image) { @for (image of propertyImages; track image) {
<span cdkDropList mixedCdkDropList> <span cdkDropList mixedCdkDropList>
<div cdkDrag mixedCdkDragSizeHelper class="image-wrap"> <div cdkDrag mixedCdkDragSizeHelper class="image-wrap">
<img src="{{environment.apiBaseUrl}}/property/{{listing.id}}/{{image.name}}" <img src="{{ environment.apiBaseUrl }}/property/{{ listing.id }}/{{ image.name }}" [alt]="image.name" class="shadow-2" cdkDrag />
[alt]="image.name" class="shadow-2" cdkDrag>
<fa-icon [icon]="faTrash" (click)="deleteConfirm(image.name)"></fa-icon> <fa-icon [icon]="faTrash" (click)="deleteConfirm(image.name)"></fa-icon>
</div> </div>
</span> </span>
} }
</div> </div>
}
<div> <div>
@if (mode==='create'){ @if (mode==='create'){
<button pButton pRipple label="Post Listing" class="w-auto" (click)="save()"></button> <button pButton pRipple label="Post Listing" class="w-auto" (click)="save()"></button>

View File

@ -1,98 +1,96 @@
import { Component, ViewChild } from '@angular/core'; import { Component, ViewChild } from '@angular/core';
import { ButtonModule } from 'primeng/button';
import { CheckboxModule } from 'primeng/checkbox';
import { InputTextModule } from 'primeng/inputtext';
import { StyleClassModule } from 'primeng/styleclass';
import { SelectOptionsService } from '../../../services/select-options.service';
import { DropdownModule } from 'primeng/dropdown';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ToggleButtonModule } from 'primeng/togglebutton';
import { TagModule } from 'primeng/tag';
import data from '../../../../assets/data/user.json';
import dataListings from '../../../../assets/data/listings.json';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { ChipModule } from 'primeng/chip';
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
import { DividerModule } from 'primeng/divider';
import { TableModule } from 'primeng/table';
import { createGenericObject, getListingType } from '../../../utils/utils';
import { ListingsService } from '../../../services/listings.service';
import { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { ListingsService } from '../../../services/listings.service';
import { SelectOptionsService } from '../../../services/select-options.service';
import { createGenericObject, getListingType, routeListingWithState } from '../../../utils/utils';
import { DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
import { HttpEventType } from '@angular/common/http';
import { faTrash } from '@fortawesome/free-solid-svg-icons';
import { AngularCropperjsModule } from 'angular-cropperjs';
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
import { ConfirmationService, MessageService } from 'primeng/api';
import { CarouselModule } from 'primeng/carousel';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { DialogModule } from 'primeng/dialog';
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
import { EditorModule } from 'primeng/editor';
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
import { AutoCompleteCompleteEvent, ImageProperty } from '../../../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../../../environments/environment';
import { ImageCropperComponent } from '../../../components/image-cropper/image-cropper.component';
import { InputNumberModule } from '../../../components/inputnumber/inputnumber.component';
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe'; import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
import { GeoService } from '../../../services/geo.service';
import { ImageService } from '../../../services/image.service';
import { LoadingService } from '../../../services/loading.service';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module'; import { SharedModule } from '../../../shared/shared/shared.module';
import { ConfirmationService, MessageService } from 'primeng/api';
import { GeoResult, GeoService } from '../../../services/geo.service';
import { InputNumberComponent, InputNumberModule } from '../../../components/inputnumber/inputnumber.component';
import { environment } from '../../../../environments/environment';
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
import { CarouselModule } from 'primeng/carousel';
import { v4 as uuidv4 } from 'uuid';
import { DialogModule } from 'primeng/dialog';
import { AngularCropperjsModule, CropperComponent } from 'angular-cropperjs';
import { HttpClient, HttpEventType } from '@angular/common/http';
import { ImageService } from '../../../services/image.service'
import { LoadingService } from '../../../services/loading.service';
import { TOOLBAR_OPTIONS } from '../../utils/defaults'; import { TOOLBAR_OPTIONS } from '../../utils/defaults';
import { EditorModule } from 'primeng/editor';
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
import { ImageCropperComponent } from '../../../components/image-cropper/image-cropper.component';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { CdkDragDrop, CdkDragEnter, CdkDragExit, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
import { faTrash } from '@fortawesome/free-solid-svg-icons';
import { AutoCompleteCompleteEvent, ImageProperty, ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
@Component({ @Component({
selector: 'create-listing', selector: 'commercial-property-listing',
standalone: true, standalone: true,
imports: [SharedModule, ArrayToStringPipe, InputNumberModule, CarouselModule, imports: [
DialogModule, AngularCropperjsModule, FileUploadModule, EditorModule, DynamicDialogModule, DragDropModule, SharedModule,
ConfirmDialogModule, MixedCdkDragDropModule], ArrayToStringPipe,
InputNumberModule,
CarouselModule,
DialogModule,
AngularCropperjsModule,
FileUploadModule,
EditorModule,
DynamicDialogModule,
DragDropModule,
ConfirmDialogModule,
MixedCdkDragDropModule,
],
providers: [MessageService, DialogService, ConfirmationService], providers: [MessageService, DialogService, ConfirmationService],
templateUrl: './edit-commercial-property-listing.component.html', templateUrl: './edit-commercial-property-listing.component.html',
styleUrl: './edit-commercial-property-listing.component.scss' styleUrl: './edit-commercial-property-listing.component.scss',
}) })
export class EditCommercialPropertyListingComponent { export class EditCommercialPropertyListingComponent {
@ViewChild(FileUpload) public fileUpload: FileUpload; @ViewChild(FileUpload) public fileUpload: FileUpload;
listingsCategory = 'commercialProperty' listingsCategory = 'commercialProperty';
category: string; category: string;
location: string; location: string;
mode: 'edit' | 'create'; mode: 'edit' | 'create';
separator: '\n\n' separator: '\n\n';
listing: CommercialPropertyListing listing: CommercialPropertyListing;
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined; private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
user: User; user: User;
maxFileSize = 3000000; maxFileSize = 3000000;
uploadUrl: string; uploadUrl: string;
environment = environment; environment = environment;
propertyImages: ImageProperty[] propertyImages: ImageProperty[];
responsiveOptions = [ responsiveOptions = [
{ {
breakpoint: '1199px', breakpoint: '1199px',
numVisible: 1, numVisible: 1,
numScroll: 1 numScroll: 1,
}, },
{ {
breakpoint: '991px', breakpoint: '991px',
numVisible: 2, numVisible: 2,
numScroll: 1 numScroll: 1,
}, },
{ {
breakpoint: '767px', breakpoint: '767px',
numVisible: 1, numVisible: 1,
numScroll: 1 numScroll: 1,
} },
]; ];
config = { aspectRatio: 16 / 9 } config = { aspectRatio: 16 / 9 };
editorModules = TOOLBAR_OPTIONS editorModules = TOOLBAR_OPTIONS;
dialogRef: DynamicDialogRef | undefined; dialogRef: DynamicDialogRef | undefined;
draggedImage: ImageProperty draggedImage: ImageProperty;
faTrash = faTrash; faTrash = faTrash;
constructor(public selectOptions: SelectOptionsService, suggestions: string[] | undefined;
data: BusinessListing;
userId: string;
constructor(
public selectOptions: SelectOptionsService,
private router: Router, private router: Router,
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private listingsService: ListingsService, private listingsService: ListingsService,
@ -102,40 +100,43 @@ export class EditCommercialPropertyListingComponent {
private imageService: ImageService, private imageService: ImageService,
private loadingService: LoadingService, private loadingService: LoadingService,
public dialogService: DialogService, public dialogService: DialogService,
private confirmationService: ConfirmationService) { private confirmationService: ConfirmationService,
this.user = this.userService.getUser(); private route: ActivatedRoute,
) {
// this.user = this.userService.getUser();
// Abonniere Router-Events, um den aktiven Link zu ermitteln // Abonniere Router-Events, um den aktiven Link zu ermitteln
this.router.events.subscribe(event => { this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) { if (event instanceof NavigationEnd) {
this.mode = event.url === '/createListing' ? 'create' : 'edit'; this.mode = event.url === '/createCommercialPropertyListing' ? 'create' : 'edit';
}
});
this.route.data.subscribe(async () => {
if (this.router.getCurrentNavigation().extras.state) {
this.data = this.router.getCurrentNavigation().extras.state['data'];
} }
}); });
} }
async ngOnInit() { async ngOnInit() {
if (this.mode === 'edit') { if (this.mode === 'edit') {
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id)); this.listing = await lastValueFrom(this.listingsService.getListingById(this.id));
} else { } else {
const uuid = sessionStorage.getItem('uuid') ? sessionStorage.getItem('uuid') : uuidv4();
sessionStorage.setItem('uuid', uuid);
this.listing = createGenericObject<CommercialPropertyListing>(); this.listing = createGenericObject<CommercialPropertyListing>();
this.listing.id = uuid this.listing.userId = await this.userService.getId();
this.listing.userId = this.user.id this.listing.title = this.data?.title;
this.listing.description = this.data?.description;
} }
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`; this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`;
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id) this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
} }
async save() { async save() {
sessionStorage.removeItem('uuid') this.listing = await this.listingsService.save(this.listing, getListingType(this.listing));
await this.listingsService.save(this.listing, getListingType(this.listing)); this.router.navigate(['editCommercialPropertyListing', this.listing.id]);
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 }); this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 });
} }
suggestions: string[] | undefined;
async search(event: AutoCompleteCompleteEvent) { async search(event: AutoCompleteCompleteEvent) {
const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query, this.listing.state)) const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query, this.listing.state));
this.suggestions = result.map(r => r.city).slice(0, 5); this.suggestions = result.map(r => r.city).slice(0, 5);
} }
@ -145,7 +146,7 @@ export class EditCommercialPropertyListingComponent {
data: { data: {
imageUrl: imageUrl, imageUrl: imageUrl,
fileUpload: this.fileUpload, fileUpload: this.fileUpload,
ratioVariable: false ratioVariable: false,
}, },
header: 'Edit Image', header: 'Edit Image',
width: '50vw', width: '50vw',
@ -155,24 +156,27 @@ export class EditCommercialPropertyListingComponent {
closable: false, closable: false,
breakpoints: { breakpoints: {
'960px': '75vw', '960px': '75vw',
'640px': '90vw' '640px': '90vw',
}, },
}); });
this.dialogRef.onClose.subscribe(cropper => { this.dialogRef.onClose.subscribe(cropper => {
if (cropper) { if (cropper) {
this.loadingService.startLoading('uploadImage'); this.loadingService.startLoading('uploadImage');
cropper.getCroppedCanvas().toBlob(async (blob) => { cropper.getCroppedCanvas().toBlob(async blob => {
this.imageService.uploadImage(blob, 'uploadPropertyPicture', this.listing.id).subscribe(async (event) => { this.imageService.uploadImage(blob, 'uploadPropertyPicture', this.listing.id).subscribe(
async event => {
if (event.type === HttpEventType.Response) { if (event.type === HttpEventType.Response) {
console.log('Upload abgeschlossen', event.body); console.log('Upload abgeschlossen', event.body);
this.loadingService.stopLoading('uploadImage'); this.loadingService.stopLoading('uploadImage');
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id) this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
} }
}, error => console.error('Fehler beim Upload:', error)); },
error => console.error('Fehler beim Upload:', error),
);
}, 'image/jpg'); }, 'image/jpg');
cropper.destroy(); cropper.destroy();
} }
}) });
} }
deleteConfirm(imageName: string) { deleteConfirm(imageName: string) {
@ -181,27 +185,28 @@ export class EditCommercialPropertyListingComponent {
message: `Do you want to delete this image ${imageName}?`, message: `Do you want to delete this image ${imageName}?`,
header: 'Delete Confirmation', header: 'Delete Confirmation',
icon: 'pi pi-info-circle', icon: 'pi pi-info-circle',
acceptButtonStyleClass: "p-button-danger p-button-text", acceptButtonStyleClass: 'p-button-danger p-button-text',
rejectButtonStyleClass: "p-button-text p-button-text", rejectButtonStyleClass: 'p-button-text p-button-text',
acceptIcon: "none", acceptIcon: 'none',
rejectIcon: "none", rejectIcon: 'none',
accept: async () => { accept: async () => {
await this.imageService.deleteListingImage(this.listing.id, imageName); await this.imageService.deleteListingImage(this.listing.id, imageName);
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Image deleted' }); this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Image deleted' });
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id) this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
}, },
reject: () => { reject: () => {
// this.messageService.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected' }); // this.messageService.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected' });
console.log('deny') console.log('deny');
} },
}); });
} }
onDrop(event: { previousIndex: number; currentIndex: number }) { onDrop(event: { previousIndex: number; currentIndex: number }) {
moveItemInArray(this.propertyImages, event.previousIndex, event.currentIndex); moveItemInArray(this.propertyImages, event.previousIndex, event.currentIndex);
this.listingsService.changeImageOrder(this.listing.id, this.propertyImages) this.listingsService.changeImageOrder(this.listing.id, this.propertyImages);
}
changeListingCategory(value: 'business' | 'commercialProperty') {
routeListingWithState(this.router, value, this.listing);
} }
} }

View File

@ -0,0 +1,216 @@
<!-- <div class="surface-ground px-4 py-8 md:px-6 lg:px-8">
<div class="p-fluid flex flex-column lg:flex-row">
<menu-account></menu-account>
<p-toast></p-toast>
<div *ngIf="listing" class="surface-card p-5 shadow-2 border-round flex-auto">
<div class="text-900 font-semibold text-lg mt-3">{{ mode === 'create' ? 'New' : 'Edit' }} Listing</div>
<p-divider></p-divider>
<div class="flex gap-5 flex-column-reverse md:flex-row">
<div class="flex-auto p-fluid">
<div class="mb-4">
<label for="listingCategory" class="block font-medium text-900 mb-2">Listing category</label>
<p-dropdown
id="listingCategory"
[options]="selectOptions?.listingCategories"
[(ngModel)]="listing.listingsCategory"
optionLabel="name"
optionValue="value"
placeholder="Listing category"
[disabled]="mode === 'edit'"
[style]="{ width: '100%' }"
></p-dropdown>
</div>
<div class="mb-4">
<label for="email" class="block font-medium text-900 mb-2">Title of Listing</label>
<input id="email" type="text" pInputText [(ngModel)]="listing.title" />
</div>
<div>
<div class="mb-4">
<label for="description" class="block font-medium text-900 mb-2">Description</label>
<p-editor [(ngModel)]="listing.description" [style]="{ height: '320px' }" [modules]="editorModules">
<ng-template pTemplate="header"></ng-template>
</p-editor>
</div>
</div>
@if (listing.listingsCategory==='business'){
<div class="mb-4">
<label for="type" class="block font-medium text-900 mb-2">Type of business</label>
<p-dropdown
id="type"
[options]="selectOptions?.typesOfBusiness"
[(ngModel)]="listing.type"
optionLabel="name"
optionValue="value"
[showClear]="true"
placeholder="Type of business"
[style]="{ width: '100%' }"
></p-dropdown>
</div>
} @if (listing.listingsCategory==='commercialProperty'){
<div class="mb-4">
<label for="type" class="block font-medium text-900 mb-2">Property Category</label>
<p-dropdown
id="type"
[options]="selectOptions?.typesOfCommercialProperty"
[(ngModel)]="listing.type"
optionLabel="name"
optionValue="value"
[showClear]="true"
placeholder="Property Category"
[style]="{ width: '100%' }"
></p-dropdown>
</div>
}
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="listingCategory" class="block font-medium text-900 mb-2">State</label>
<p-dropdown
id="listingCategory"
[options]="selectOptions?.states"
[(ngModel)]="listing.state"
optionLabel="name"
optionValue="value"
[showClear]="true"
placeholder="State"
[style]="{ width: '100%' }"
></p-dropdown>
</div>
<div class="mb-4 col-12 md:col-6">
<label for="listingCategory" class="block font-medium text-900 mb-2">City</label>
<p-autoComplete [(ngModel)]="listing.city" [suggestions]="suggestions" (completeMethod)="search($event)"></p-autoComplete>
</div>
</div>
@if (listing.listingsCategory==='commercialProperty'){
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="zipCode" class="block font-medium text-900 mb-2">Zip Code</label>
<input id="zipCode" type="text" pInputText [(ngModel)]="listing.zipCode" />
</div>
<div class="mb-4 col-12 md:col-6">
<label for="county" class="block font-medium text-900 mb-2">County</label>
<input id="county" type="text" pInputText [(ngModel)]="listing.county" />
</div>
</div>
}
</div>
</div>
<p-divider></p-divider>
<div class="flex gap-5 flex-column-reverse md:flex-row">
<div class="flex-auto p-fluid">
@if (listing.listingsCategory==='commercialProperty'){
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="price" class="block font-medium text-900 mb-2">Price</label>
<app-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price"></app-inputNumber>
</div>
<div class="mb-4 col-12 md:col-6">
<div class="flex flex-column align-items-center flex-or">
<span class="font-medium text-900 mb-2">Property Pictures</span>
<p-fileUpload
mode="basic"
chooseLabel="Upload"
[customUpload]="true"
name="file"
accept="image/*"
[maxFileSize]="maxFileSize"
(onSelect)="select($event)"
styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"
>
</p-fileUpload>
</div>
</div>
</div>
<div class="p-2 border-1 surface-border border-round mb-4 image-container" cdkDropListGroup mixedCdkDragDrop (dropped)="onDrop($event)" cdkDropListOrientation="horizontal">
@for (image of propertyImages; track image) {
<span cdkDropList mixedCdkDropList>
<div cdkDrag mixedCdkDragSizeHelper class="image-wrap">
<img src="{{ environment.apiBaseUrl }}/property/{{ listing.id }}/{{ image.name }}" [alt]="image.name" class="shadow-2" cdkDrag />
<fa-icon [icon]="faTrash" (click)="deleteConfirm(image.name)"></fa-icon>
</div>
</span>
}
</div>
} @if (listing.listingsCategory==='business'){
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="price" class="block font-medium text-900 mb-2">Price</label>
<app-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price"></app-inputNumber>
</div>
<div class="mb-4 col-12 md:col-6">
<label for="salesRevenue" class="block font-medium text-900 mb-2">Sales Revenue</label>
<app-inputNumber mode="currency" currency="USD" inputId="salesRevenue" [(ngModel)]="listing.salesRevenue"></app-inputNumber>
</div>
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="cashFlow" class="block font-medium text-900 mb-2">Cash Flow</label>
<app-inputNumber mode="currency" currency="USD" inputId="cashFlow" [(ngModel)]="listing.cashFlow"></app-inputNumber>
</div>
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="employees" class="block font-medium text-900 mb-2">Years Established Since</label>
<app-inputNumber mode="decimal" inputId="established" [(ngModel)]="listing.established"></app-inputNumber>
</div>
<div class="mb-4 col-12 md:col-6">
<label for="employees" class="block font-medium text-900 mb-2">Employees</label>
<app-inputNumber mode="decimal" inputId="employees" [(ngModel)]="listing.employees"></app-inputNumber>
</div>
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-4">
<p-checkbox [binary]="true" [(ngModel)]="listing.realEstateIncluded"></p-checkbox>
<span class="ml-2 text-900">Real Estate Included</span>
</div>
<div class="mb-4 col-12 md:col-4">
<p-checkbox [binary]="true" [(ngModel)]="listing.leasedLocation"></p-checkbox>
<span class="ml-2 text-900">Leased Location</span>
</div>
<div class="mb-4 col-12 md:col-4">
<p-checkbox [binary]="true" [(ngModel)]="listing.franchiseResale"></p-checkbox>
<span class="ml-2 text-900">Franchise Re-Sale</span>
</div>
</div>
<div class="mb-4">
<label for="supportAndTraining" class="block font-medium text-900 mb-2">Support & Training</label>
<input id="supportAndTraining" type="text" pInputText [(ngModel)]="listing.supportAndTraining" />
</div>
<div class="mb-4">
<label for="reasonForSale" class="block font-medium text-900 mb-2">Reason for Sale</label>
<textarea id="reasonForSale" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.reasonForSale"></textarea>
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="brokerLicensing" class="block font-medium text-900 mb-2">Broker Licensing</label>
<input id="brokerLicensing" type="text" pInputText [(ngModel)]="listing.brokerLicencing" />
</div>
<div class="mb-4 col-12 md:col-6">
<label for="internalListingNumber" class="block font-medium text-900 mb-2">Internal Listing Number</label>
<app-inputNumber mode="decimal" inputId="internalListingNumber" type="text" [(ngModel)]="listing.internalListingNumber"></app-inputNumber>
</div>
</div>
<div class="mb-4">
<label for="internalListing" class="block font-medium text-900 mb-2">Internal Notes (Will not be shown on the listing, for your records only.)</label>
<input id="internalListing" type="text" pInputText [(ngModel)]="listing.internals" />
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<p-inputSwitch inputId="draft" [(ngModel)]="listing.draft"></p-inputSwitch>
<span class="ml-2 text-900 absolute translate-y-5">Draft Mode (Will not be shown as public listing)</span>
</div>
</div>
}
<div>
@if (mode==='create'){
<button pButton pRipple label="Post Listing" class="w-auto" (click)="save()"></button>
} @else {
<button pButton pRipple label="Update Listing" class="w-auto" (click)="save()"></button>
}
</div>
</div>
</div>
</div>
</div>
</div>
<p-toast></p-toast>
<p-confirmDialog></p-confirmDialog> -->

View File

@ -1,60 +1,55 @@
import { Component, ViewChild } from '@angular/core'; import { Component, ViewChild } from '@angular/core';
import { ButtonModule } from 'primeng/button';
import { CheckboxModule } from 'primeng/checkbox';
import { InputTextModule } from 'primeng/inputtext';
import { StyleClassModule } from 'primeng/styleclass';
import { SelectOptionsService } from '../../../services/select-options.service';
import { DropdownModule } from 'primeng/dropdown';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ToggleButtonModule } from 'primeng/togglebutton';
import { TagModule } from 'primeng/tag';
import data from '../../../../assets/data/user.json';
import dataListings from '../../../../assets/data/listings.json';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { ChipModule } from 'primeng/chip';
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
import { DividerModule } from 'primeng/divider';
import { TableModule } from 'primeng/table';
import { createGenericObject, getListingType } from '../../../utils/utils';
import { ListingsService } from '../../../services/listings.service';
import { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { ListingsService } from '../../../services/listings.service';
import { SelectOptionsService } from '../../../services/select-options.service';
import { createGenericObject } from '../../../utils/utils';
import { DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
import { HttpEventType } from '@angular/common/http';
import { faTrash } from '@fortawesome/free-solid-svg-icons';
import { AngularCropperjsModule } from 'angular-cropperjs';
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
import { ConfirmationService, MessageService } from 'primeng/api';
import { CarouselModule } from 'primeng/carousel';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { DialogModule } from 'primeng/dialog';
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
import { EditorModule } from 'primeng/editor';
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
import { v4 as uuidv4 } from 'uuid';
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
import { AutoCompleteCompleteEvent, ImageProperty, ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../../../environments/environment';
import { ImageCropperComponent } from '../../../components/image-cropper/image-cropper.component';
import { InputNumberModule } from '../../../components/inputnumber/inputnumber.component';
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe'; import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
import { GeoService } from '../../../services/geo.service';
import { ImageService } from '../../../services/image.service';
import { LoadingService } from '../../../services/loading.service';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module'; import { SharedModule } from '../../../shared/shared/shared.module';
import { ConfirmationService, MessageService } from 'primeng/api';
import { GeoResult, GeoService } from '../../../services/geo.service';
import { InputNumberComponent, InputNumberModule } from '../../../components/inputnumber/inputnumber.component';
import { environment } from '../../../../environments/environment';
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
import { CarouselModule } from 'primeng/carousel';
import { v4 as uuidv4 } from 'uuid';
import { DialogModule } from 'primeng/dialog';
import { AngularCropperjsModule, CropperComponent } from 'angular-cropperjs';
import { HttpClient, HttpEventType } from '@angular/common/http';
import { ImageService } from '../../../services/image.service'
import { LoadingService } from '../../../services/loading.service';
import { TOOLBAR_OPTIONS } from '../../utils/defaults'; import { TOOLBAR_OPTIONS } from '../../utils/defaults';
import { EditorModule } from 'primeng/editor';
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
import { ImageCropperComponent } from '../../../components/image-cropper/image-cropper.component';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { CdkDragDrop, CdkDragEnter, CdkDragExit, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
import { faTrash } from '@fortawesome/free-solid-svg-icons';
import { AutoCompleteCompleteEvent, ImageProperty, ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
@Component({ @Component({
selector: 'create-listing', selector: 'create-listing',
standalone: true, standalone: true,
imports: [SharedModule, ArrayToStringPipe, InputNumberModule, CarouselModule, imports: [
DialogModule, AngularCropperjsModule, FileUploadModule, EditorModule, DynamicDialogModule, DragDropModule, SharedModule,
ConfirmDialogModule, MixedCdkDragDropModule], ArrayToStringPipe,
InputNumberModule,
CarouselModule,
DialogModule,
AngularCropperjsModule,
FileUploadModule,
EditorModule,
DynamicDialogModule,
DragDropModule,
ConfirmDialogModule,
MixedCdkDragDropModule,
],
providers: [MessageService, DialogService, ConfirmationService], providers: [MessageService, DialogService, ConfirmationService],
templateUrl: './edit-listing.component.html', templateUrl: './edit-listing.component.html',
styleUrl: './edit-listing.component.scss' styleUrl: './edit-listing.component.scss',
}) })
export class EditListingComponent { export class EditListingComponent {
@ViewChild(FileUpload) public fileUpload: FileUpload; @ViewChild(FileUpload) public fileUpload: FileUpload;
@ -62,37 +57,38 @@ export class EditListingComponent {
category: string; category: string;
location: string; location: string;
mode: 'edit' | 'create'; mode: 'edit' | 'create';
separator: '\n\n' separator: '\n\n';
listing: ListingType listing: ListingType;
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined; private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
user: User; user: User;
maxFileSize = 3000000; maxFileSize = 3000000;
uploadUrl: string; uploadUrl: string;
environment = environment; environment = environment;
propertyImages: ImageProperty[] propertyImages: ImageProperty[];
responsiveOptions = [ responsiveOptions = [
{ {
breakpoint: '1199px', breakpoint: '1199px',
numVisible: 1, numVisible: 1,
numScroll: 1 numScroll: 1,
}, },
{ {
breakpoint: '991px', breakpoint: '991px',
numVisible: 2, numVisible: 2,
numScroll: 1 numScroll: 1,
}, },
{ {
breakpoint: '767px', breakpoint: '767px',
numVisible: 1, numVisible: 1,
numScroll: 1 numScroll: 1,
} },
]; ];
config = { aspectRatio: 16 / 9 } config = { aspectRatio: 16 / 9 };
editorModules = TOOLBAR_OPTIONS editorModules = TOOLBAR_OPTIONS;
dialogRef: DynamicDialogRef | undefined; dialogRef: DynamicDialogRef | undefined;
draggedImage: ImageProperty draggedImage: ImageProperty;
faTrash = faTrash; faTrash = faTrash;
constructor(public selectOptions: SelectOptionsService, constructor(
public selectOptions: SelectOptionsService,
private router: Router, private router: Router,
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private listingsService: ListingsService, private listingsService: ListingsService,
@ -102,7 +98,8 @@ export class EditListingComponent {
private imageService: ImageService, private imageService: ImageService,
private loadingService: LoadingService, private loadingService: LoadingService,
public dialogService: DialogService, public dialogService: DialogService,
private confirmationService: ConfirmationService) { private confirmationService: ConfirmationService,
) {
this.user = this.userService.getUser(); this.user = this.userService.getUser();
// Abonniere Router-Events, um den aktiven Link zu ermitteln // Abonniere Router-Events, um den aktiven Link zu ermitteln
this.router.events.subscribe(event => { this.router.events.subscribe(event => {
@ -110,7 +107,6 @@ export class EditListingComponent {
this.mode = event.url === '/createListing' ? 'create' : 'edit'; this.mode = event.url === '/createListing' ? 'create' : 'edit';
} }
}); });
} }
async ngOnInit() { async ngOnInit() {
if (this.mode === 'edit') { if (this.mode === 'edit') {
@ -119,23 +115,23 @@ export class EditListingComponent {
const uuid = sessionStorage.getItem('uuid') ? sessionStorage.getItem('uuid') : uuidv4(); const uuid = sessionStorage.getItem('uuid') ? sessionStorage.getItem('uuid') : uuidv4();
sessionStorage.setItem('uuid', uuid); sessionStorage.setItem('uuid', uuid);
this.listing = createGenericObject<BusinessListing>(); this.listing = createGenericObject<BusinessListing>();
this.listing.id = uuid this.listing.id = uuid;
this.listing.userId = this.user.id this.listing.userId = this.user.id;
} }
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`; this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`;
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id) this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
} }
async save() { async save() {
sessionStorage.removeItem('uuid') sessionStorage.removeItem('uuid');
await this.listingsService.save(this.listing, getListingType(this.listing)); // await this.listingsService.save(this.listing, this.listing.listingsCategory);
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 }); this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 });
} }
suggestions: string[] | undefined; suggestions: string[] | undefined;
async search(event: AutoCompleteCompleteEvent) { async search(event: AutoCompleteCompleteEvent) {
const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query, this.listing.state)) const result = await lastValueFrom(this.geoService.findCitiesStartingWith(event.query, this.listing.state));
this.suggestions = result.map(r => r.city).slice(0, 5); this.suggestions = result.map(r => r.city).slice(0, 5);
} }
@ -145,7 +141,7 @@ export class EditListingComponent {
data: { data: {
imageUrl: imageUrl, imageUrl: imageUrl,
fileUpload: this.fileUpload, fileUpload: this.fileUpload,
ratioVariable: false ratioVariable: false,
}, },
header: 'Edit Image', header: 'Edit Image',
width: '50vw', width: '50vw',
@ -155,24 +151,27 @@ export class EditListingComponent {
closable: false, closable: false,
breakpoints: { breakpoints: {
'960px': '75vw', '960px': '75vw',
'640px': '90vw' '640px': '90vw',
}, },
}); });
this.dialogRef.onClose.subscribe(cropper => { this.dialogRef.onClose.subscribe(cropper => {
if (cropper) { if (cropper) {
this.loadingService.startLoading('uploadImage'); this.loadingService.startLoading('uploadImage');
cropper.getCroppedCanvas().toBlob(async (blob) => { cropper.getCroppedCanvas().toBlob(async blob => {
this.imageService.uploadImage(blob, 'uploadPropertyPicture', this.listing.id).subscribe(async (event) => { this.imageService.uploadImage(blob, 'uploadPropertyPicture', this.listing.id).subscribe(
async event => {
if (event.type === HttpEventType.Response) { if (event.type === HttpEventType.Response) {
console.log('Upload abgeschlossen', event.body); console.log('Upload abgeschlossen', event.body);
this.loadingService.stopLoading('uploadImage'); this.loadingService.stopLoading('uploadImage');
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id) this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
} }
}, error => console.error('Fehler beim Upload:', error)); },
error => console.error('Fehler beim Upload:', error),
);
}, 'image/jpg'); }, 'image/jpg');
cropper.destroy(); cropper.destroy();
} }
}) });
} }
deleteConfirm(imageName: string) { deleteConfirm(imageName: string) {
@ -181,27 +180,24 @@ export class EditListingComponent {
message: `Do you want to delete this image ${imageName}?`, message: `Do you want to delete this image ${imageName}?`,
header: 'Delete Confirmation', header: 'Delete Confirmation',
icon: 'pi pi-info-circle', icon: 'pi pi-info-circle',
acceptButtonStyleClass: "p-button-danger p-button-text", acceptButtonStyleClass: 'p-button-danger p-button-text',
rejectButtonStyleClass: "p-button-text p-button-text", rejectButtonStyleClass: 'p-button-text p-button-text',
acceptIcon: "none", acceptIcon: 'none',
rejectIcon: "none", rejectIcon: 'none',
accept: async () => { accept: async () => {
await this.imageService.deleteListingImage(this.listing.id, imageName); await this.imageService.deleteListingImage(this.listing.id, imageName);
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Image deleted' }); this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Image deleted' });
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id) this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
}, },
reject: () => { reject: () => {
// this.messageService.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected' }); console.log('deny');
console.log('deny') },
}
}); });
} }
onDrop(event: { previousIndex: number; currentIndex: number }) { onDrop(event: { previousIndex: number; currentIndex: number }) {
moveItemInArray(this.propertyImages, event.previousIndex, event.currentIndex); moveItemInArray(this.propertyImages, event.previousIndex, event.currentIndex);
this.listingsService.changeImageOrder(this.listing.id, this.propertyImages) this.listingsService.changeImageOrder(this.listing.id, this.propertyImages);
} }
} }

View File

@ -1,4 +1,3 @@
<div class="surface-ground px-4 py-8 md:px-6 lg:px-8 h-full"> <div class="surface-ground px-4 py-8 md:px-6 lg:px-8 h-full">
<div class="p-fluid flex flex-column lg:flex-row"> <div class="p-fluid flex flex-column lg:flex-row">
<menu-account></menu-account> <menu-account></menu-account>
@ -7,7 +6,16 @@
<div class="surface-card p-5 shadow-2 border-round flex-auto"> <div class="surface-card p-5 shadow-2 border-round flex-auto">
<div class="text-900 font-semibold text-lg mt-3">My Listings</div> <div class="text-900 font-semibold text-lg mt-3">My Listings</div>
<p-divider></p-divider> <p-divider></p-divider>
<p-table [value]="myListings" [tableStyle]="{ 'min-width': '50rem' }" dataKey="id" [paginator]="true" [rows]="10" [rowsPerPageOptions]="[10, 20, 50]" [showCurrentPageReport]="true" currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries"> <p-table
[value]="myListings"
[tableStyle]="{ 'min-width': '50rem' }"
dataKey="id"
[paginator]="true"
[rows]="10"
[rowsPerPageOptions]="[10, 20, 50]"
[showCurrentPageReport]="true"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries"
>
<ng-template pTemplate="header"> <ng-template pTemplate="header">
<tr> <tr>
<th class="wide-column">Title</th> <th class="wide-column">Title</th>
@ -20,9 +28,13 @@
<tr> <tr>
<td class="wide-column line-height-3">{{ listing.title }}</td> <td class="wide-column line-height-3">{{ listing.title }}</td>
<td>{{ selectOptions.getListingsCategory(listing.listingsCategory) }}</td> <td>{{ selectOptions.getListingsCategory(listing.listingsCategory) }}</td>
<td>{{ selectOptions.getState(listing.location) }}</td> <td>{{ selectOptions.getState(listing.city) }}</td>
<td> <td>
<button pButton pRipple icon="pi pi-pencil" class="p-button-rounded p-button-success mr-2" [routerLink]="['/editListing',listing.id]"></button> @if(isBusinessListing(listing)){
<button pButton pRipple icon="pi pi-pencil" class="p-button-rounded p-button-success mr-2" [routerLink]="['/editBusinessListing', listing.id]"></button>
} @if(isCommercialPropertyListing(listing)){
<button pButton pRipple icon="pi pi-pencil" class="p-button-rounded p-button-success mr-2" [routerLink]="['/editCommercialPropertyListing', listing.id]"></button>
}
<button pButton pRipple icon="pi pi-trash" class="p-button-rounded p-button-warning" (click)="confirm($event, listing)"></button> <button pButton pRipple icon="pi pi-trash" class="p-button-rounded p-button-warning" (click)="confirm($event, listing)"></button>
</td> </td>
</tr> </tr>

View File

@ -1,41 +1,42 @@
import { ChangeDetectorRef, Component } from '@angular/core'; import { ChangeDetectorRef, Component } from '@angular/core';
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
import dataListings from '../../../../assets/data/listings.json';
import { SharedModule } from '../../../shared/shared/shared.module';
import { UserService } from '../../../services/user.service';
import { ListingsService } from '../../../services/listings.service';
import { lastValueFrom } from 'rxjs';
import { SelectOptionsService } from '../../../services/select-options.service';
import { ConfirmationService, MessageService } from 'primeng/api'; import { ConfirmationService, MessageService } from 'primeng/api';
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
import { ListingType } from '../../../../../../bizmatch-server/src/models/main.model'; import { ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
import { getListingType } from '../../../utils/utils'; import { ListingsService } from '../../../services/listings.service';
import { SelectOptionsService } from '../../../services/select-options.service';
import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module';
import { getListingType, isBusinessListing, isCommercialPropertyListing } from '../../../utils/utils';
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
@Component({ @Component({
selector: 'app-my-listing', selector: 'app-my-listing',
standalone: true, standalone: true,
imports: [MenuAccountComponent, SharedModule], imports: [MenuAccountComponent, SharedModule],
providers: [ConfirmationService, MessageService], providers: [ConfirmationService, MessageService],
templateUrl: './my-listing.component.html', templateUrl: './my-listing.component.html',
styleUrl: './my-listing.component.scss' styleUrl: './my-listing.component.scss',
}) })
export class MyListingComponent { export class MyListingComponent {
user: User; listings: Array<ListingType> = []; //dataListings as unknown as Array<BusinessListing>;
listings: Array<ListingType> =[]//dataListings as unknown as Array<BusinessListing>; myListings: Array<ListingType>;
myListings: Array<ListingType> userId: string;
constructor(public userService: UserService,private listingsService:ListingsService, private cdRef:ChangeDetectorRef,public selectOptions:SelectOptionsService,private confirmationService: ConfirmationService,private messageService: MessageService){ isBusinessListing = isBusinessListing;
this.user=this.userService.getUser(); isCommercialPropertyListing = isCommercialPropertyListing;
constructor(
} public userService: UserService,
private listingsService: ListingsService,
private cdRef: ChangeDetectorRef,
public selectOptions: SelectOptionsService,
private confirmationService: ConfirmationService,
private messageService: MessageService,
) {}
async ngOnInit() { async ngOnInit() {
// this.listings=await lastValueFrom(this.listingsService.getAllListings()); this.userId = await this.userService.getId();
this.myListings=this.listings.filter(l=>l.userId===this.user.id); this.myListings = await this.listingsService.getListingByUserId(this.userId);
} }
async deleteListing(listing: ListingType) { async deleteListing(listing: ListingType) {
await this.listingsService.deleteListing(listing.id, getListingType(listing)); await this.listingsService.deleteListing(listing.id, getListingType(listing));
// this.listings=await lastValueFrom(this.listingsService.getAllListings()); this.myListings = await this.listingsService.getListingByUserId(this.userId);
this.myListings=this.listings.filter(l=>l.userId===this.user.id);
} }
confirm(event: Event, listing: ListingType) { confirm(event: Event, listing: ListingType) {
@ -46,7 +47,7 @@ export class MyListingComponent {
accept: () => { accept: () => {
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing has been deleted', life: 3000 }); this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing has been deleted', life: 3000 });
this.deleteListing(listing); this.deleteListing(listing);
} },
}); });
} }
} }

View File

@ -1,19 +1,16 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, lastValueFrom } from 'rxjs'; import { Observable, lastValueFrom } from 'rxjs';
import { environment } from '../../environments/environment';
import onChange from 'on-change';
import { getListingType, getSessionStorageHandler } from '../utils/utils';
import { ImageProperty, ListingCriteria, ListingType, ResponseBusinessListingArray, ResponseCommercialPropertyListingArray } from '../../../../bizmatch-server/src/models/main.model';
import { BusinessListing } from '../../../../bizmatch-server/src/models/db.model'; import { BusinessListing } from '../../../../bizmatch-server/src/models/db.model';
import { ImageProperty, ListingCriteria, ListingType, ResponseBusinessListingArray } from '../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../environments/environment';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class ListingsService { export class ListingsService {
private apiBaseUrl = environment.apiBaseUrl; private apiBaseUrl = environment.apiBaseUrl;
constructor(private http: HttpClient) { constructor(private http: HttpClient) {}
}
// getAllListings():Observable<ListingType[]>{ // getAllListings():Observable<ListingType[]>{
// return this.http.get<ListingType[]>(`${this.apiBaseUrl}/bizmatch/business-listings`); // return this.http.get<ListingType[]>(`${this.apiBaseUrl}/bizmatch/business-listings`);
@ -26,11 +23,15 @@ export class ListingsService {
const result = this.http.get<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/${id}`); const result = this.http.get<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/${id}`);
return result; return result;
} }
getListingByUserId(userid:string):Promise<BusinessListing[]>{ getListingByUserId(userid: string): Promise<ListingType[]> {
return lastValueFrom(this.http.get<BusinessListing[]>(`${this.apiBaseUrl}/bizmatch/listings/business/user/${userid}`)); return lastValueFrom(this.http.get<BusinessListing[]>(`${this.apiBaseUrl}/bizmatch/listings/business/user/${userid}`));
} }
async save(listing: any, listingsCategory: 'business' | 'professionals_brokers' | 'commercialProperty') { async save(listing: any, listingsCategory: 'business' | 'professionals_brokers' | 'commercialProperty') {
await lastValueFrom(this.http.post<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}`,listing)); if (listing.id) {
return await lastValueFrom(this.http.put<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}`, listing));
} else {
return await lastValueFrom(this.http.post<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}`, listing));
}
} }
async deleteListing(id: string, listingsCategory: 'business' | 'professionals_brokers' | 'commercialProperty') { async deleteListing(id: string, listingsCategory: 'business' | 'professionals_brokers' | 'commercialProperty') {
await lastValueFrom(this.http.delete<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/${id}`)); await lastValueFrom(this.http.delete<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/${id}`));

View File

@ -75,7 +75,17 @@ export class UserService {
getUserObservable(): Observable<User> { getUserObservable(): Observable<User> {
return this.user$; return this.user$;
} }
async getId(): Promise<string> {
if (sessionStorage.getItem('USERID')) {
return sessionStorage.getItem('USERID');
} else {
const user = await this.getByMail(this.user.email);
sessionStorage.setItem('USERID', user.id);
return user.id;
}
}
logout() { logout() {
sessionStorage.removeItem('USERID');
this.keycloak.logout(window.location.origin + '/home'); this.keycloak.logout(window.location.origin + '/home');
} }
async login(url: string) { async login(url: string) {

View File

@ -1,6 +1,7 @@
import { INFO, ConsoleFormattedStream, createLogger as _createLogger, stdSerializers } from "browser-bunyan"; import { Router } from '@angular/router';
import { ListingCriteria } from "../../../../bizmatch-server/src/models/main.model"; import { ConsoleFormattedStream, INFO, createLogger as _createLogger, stdSerializers } from 'browser-bunyan';
import { BusinessListing, CommercialPropertyListing } from "../../../../bizmatch-server/src/models/db.model"; import { BusinessListing, CommercialPropertyListing } from '../../../../bizmatch-server/src/models/db.model';
import { ListingCriteria } from '../../../../bizmatch-server/src/models/main.model';
export function createGenericObject<T>(): T { export function createGenericObject<T>(): T {
// Ein leeres Objekt vom Typ T erstellen // Ein leeres Objekt vom Typ T erstellen
@ -24,12 +25,12 @@ export function createGenericObject<T>(): T {
serializers: stdSerializers, serializers: stdSerializers,
src: true, src: true,
...options, ...options,
}) });
} }
export const getSessionStorageHandler = function (path, value, previous, applyData) { export const getSessionStorageHandler = function (path, value, previous, applyData) {
sessionStorage.setItem('criteria', JSON.stringify(this)); sessionStorage.setItem('criteria', JSON.stringify(this));
} };
export function getCriteriaStateObject() { export function getCriteriaStateObject() {
const initialState = createGenericObject<ListingCriteria>(); const initialState = createGenericObject<ListingCriteria>();
@ -46,3 +47,10 @@ export function createGenericObject<T>(): T {
export function isCommercialPropertyListing(listing: BusinessListing | CommercialPropertyListing): listing is CommercialPropertyListing { export function isCommercialPropertyListing(listing: BusinessListing | CommercialPropertyListing): listing is CommercialPropertyListing {
return listing?.type >= 100; return listing?.type >= 100;
} }
export function routeListingWithState(router: Router, value: string, data: any) {
if (value === 'business') {
router.navigate(['createBusinessListing'], { state: { data } });
} else {
router.navigate(['createCommercialPropertyListing'], { state: { data } });
}
}