diff --git a/bizmatch/angular.json b/bizmatch/angular.json
index d10bf7d..02ebf47 100644
--- a/bizmatch/angular.json
+++ b/bizmatch/angular.json
@@ -32,7 +32,8 @@
],
"styles": [
"src/styles.scss",
- "node_modules/quill/dist/quill.snow.css"
+ "node_modules/quill/dist/quill.snow.css",
+ "node_modules/leaflet/dist/leaflet.css"
]
},
"configurations": {
@@ -81,7 +82,9 @@
}
},
"defaultConfiguration": "development",
- "options": {"proxyConfig": "proxy.conf.json"}
+ "options": {
+ "proxyConfig": "proxy.conf.json"
+ }
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
@@ -102,7 +105,12 @@
"src/assets",
"cropped-Favicon-32x32.png",
"cropped-Favicon-180x180.png",
- "cropped-Favicon-191x192.png"
+ "cropped-Favicon-191x192.png",
+ {
+ "glob": "**/*",
+ "input": "./node_modules/leaflet/dist/images",
+ "output": "assets/"
+ }
],
"styles": [
"src/styles.scss"
@@ -116,4 +124,4 @@
"cli": {
"analytics": false
}
-}
+}
\ No newline at end of file
diff --git a/bizmatch/package.json b/bizmatch/package.json
index fa4d491..5cf23a8 100644
--- a/bizmatch/package.json
+++ b/bizmatch/package.json
@@ -23,6 +23,7 @@
"@angular/platform-browser-dynamic": "^18.1.3",
"@angular/platform-server": "^18.1.3",
"@angular/router": "^18.1.3",
+ "@bluehalo/ngx-leaflet": "^18.0.2",
"@fortawesome/angular-fontawesome": "^0.15.0",
"@fortawesome/fontawesome-free": "^6.5.2",
"@fortawesome/fontawesome-svg-core": "^6.5.2",
@@ -33,6 +34,7 @@
"@ngneat/until-destroy": "^10.0.0",
"@stripe/stripe-js": "^4.3.0",
"@types/cropperjs": "^1.3.0",
+ "@types/leaflet": "^1.9.12",
"@types/uuid": "^10.0.0",
"browser-bunyan": "^1.8.0",
"dayjs": "^1.11.11",
@@ -41,6 +43,7 @@
"jwt-decode": "^4.0.0",
"keycloak-angular": "^16.0.1",
"keycloak-js": "^25.0.1",
+ "leaflet": "^1.9.4",
"memoize-one": "^6.0.0",
"ng-gallery": "^11.0.0",
"ngx-currency": "^18.0.0",
diff --git a/bizmatch/src/app/pages/details/base-details.component.ts b/bizmatch/src/app/pages/details/base-details.component.ts
new file mode 100644
index 0000000..3a46fcf
--- /dev/null
+++ b/bizmatch/src/app/pages/details/base-details.component.ts
@@ -0,0 +1,94 @@
+import { Component } from '@angular/core';
+import { Control, DomEvent, DomUtil, icon, Icon, latLng, Layer, Map, MapOptions, Marker, tileLayer } from 'leaflet';
+import { BusinessListing, CommercialPropertyListing } from '../../../../../bizmatch-server/src/models/db.model';
+@Component({
+ selector: 'app-base-details',
+ template: ``,
+ standalone: true,
+ imports: [],
+})
+export abstract class BaseDetailsComponent {
+ // Leaflet-Map-Einstellungen
+ mapOptions: MapOptions;
+ mapLayers: Layer[] = [];
+ mapCenter: any;
+ mapZoom: number = 13; // Standardzoomlevel
+ protected listing: BusinessListing | CommercialPropertyListing;
+ constructor() {
+ this.mapOptions = {
+ layers: [
+ tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ attribution: '© OpenStreetMap contributors',
+ }),
+ ],
+ zoom: this.mapZoom,
+ center: latLng(0, 0), // Platzhalter, wird später gesetzt
+ };
+ }
+ protected configureMap() {
+ const latitude = this.listing.location.latitude;
+ const longitude = this.listing.location.longitude;
+
+ if (latitude && longitude) {
+ this.mapCenter = latLng(latitude, longitude);
+ this.mapLayers = [
+ tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ attribution: '© OpenStreetMap contributors',
+ }),
+ new Marker([latitude, longitude], {
+ icon: icon({
+ ...Icon.Default.prototype.options,
+ iconUrl: 'assets/leaflet/marker-icon.png',
+ iconRetinaUrl: 'assets/leaflet/marker-icon-2x.png',
+ shadowUrl: 'assets/leaflet/marker-shadow.png',
+ }),
+ }),
+ ];
+ this.mapOptions = {
+ ...this.mapOptions,
+ center: this.mapCenter,
+ zoom: this.mapZoom,
+ };
+ }
+ }
+ onMapReady(map: Map) {
+ if (this.listing.location.street) {
+ const addressControl = new Control({ position: 'topright' });
+
+ addressControl.onAdd = () => {
+ const container = DomUtil.create('div', 'address-control bg-white p-2 rounded shadow');
+ const address = `${this.listing.location.housenumber ? this.listing.location.housenumber : ''} ${this.listing.location.street}, ${
+ this.listing.location.name ? this.listing.location.name : this.listing.location.county
+ }, ${this.listing.location.state}`;
+ container.innerHTML = `
+ ${address}
+ View larger map
+ `;
+
+ // Verhindere, dass die Karte durch das Klicken des Links bewegt wird
+ DomEvent.disableClickPropagation(container);
+
+ // Füge einen Event Listener für den Link hinzu
+ const link = container.querySelector('#view-full-map') as HTMLElement;
+ if (link) {
+ DomEvent.on(link, 'click', (e: Event) => {
+ e.preventDefault();
+ this.openFullMap();
+ });
+ }
+
+ return container;
+ };
+
+ addressControl.addTo(map);
+ }
+ }
+ openFullMap() {
+ const latitude = this.listing.location.latitude;
+ const longitude = this.listing.location.longitude;
+ const address = `${this.listing.location.housenumber} ${this.listing.location.street}, ${this.listing.location.name ? this.listing.location.name : this.listing.location.county}, ${this.listing.location.state}`;
+ const url = `https://www.openstreetmap.org/?mlat=${latitude}&mlon=${longitude}#map=15/${latitude}/${longitude}`;
+
+ window.open(url, '_blank');
+ }
+}
diff --git a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.html b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.html
index 77f0cd2..33c5d66 100644
--- a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.html
+++ b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.html
@@ -56,6 +56,12 @@
+
+
diff --git a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts
index aaa0856..e396e29 100644
--- a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts
+++ b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts
@@ -1,6 +1,7 @@
import { Component } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
+import { LeafletModule } from '@bluehalo/ngx-leaflet';
import { KeycloakService } from 'keycloak-angular';
import { ShareButton } from 'ngx-sharebuttons/button';
import { lastValueFrom } from 'rxjs';
@@ -22,15 +23,18 @@ import { SelectOptionsService } from '../../../services/select-options.service';
import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module';
import { createMailInfo, map2User } from '../../../utils/utils';
+// Import für Leaflet
+// Benannte Importe für Leaflet
+import { BaseDetailsComponent } from '../base-details.component';
@Component({
selector: 'app-details-business-listing',
standalone: true,
- imports: [SharedModule, ValidatedInputComponent, ValidatedTextareaComponent, ShareButton, ValidatedNgSelectComponent],
+ imports: [SharedModule, ValidatedInputComponent, ValidatedTextareaComponent, ShareButton, ValidatedNgSelectComponent, LeafletModule],
providers: [],
templateUrl: './details-business-listing.component.html',
styleUrl: '../details.scss',
})
-export class DetailsBusinessListingComponent {
+export class DetailsBusinessListingComponent extends BaseDetailsComponent {
// listings: Array;
responsiveOptions = [
{
@@ -50,7 +54,7 @@ export class DetailsBusinessListingComponent {
},
];
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
- listing: BusinessListing;
+ override listing: BusinessListing;
mailinfo: MailInfo;
environment = environment;
keycloakUser: KeycloakUser;
@@ -60,6 +64,7 @@ export class DetailsBusinessListingComponent {
private history: string[] = [];
ts = new Date().getTime();
env = environment;
+
constructor(
private activatedRoute: ActivatedRoute,
private listingsService: ListingsService,
@@ -76,12 +81,14 @@ export class DetailsBusinessListingComponent {
public emailService: EMailService,
private geoService: GeoService,
) {
+ super();
this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
this.history.push(event.urlAfterRedirects);
}
});
this.mailinfo = { sender: {}, email: '', url: environment.mailinfoUrl };
+ // Initialisiere die Map-Optionen
}
async ngOnInit() {
@@ -96,11 +103,15 @@ export class DetailsBusinessListingComponent {
this.auditService.createEvent(this.listing.id, 'view', this.user?.email);
this.listingUser = await this.userService.getByMail(this.listing.email);
this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description);
+ if (this.listing.location.street) {
+ this.configureMap();
+ }
} catch (error) {
this.auditService.log({ severity: 'error', text: error.error.message });
this.router.navigate(['notfound']);
}
}
+
ngOnDestroy() {
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
}
@@ -139,7 +150,7 @@ export class DetailsBusinessListingComponent {
}
const result = [
{ label: 'Category', value: this.selectOptions.getBusiness(this.listing.type) },
- { label: 'Located in', value: `${this.listing.location.name}, ${this.selectOptions.getState(this.listing.location.state)}` },
+ { label: 'Located in', value: `${this.listing.location.name ? this.listing.location.name : this.listing.location.county}, ${this.selectOptions.getState(this.listing.location.state)}` },
{ label: 'Asking Price', value: `$${this.listing.price?.toLocaleString()}` },
{ label: 'Sales revenue', value: `$${this.listing.salesRevenue?.toLocaleString()}` },
{ label: 'Cash flow', value: `$${this.listing.cashFlow?.toLocaleString()}` },
diff --git a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.html b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.html
index b126409..fcb9046 100644
--- a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.html
+++ b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.html
@@ -52,6 +52,12 @@
+
+
diff --git a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts
index 31db425..b5fa0bd 100644
--- a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts
+++ b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts
@@ -1,6 +1,7 @@
import { Component, NgZone } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
+import { LeafletModule } from '@bluehalo/ngx-leaflet';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { KeycloakService } from 'keycloak-angular';
import { GalleryModule, ImageItem } from 'ng-gallery';
@@ -24,16 +25,17 @@ import { SelectOptionsService } from '../../../services/select-options.service';
import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module';
import { createMailInfo, map2User } from '../../../utils/utils';
+import { BaseDetailsComponent } from '../base-details.component';
@Component({
selector: 'app-details-commercial-property-listing',
standalone: true,
- imports: [SharedModule, ValidatedInputComponent, ValidatedTextareaComponent, ShareButton, ValidatedNgSelectComponent, GalleryModule],
+ imports: [SharedModule, ValidatedInputComponent, ValidatedTextareaComponent, ShareButton, ValidatedNgSelectComponent, GalleryModule, LeafletModule],
providers: [],
templateUrl: './details-commercial-property-listing.component.html',
styleUrl: '../details.scss',
})
-export class DetailsCommercialPropertyListingComponent {
+export class DetailsCommercialPropertyListingComponent extends BaseDetailsComponent {
responsiveOptions = [
{
breakpoint: '1199px',
@@ -52,7 +54,7 @@ export class DetailsCommercialPropertyListingComponent {
},
];
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
- listing: CommercialPropertyListing;
+ override listing: CommercialPropertyListing;
criteria: CommercialPropertyListingCriteria;
mailinfo: MailInfo;
environment = environment;
@@ -83,9 +85,8 @@ export class DetailsCommercialPropertyListingComponent {
private auditService: AuditService,
private emailService: EMailService,
) {
+ super();
this.mailinfo = { sender: {}, email: '', url: environment.mailinfoUrl };
-
- // this.criteria = onChange(getCriteriaStateObject('commercialProperty'), getSessionStorageHandler);
}
async ngOnInit() {
@@ -106,7 +107,7 @@ export class DetailsCommercialPropertyListingComponent {
this.propertyDetails = [
{ label: 'Property Category', value: this.selectOptions.getCommercialProperty(this.listing.type) },
{ label: 'Located in', value: this.selectOptions.getState(this.listing.location.state) },
- { label: 'City', value: this.listing.location.name },
+ { label: this.listing.location.name ? 'City' : 'County', value: this.listing.location.name ? this.listing.location.name : this.listing.location.county },
{ label: 'Asking Price:', value: `$${this.listing.price?.toLocaleString()}` },
];
if (this.listing.draft) {
@@ -116,6 +117,9 @@ export class DetailsCommercialPropertyListingComponent {
const imageURL = `${this.env.imageBaseUrl}/pictures/property/${this.listing.imagePath}/${this.listing.serialId}/${image}`;
this.images.push(new ImageItem({ src: imageURL, thumb: imageURL }));
});
+ if (this.listing.location.street) {
+ this.configureMap();
+ }
} catch (error) {
this.auditService.log({ severity: 'error', text: error.error.message });
this.router.navigate(['notfound']);
diff --git a/bizmatch/src/app/pages/details/details.scss b/bizmatch/src/app/pages/details/details.scss
index 3273eaf..1ed42e5 100644
--- a/bizmatch/src/app/pages/details/details.scss
+++ b/bizmatch/src/app/pages/details/details.scss
@@ -77,3 +77,23 @@ button.share {
top: 10px;
}
}
+/* details.scss */
+
+/* Stil für das Adress-Info-Feld */
+.address-control {
+ background: rgba(255, 255, 255, 0.9);
+ padding: 10px;
+ border-radius: 5px;
+ box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65);
+ font-size: 14px;
+ line-height: 1.4;
+}
+
+.address-control a {
+ color: #007bff;
+ text-decoration: none;
+}
+
+.address-control a:hover {
+ text-decoration: underline;
+}
diff --git a/bizmatch/src/app/pages/listings/business-listings/business-listings.component.html b/bizmatch/src/app/pages/listings/business-listings/business-listings.component.html
index 67937a6..eec18e7 100644
--- a/bizmatch/src/app/pages/listings/business-listings/business-listings.component.html
+++ b/bizmatch/src/app/pages/listings/business-listings/business-listings.component.html
@@ -33,7 +33,7 @@
Sales revenue: {{ listing.salesRevenue | currency : 'USD' : 'symbol' : '1.0-0' }}
Net profit: {{ listing.cashFlow | currency : 'USD' : 'symbol' : '1.0-0' }}
-
Location: {{ listing.location.name }}
+
Location: {{ listing.location.name ? listing.location.name : listing.location.county }}
Established: {{ listing.established }}

diff --git a/bizmatch/src/app/pages/listings/commercial-property-listings/commercial-property-listings.component.html b/bizmatch/src/app/pages/listings/commercial-property-listings/commercial-property-listings.component.html
index 071305b..20b871b 100644
--- a/bizmatch/src/app/pages/listings/commercial-property-listings/commercial-property-listings.component.html
+++ b/bizmatch/src/app/pages/listings/commercial-property-listings/commercial-property-listings.component.html
@@ -23,7 +23,7 @@
Draft
}
-
{{ listing.location.name }}
+
{{ listing.location.name ? listing.location.name : listing.location.county }}
{{ listing.price | currency : 'USD' : 'symbol' : '1.0-0' }}