not-found page, #85, client logging

This commit is contained in:
Andreas Knuth 2024-08-13 19:06:32 +02:00
parent 1f8febc479
commit a8bb163acf
15 changed files with 233 additions and 185 deletions

View File

@ -13,6 +13,9 @@ import { FileService } from './file/file.service.js';
import { GeoModule } from './geo/geo.module.js'; import { GeoModule } from './geo/geo.module.js';
import { ImageModule } from './image/image.module.js'; import { ImageModule } from './image/image.module.js';
import { ListingsModule } from './listings/listings.module.js'; import { ListingsModule } from './listings/listings.module.js';
import { LogController } from './log/log.controller.js';
import { LogModule } from './log/log.module.js';
import { MailModule } from './mail/mail.module.js'; import { MailModule } from './mail/mail.module.js';
import { RequestDurationMiddleware } from './request-duration/request-duration.middleware.js'; import { RequestDurationMiddleware } from './request-duration/request-duration.middleware.js';
import { SelectOptionsModule } from './select-options/select-options.module.js'; import { SelectOptionsModule } from './select-options/select-options.module.js';
@ -75,8 +78,9 @@ loadEnvFiles();
ImageModule, ImageModule,
PassportModule, PassportModule,
AiModule, AiModule,
LogModule,
], ],
controllers: [AppController], controllers: [AppController, LogController],
providers: [AppService, FileService], providers: [AppService, FileService],
}) })
export class AppModule { export class AppModule {

View File

@ -0,0 +1,19 @@
import { Body, Controller, Inject, Post, Request, UseGuards } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard.js';
import { LogMessage } from '../models/main.model.js';
@Controller('log')
export class LogController {
constructor(@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {}
@UseGuards(OptionalJwtAuthGuard)
@Post()
log(@Request() req, @Body() message: LogMessage) {
if (message.severity === 'info') {
this.logger.info(message.text);
} else {
this.logger.error(message.text);
}
}
}

View File

@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { LogController } from './log.controller.js';
@Module({
controllers: [LogController],
})
export class LogModule {}

View File

@ -247,6 +247,14 @@ export interface CountyResult {
state: string; state: string;
state_code: string; state_code: string;
} }
export interface LogMessage {
severity: 'error' | 'info';
text: string;
}
export interface ModalResult {
accepted: boolean;
criteria?: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria;
}
export function isEmpty(value: any): boolean { export function isEmpty(value: any): boolean {
// Check for undefined or null // Check for undefined or null
if (value === undefined || value === null) { if (value === undefined || value === null) {

View File

@ -54,6 +54,10 @@ export const routes: Routes = [
canActivate: [ListingCategoryGuard], canActivate: [ListingCategoryGuard],
component: NotFoundComponent, // Dummy-Komponente, wird nie angezeigt, da der Guard weiterleitet component: NotFoundComponent, // Dummy-Komponente, wird nie angezeigt, da der Guard weiterleitet
}, },
{
path: 'notfound',
component: NotFoundComponent,
},
// ######### // #########
// User Details // User Details
{ {

View File

@ -14,7 +14,7 @@ import { CriteriaChangeService } from '../../services/criteria-change.service';
import { SearchService } from '../../services/search.service'; import { SearchService } from '../../services/search.service';
import { SharedService } from '../../services/shared.service'; import { SharedService } from '../../services/shared.service';
import { UserService } from '../../services/user.service'; import { UserService } from '../../services/user.service';
import { compareObjects, createEmptyBusinessListingCriteria, createEmptyCommercialPropertyListingCriteria, createEmptyUserListingCriteria, getCriteriaProxy, map2User } from '../../utils/utils'; import { assignProperties, compareObjects, createEmptyBusinessListingCriteria, createEmptyCommercialPropertyListingCriteria, createEmptyUserListingCriteria, getCriteriaProxy, map2User } from '../../utils/utils';
import { DropdownComponent } from '../dropdown/dropdown.component'; import { DropdownComponent } from '../dropdown/dropdown.component';
import { ModalService } from '../search-modal/modal.service'; import { ModalService } from '../search-modal/modal.service';
@Component({ @Component({
@ -86,9 +86,11 @@ export class HeaderComponent {
ngAfterViewInit() {} ngAfterViewInit() {}
async openModal() { async openModal() {
const accepted = await this.modalService.showModal(this.criteria); const modalResult = await this.modalService.showModal(this.criteria);
if (accepted) { if (modalResult.accepted) {
this.searchService.search(this.criteria); this.searchService.search(this.criteria);
} else {
this.criteria = assignProperties(this.criteria, modalResult.criteria);
} }
} }
navigateWithState(dest: string, state: any) { navigateWithState(dest: string, state: any) {

View File

@ -1 +1,35 @@
<p>not-found works!</p> <!-- <section class="bg-white dark:bg-gray-900">
<div class="py-8 px-4 mx-auto max-w-screen-xl lg:py-16 lg:px-6">
<div class="mx-auto max-w-screen-sm text-center">
<h1 class="mb-4 text-7xl tracking-tight font-extrabold lg:text-9xl text-primary-600 dark:text-primary-500">404</h1>
<p class="mb-4 text-3xl tracking-tight font-bold text-gray-900 md:text-4xl dark:text-white">Something's missing.</p>
<p class="mb-4 text-lg font-light text-gray-500 dark:text-gray-400">Sorry, we can't find that page.</p>
<a
routerLink="/home"
class="inline-flex text-white bg-primary-600 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:focus:ring-primary-900 my-4"
>Back to Homepage</a
>
</div>
</div>
</section> -->
<section class="bg-white dark:bg-gray-900">
<div class="py-8 px-4 mx-auto max-w-screen-xl lg:py-16 lg:px-6">
<div class="mx-auto max-w-screen-sm text-center">
<h1 class="mb-4 text-7xl tracking-tight font-extrabold lg:text-9xl text-blue-700 dark:text-blue-500">404</h1>
<p class="mb-4 text-3xl tracking-tight font-bold text-gray-900 md:text-4xl dark:text-white">Something's missing.</p>
<p class="mb-4 text-lg font-light text-gray-500 dark:text-gray-400">Sorry, we can't find that page</p>
<!-- <a
href="#"
class="inline-flex text-white bg-primary-600 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:focus:ring-primary-900 my-4"
>Back to Homepage</a
> -->
<button
type="button"
[routerLink]="['/home']"
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
>
Back to Homepage
</button>
</div>
</div>
</section>

View File

@ -1,8 +1,11 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
@Component({ @Component({
selector: 'app-not-found', selector: 'app-not-found',
standalone: true, standalone: true,
template: '<h2>Page not found</h2>', imports: [CommonModule, RouterModule],
templateUrl: './not-found.component.html',
}) })
export class NotFoundComponent {} export class NotFoundComponent {}

View File

@ -1,7 +1,7 @@
// 1. Shared Service (modal.service.ts) // 1. Shared Service (modal.service.ts)
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { BusinessListingCriteria, CommercialPropertyListingCriteria, UserListingCriteria } from '../../../../../bizmatch-server/src/models/main.model'; import { BusinessListingCriteria, CommercialPropertyListingCriteria, ModalResult, UserListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -9,26 +9,26 @@ import { BusinessListingCriteria, CommercialPropertyListingCriteria, UserListing
export class ModalService { export class ModalService {
private modalVisibleSubject = new BehaviorSubject<boolean>(false); private modalVisibleSubject = new BehaviorSubject<boolean>(false);
private messageSubject = new BehaviorSubject<BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria>(null); private messageSubject = new BehaviorSubject<BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria>(null);
private resolvePromise!: (value: boolean) => void; private resolvePromise!: (value: ModalResult) => void;
modalVisible$: Observable<boolean> = this.modalVisibleSubject.asObservable(); modalVisible$: Observable<boolean> = this.modalVisibleSubject.asObservable();
message$: Observable<BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria> = this.messageSubject.asObservable(); message$: Observable<BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria> = this.messageSubject.asObservable();
showModal(message: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria): Promise<boolean> { showModal(message: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria): Promise<ModalResult> {
this.messageSubject.next(message); this.messageSubject.next(message);
this.modalVisibleSubject.next(true); this.modalVisibleSubject.next(true);
return new Promise<boolean>(resolve => { return new Promise<ModalResult>(resolve => {
this.resolvePromise = resolve; this.resolvePromise = resolve;
}); });
} }
accept(): void { accept(): void {
this.modalVisibleSubject.next(false); this.modalVisibleSubject.next(false);
this.resolvePromise(true); this.resolvePromise({ accepted: true });
} }
reject(): void { reject(backupCriteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria): void {
this.modalVisibleSubject.next(false); this.modalVisibleSubject.next(false);
this.resolvePromise(false); this.resolvePromise({ accepted: false, criteria: backupCriteria });
} }
} }

View File

@ -9,7 +9,7 @@
} @else { } @else {
<h3 class="text-xl font-semibold text-gray-900">Professional Listing Search</h3> <h3 class="text-xl font-semibold text-gray-900">Professional Listing Search</h3>
} }
<button (click)="modalService.reject()" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center"> <button (click)="close()" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center">
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14"> <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" /> <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
</svg> </svg>
@ -20,6 +20,11 @@
<div class="flex space-x-4 mb-4"> <div class="flex space-x-4 mb-4">
<button class="text-blue-600 font-medium border-b-2 border-blue-600 pb-2">Classic Search</button> <button class="text-blue-600 font-medium border-b-2 border-blue-600 pb-2">Classic Search</button>
<button class="text-gray-500">AI Search <span class="bg-gray-200 text-xs font-semibold px-2 py-1 rounded">BETA</span></button> <button class="text-gray-500">AI Search <span class="bg-gray-200 text-xs font-semibold px-2 py-1 rounded">BETA</span></button>
<i data-tooltip-target="tooltip-light" class="fa-solid fa-trash-can flex self-center ml-2 hover:cursor-pointer text-blue-500" (click)="clearFilter()"></i>
<div id="tooltip-light" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg shadow-sm opacity-0 tooltip">
Clear all Filter
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div> </div>
@if(criteria.criteriaType==='businessListings'){ @if(criteria.criteriaType==='businessListings'){
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@ -470,7 +475,7 @@
</button> </button>
<button <button
type="button" type="button"
(click)="modalService.reject()" (click)="close()"
class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10" class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10"
> >
Cancel Cancel

View File

@ -10,6 +10,7 @@ import { ListingsService } from '../../services/listings.service';
import { SelectOptionsService } from '../../services/select-options.service'; import { SelectOptionsService } from '../../services/select-options.service';
import { UserService } from '../../services/user.service'; import { UserService } from '../../services/user.service';
import { SharedModule } from '../../shared/shared/shared.module'; import { SharedModule } from '../../shared/shared/shared.module';
import { resetBusinessListingCriteria, resetCommercialPropertyListingCriteria, resetUserListingCriteria } from '../../utils/utils';
import { ValidatedCityComponent } from '../validated-city/validated-city.component'; import { ValidatedCityComponent } from '../validated-city/validated-city.component';
import { ModalService } from './modal.service'; import { ModalService } from './modal.service';
@UntilDestroy() @UntilDestroy()
@ -29,7 +30,9 @@ export class SearchModalComponent {
countyInput$ = new Subject<string>(); countyInput$ = new Subject<string>();
private criteriaChangeSubscription: Subscription; private criteriaChangeSubscription: Subscription;
public criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria; public criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria;
backupCriteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria;
numberOfResults$: Observable<number>; numberOfResults$: Observable<number>;
cancelDisable = false;
constructor( constructor(
public selectOptions: SelectOptionsService, public selectOptions: SelectOptionsService,
public modalService: ModalService, public modalService: ModalService,
@ -42,6 +45,7 @@ export class SearchModalComponent {
this.setupCriteriaChangeListener(); this.setupCriteriaChangeListener();
this.modalService.message$.pipe(untilDestroyed(this)).subscribe(msg => { this.modalService.message$.pipe(untilDestroyed(this)).subscribe(msg => {
this.criteria = msg; this.criteria = msg;
this.backupCriteria = JSON.parse(JSON.stringify(msg));
this.setTotalNumberOfResults(); this.setTotalNumberOfResults();
}); });
this.modalService.modalVisible$.pipe(untilDestroyed(this)).subscribe(val => { this.modalService.modalVisible$.pipe(untilDestroyed(this)).subscribe(val => {
@ -54,7 +58,9 @@ export class SearchModalComponent {
this.loadCounties(); this.loadCounties();
} }
ngOnChanges() {} ngOnChanges() {
console.log('sdf');
}
categoryClicked(checked: boolean, value: string) { categoryClicked(checked: boolean, value: string) {
if (checked) { if (checked) {
this.criteria.types.push(value); this.criteria.types.push(value);
@ -116,7 +122,10 @@ export class SearchModalComponent {
} }
} }
private setupCriteriaChangeListener() { private setupCriteriaChangeListener() {
this.criteriaChangeSubscription = this.criteriaChangeService.criteriaChange$.pipe(debounceTime(400)).subscribe(() => this.setTotalNumberOfResults()); this.criteriaChangeSubscription = this.criteriaChangeService.criteriaChange$.pipe(debounceTime(400)).subscribe(() => {
this.setTotalNumberOfResults();
this.cancelDisable = true;
});
} }
trackByFn(item: GeoResult) { trackByFn(item: GeoResult) {
return item.id; return item.id;
@ -148,4 +157,16 @@ export class SearchModalComponent {
} }
} }
} }
clearFilter() {
if (this.criteria.criteriaType === 'businessListings') {
resetBusinessListingCriteria(this.criteria);
} else if (this.criteria.criteriaType === 'commercialPropertyListings') {
resetCommercialPropertyListingCriteria(this.criteria);
} else {
resetUserListingCriteria(this.criteria);
}
}
close() {
this.modalService.reject(this.backupCriteria);
}
} }

View File

@ -12,6 +12,7 @@ import { ValidatedTextareaComponent } from '../../../components/validated-textar
import { ValidationMessagesService } from '../../../components/validation-messages.service'; import { ValidationMessagesService } from '../../../components/validation-messages.service';
import { HistoryService } from '../../../services/history.service'; import { HistoryService } from '../../../services/history.service';
import { ListingsService } from '../../../services/listings.service'; import { ListingsService } from '../../../services/listings.service';
import { LogService } from '../../../services/log.service';
import { MailService } from '../../../services/mail.service'; import { MailService } from '../../../services/mail.service';
import { SelectOptionsService } from '../../../services/select-options.service'; import { SelectOptionsService } from '../../../services/select-options.service';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
@ -67,6 +68,7 @@ export class DetailsBusinessListingComponent {
public keycloakService: KeycloakService, public keycloakService: KeycloakService,
private validationMessagesService: ValidationMessagesService, private validationMessagesService: ValidationMessagesService,
private messageService: MessageService, private messageService: MessageService,
private logService: LogService,
) { ) {
this.router.events.subscribe(event => { this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) { if (event instanceof NavigationEnd) {
@ -88,7 +90,8 @@ export class DetailsBusinessListingComponent {
this.listingUser = await this.userService.getByMail(this.listing.email); this.listingUser = await this.userService.getByMail(this.listing.email);
this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description); this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description);
} catch (error) { } catch (error) {
console.log(error); this.logService.log({ severity: 'error', text: error.error.message });
this.router.navigate(['notfound']);
} }
} }
ngOnDestroy() { ngOnDestroy() {

View File

@ -14,6 +14,7 @@ import { ValidationMessagesService } from '../../../components/validation-messag
import { HistoryService } from '../../../services/history.service'; import { HistoryService } from '../../../services/history.service';
import { ImageService } from '../../../services/image.service'; import { ImageService } from '../../../services/image.service';
import { ListingsService } from '../../../services/listings.service'; import { ListingsService } from '../../../services/listings.service';
import { LogService } from '../../../services/log.service';
import { MailService } from '../../../services/mail.service'; import { MailService } from '../../../services/mail.service';
import { SelectOptionsService } from '../../../services/select-options.service'; import { SelectOptionsService } from '../../../services/select-options.service';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
@ -75,6 +76,7 @@ export class DetailsCommercialPropertyListingComponent {
private ngZone: NgZone, private ngZone: NgZone,
private validationMessagesService: ValidationMessagesService, private validationMessagesService: ValidationMessagesService,
private messageService: MessageService, private messageService: MessageService,
private logService: LogService,
) { ) {
this.mailinfo = { sender: {}, email: '', url: environment.mailinfoUrl }; this.mailinfo = { sender: {}, email: '', url: environment.mailinfoUrl };
@ -105,7 +107,8 @@ export class DetailsCommercialPropertyListingComponent {
this.propertyDetails.push({ label: 'Draft', value: this.listing.draft ? 'Yes' : 'No' }); this.propertyDetails.push({ label: 'Draft', value: this.listing.draft ? 'Yes' : 'No' });
} }
} catch (error) { } catch (error) {
console.log(error); this.logService.log({ severity: 'error', text: error.error.message });
this.router.navigate(['notfound']);
} }
//this.initFlowbite(); //this.initFlowbite();

View File

@ -0,0 +1,17 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { lastValueFrom } from 'rxjs';
import { LogMessage } from '../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root',
})
export class LogService {
private apiBaseUrl = environment.apiBaseUrl;
constructor(private http: HttpClient) {}
async log(message: LogMessage): Promise<void> {
lastValueFrom(this.http.post(`${this.apiBaseUrl}/bizmatch/log`, message));
}
}

View File

@ -1,93 +1,11 @@
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { ConsoleFormattedStream, INFO, createLogger as _createLogger, stdSerializers } from 'browser-bunyan'; import { ConsoleFormattedStream, INFO, createLogger as _createLogger, stdSerializers } from 'browser-bunyan';
import { jwtDecode } from 'jwt-decode'; import { jwtDecode } from 'jwt-decode';
import onChange from 'on-change';
import { User } from '../../../../bizmatch-server/src/models/db.model';
import { BusinessListingCriteria, CommercialPropertyListingCriteria, JwtToken, KeycloakUser, MailInfo, UserListingCriteria } from '../../../../bizmatch-server/src/models/main.model'; import { BusinessListingCriteria, CommercialPropertyListingCriteria, JwtToken, KeycloakUser, MailInfo, UserListingCriteria } from '../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../environments/environment';
// export function createDefaultUser(email: string, firstname: string, lastname: string): User {
// return {
// id: undefined,
// email,
// firstname,
// lastname,
// phoneNumber: '',
// description: '',
// companyName: '',
// companyOverview: '',
// companyWebsite: '',
// companyLocation: '',
// offeredServices: '',
// areasServed: [],
// hasProfile: false,
// hasCompanyLogo: false,
// licensedIn: [],
// gender: undefined,
// customerType: undefined,
// customerSubType: undefined,
// created: new Date(),
// updated: new Date(),
// };
// }
// export function createDefaultCommercialPropertyListing(): CommercialPropertyListing {
// return {
// id: undefined,
// serialId: undefined,
// email: '',
// type: null,
// title: '',
// description: '',
// city: '',
// state: '',
// price: null,
// favoritesForUser: [],
// hideImage: false,
// draft: false,
// zipCode: null,
// county: '',
// imageOrder: [],
// imagePath: '',
// created: null,
// updated: null,
// visits: null,
// lastVisit: null,
// latitude: null,
// longitude: null,
// listingsCategory: 'commercialProperty',
// };
// }
// export function createDefaultBusinessListing(): BusinessListing {
// return {
// id: undefined,
// email: '',
// type: null,
// title: '',
// description: '',
// city: '',
// state: '',
// price: null,
// favoritesForUser: [],
// draft: false,
// realEstateIncluded: false,
// leasedLocation: false,
// franchiseResale: false,
// salesRevenue: null,
// cashFlow: null,
// supportAndTraining: '',
// employees: null,
// established: null,
// internalListingNumber: null,
// reasonForSale: '',
// brokerLicencing: '',
// internals: '',
// created: null,
// updated: null,
// visits: null,
// lastVisit: null,
// latitude: null,
// longitude: null,
// listingsCategory: 'business',
// };
// }
export function createEmptyBusinessListingCriteria(): BusinessListingCriteria { export function createEmptyBusinessListingCriteria(): BusinessListingCriteria {
return { return {
start: 0, start: 0,
@ -153,6 +71,66 @@ export function createEmptyUserListingCriteria(): UserListingCriteria {
radius: null, radius: null,
}; };
} }
export function resetBusinessListingCriteria(criteria: BusinessListingCriteria) {
criteria.start = 0;
criteria.length = 0;
criteria.page = 0;
criteria.state = null;
criteria.city = null;
criteria.types = [];
criteria.prompt = '';
criteria.criteriaType = 'businessListings';
criteria.minPrice = null;
criteria.maxPrice = null;
criteria.minRevenue = null;
criteria.maxRevenue = null;
criteria.minCashFlow = null;
criteria.maxCashFlow = null;
criteria.minNumberEmployees = null;
criteria.maxNumberEmployees = null;
criteria.establishedSince = null;
criteria.establishedUntil = null;
criteria.realEstateChecked = false;
criteria.leasedLocation = false;
criteria.franchiseResale = false;
criteria.title = '';
criteria.brokerName = '';
criteria.searchType = 'exact';
criteria.radius = null;
}
export function resetCommercialPropertyListingCriteria(criteria: CommercialPropertyListingCriteria) {
criteria.start = 0;
criteria.length = 0;
criteria.page = 0;
criteria.state = null;
criteria.city = null;
criteria.types = [];
criteria.prompt = '';
criteria.criteriaType = 'commercialPropertyListings';
criteria.minPrice = null;
criteria.maxPrice = null;
criteria.title = '';
criteria.searchType = 'exact';
criteria.radius = null;
}
export function resetUserListingCriteria(criteria: UserListingCriteria) {
criteria.start = 0;
criteria.length = 0;
criteria.page = 0;
criteria.city = null;
criteria.types = [];
criteria.prompt = '';
criteria.criteriaType = 'brokerListings';
criteria.brokerName = '';
criteria.companyName = '';
criteria.counties = [];
criteria.state = '';
criteria.searchType = 'exact';
criteria.radius = null;
}
export function createMailInfo(user: User): MailInfo { export function createMailInfo(user: User): MailInfo {
return { return {
sender: { name: `${user.firstname} ${user.lastname}`, email: user.email, phoneNumber: user.phoneNumber, state: user.companyLocation?.state, comments: null }, sender: { name: `${user.firstname} ${user.lastname}`, email: user.email, phoneNumber: user.phoneNumber, state: user.companyLocation?.state, comments: null },
@ -178,9 +156,7 @@ export function formatPhoneNumber(phone: string): string {
} }
return phone; return phone;
} }
// export const getSessionStorageHandler = function (path, value, previous, applyData) {
// sessionStorage.setItem(applyData, JSON.stringify(this));
// };
export const getSessionStorageHandler = function (criteriaType, path, value, previous, applyData) { export const getSessionStorageHandler = function (criteriaType, path, value, previous, applyData) {
sessionStorage.setItem(`${criteriaType}_criteria`, JSON.stringify(this)); sessionStorage.setItem(`${criteriaType}_criteria`, JSON.stringify(this));
console.log('Zusätzlicher Parameter:', criteriaType); console.log('Zusätzlicher Parameter:', criteriaType);
@ -191,19 +167,6 @@ export const getSessionStorageHandlerWrapper = param => {
}; };
}; };
export function getCriteriaStateObject(criteriaType: 'businessListings' | 'commercialPropertyListings' | 'brokerListings') {
let initialState;
if (criteriaType === 'businessListings') {
initialState = createEmptyBusinessListingCriteria();
} else if (criteriaType === 'commercialPropertyListings') {
initialState = createEmptyCommercialPropertyListingCriteria();
} else {
initialState = createEmptyUserListingCriteria();
}
const storedState = sessionStorage.getItem(`${criteriaType}`);
return storedState ? JSON.parse(storedState) : initialState;
}
export function routeListingWithState(router: Router, value: string, data: any) { export function routeListingWithState(router: Router, value: string, data: any) {
if (value === 'business') { if (value === 'business') {
router.navigate(['createBusinessListing'], { state: { data } }); router.navigate(['createBusinessListing'], { state: { data } });
@ -248,70 +211,6 @@ export function getDialogWidth(dimensions): string {
return dialogWidth; return dialogWidth;
} }
import { initFlowbite } from 'flowbite';
import onChange from 'on-change';
import { Subject, concatMap, delay, of } from 'rxjs';
import { User } from '../../../../bizmatch-server/src/models/db.model';
import { environment } from '../../environments/environment';
const flowbiteQueue = new Subject<() => void>();
flowbiteQueue.pipe(concatMap(item => of(item).pipe(delay(100)))).subscribe(x => {
x();
});
export function Flowbite() {
return function (target: any) {
const originalOnInit = target.prototype.ngOnInit;
target.prototype.ngOnInit = function () {
if (originalOnInit) {
originalOnInit.apply(this);
}
initFlowbiteFix();
};
};
}
export function initFlowbiteFix() {
flowbiteQueue.next(() => {
const elements = Array.from(document.querySelectorAll('*'));
const flowbiteElements: Element[] = [];
const initializedElements = Array.from(document.querySelectorAll('[flowbite-initialized]'));
for (const element of elements) {
const attributes = Array.from(element.attributes);
for (const attribute of attributes) {
if (attribute.name.startsWith('data-') && !initializedElements.includes(element)) {
flowbiteElements.push(element);
break;
}
}
}
for (const element of flowbiteElements) {
element.setAttribute('flowbite-initialized', '');
}
initFlowbite();
for (const element of flowbiteElements) {
const attributes: { name: string; value: string }[] = Array.from(element.attributes);
const dataAttributes = attributes.filter(attribute => attribute.name.startsWith('data-'));
for (const attribute of dataAttributes) {
element.setAttribute(attribute.name.replace('data-', 'fb-'), attribute.value);
element.removeAttribute(attribute.name);
}
}
});
}
// export function arraysEqual(arr1: any[], arr2: any[]): boolean {
// if (arr1.length !== arr2.length) return false;
// for (let i = 0; i < arr1.length; i++) {
// if (arr1[i] !== arr2[i]) return false;
// }
// return true;
// }
export function compareObjects<T extends object>(obj1: T, obj2: T, ignoreProperties: (keyof T)[] = []): number { export function compareObjects<T extends object>(obj1: T, obj2: T, ignoreProperties: (keyof T)[] = []): number {
let differences = 0; let differences = 0;
const keys = Object.keys(obj1) as Array<keyof T>; const keys = Object.keys(obj1) as Array<keyof T>;
@ -380,10 +279,29 @@ function arraysEqual(arr1: any[] | null | undefined, arr2: any[] | null | undefi
} }
return true; return true;
} }
export function assignProperties(target, source) {
for (let key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
// ----------------------------- // -----------------------------
// Criteria Proxy // Criteria Proxy
// ----------------------------- // -----------------------------
export function getCriteriaStateObject(criteriaType: 'businessListings' | 'commercialPropertyListings' | 'brokerListings') {
let initialState;
if (criteriaType === 'businessListings') {
initialState = createEmptyBusinessListingCriteria();
} else if (criteriaType === 'commercialPropertyListings') {
initialState = createEmptyCommercialPropertyListingCriteria();
} else {
initialState = createEmptyUserListingCriteria();
}
const storedState = sessionStorage.getItem(`${criteriaType}`);
return storedState ? JSON.parse(storedState) : initialState;
}
export function getCriteriaProxy(path: string, component: any): BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria { export function getCriteriaProxy(path: string, component: any): BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria {
if ('businessListings' === path) { if ('businessListings' === path) {
return createEnhancedProxy(getCriteriaStateObject('businessListings'), component); return createEnhancedProxy(getCriteriaStateObject('businessListings'), component);