Issue #55 View on map
This commit is contained in:
parent
8595e70ceb
commit
c00c2caccc
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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}<br/>
|
||||
<a href="#" id="view-full-map">View larger map</a>
|
||||
`;
|
||||
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
|
|
@ -56,6 +56,12 @@
|
|||
<share-button button="x" showText="true" (click)="createEvent('x')"></share-button>
|
||||
<share-button button="linkedin" showText="true" (click)="createEvent('linkedin')"></share-button>
|
||||
</div>
|
||||
<!-- Karte hinzufügen, wenn Straße vorhanden ist -->
|
||||
<div *ngIf="listing.location.street" class="mt-6">
|
||||
<h2 class="text-lg font-semibold mb-2">Location Map</h2>
|
||||
<!-- <div style="height: 300px" leaflet [leafletOptions]="mapOptions" [leafletLayers]="mapLayers" [leafletCenter]="mapCenter" [leafletZoom]="mapZoom"></div> -->
|
||||
<div style="height: 400px" leaflet [leafletOptions]="mapOptions" [leafletLayers]="mapLayers" [leafletCenter]="mapCenter" [leafletZoom]="mapZoom" (leafletMapReady)="onMapReady($event)"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right column -->
|
||||
|
|
|
|||
|
|
@ -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<BusinessListing>;
|
||||
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()}` },
|
||||
|
|
|
|||
|
|
@ -52,6 +52,12 @@
|
|||
<share-button button="x" showText="true" (click)="createEvent('x')"></share-button>
|
||||
<share-button button="linkedin" showText="true" (click)="createEvent('linkedin')"></share-button>
|
||||
</div>
|
||||
<!-- Karte hinzufügen, wenn Straße vorhanden ist -->
|
||||
<div *ngIf="listing.location.street" class="mt-6">
|
||||
<h2 class="text-lg font-semibold mb-2">Location Map</h2>
|
||||
<!-- <div style="height: 300px" leaflet [leafletOptions]="mapOptions" [leafletLayers]="mapLayers" [leafletCenter]="mapCenter" [leafletZoom]="mapZoom"></div> -->
|
||||
<div style="height: 400px" leaflet [leafletOptions]="mapOptions" [leafletLayers]="mapLayers" [leafletCenter]="mapCenter" [leafletZoom]="mapZoom" (leafletMapReady)="onMapReady($event)"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full lg:w-1/2 mt-6 lg:mt-0">
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
</p>
|
||||
<p class="text-sm text-gray-600 mb-2"><strong>Sales revenue:</strong> {{ listing.salesRevenue | currency : 'USD' : 'symbol' : '1.0-0' }}</p>
|
||||
<p class="text-sm text-gray-600 mb-2"><strong>Net profit:</strong> {{ listing.cashFlow | currency : 'USD' : 'symbol' : '1.0-0' }}</p>
|
||||
<p class="text-sm text-gray-600 mb-2"><strong>Location:</strong> {{ listing.location.name }}</p>
|
||||
<p class="text-sm text-gray-600 mb-2"><strong>Location:</strong> {{ listing.location.name ? listing.location.name : listing.location.county }}</p>
|
||||
<p class="text-sm text-gray-600 mb-4"><strong>Established:</strong> {{ listing.established }}</p>
|
||||
|
||||
<img src="{{ env.imageBaseUrl }}/pictures/logo/{{ listing.imageName }}.avif?_ts={{ ts }}" alt="Company logo" class="absolute bottom-[80px] right-[20px] h-[45px] w-auto" />
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
<span class="bg-red-100 text-red-800 text-sm font-medium me-2 ml-2 px-2.5 py-0.5 rounded dark:bg-red-900 dark:text-red-300">Draft</span>
|
||||
}
|
||||
</h3>
|
||||
<p class="text-gray-600 mb-2">{{ listing.location.name }}</p>
|
||||
<p class="text-gray-600 mb-2">{{ listing.location.name ? listing.location.name : listing.location.county }}</p>
|
||||
<p class="text-xl font-bold mb-4">{{ listing.price | currency : 'USD' : 'symbol' : '1.0-0' }}</p>
|
||||
<div class="flex-grow"></div>
|
||||
<button [routerLink]="['/details-commercial-property-listing', listing.id]" class="bg-green-500 text-white px-4 py-2 rounded-full w-full hover:bg-green-600 transition duration-300 mt-auto">
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 696 B |
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 618 B |
|
|
@ -11,6 +11,8 @@
|
|||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
@import 'ngx-sharebuttons/themes/default';
|
||||
/* styles.scss */
|
||||
@import 'leaflet/dist/leaflet.css';
|
||||
:root {
|
||||
--text-color-secondary: rgba(255, 255, 255);
|
||||
--wrapper-width: 1491px;
|
||||
|
|
@ -95,3 +97,20 @@ input::placeholder,
|
|||
textarea::placeholder {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
/* Fix für Marker-Icons in Leaflet */
|
||||
.leaflet-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.leaflet-marker-icon {
|
||||
/* Optional: Anpassen der Marker-Icon-Größe */
|
||||
width: 25px;
|
||||
height: 41px;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue