feat: Initialize Angular SSR application with core pages, components, and server setup.
This commit is contained in:
@@ -2,7 +2,6 @@ import { ChangeDetectorRef, 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 { ShareButton } from 'ngx-sharebuttons/button';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { BusinessListing, EventTypeEnum, ShareByEMail, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||
import { KeycloakUser, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||
@@ -25,15 +24,16 @@ 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
|
||||
// Note: Leaflet requires browser environment - protected by isBrowser checks in base class
|
||||
import { circle, Circle, Control, DomEvent, DomUtil, icon, Icon, latLng, LatLngBounds, Marker, polygon, Polygon, tileLayer } from 'leaflet';
|
||||
import dayjs from 'dayjs';
|
||||
import { AuthService } from '../../../services/auth.service';
|
||||
import { BaseDetailsComponent } from '../base-details.component';
|
||||
import { ShareButton } from 'ngx-sharebuttons/button';
|
||||
@Component({
|
||||
selector: 'app-details-business-listing',
|
||||
standalone: true,
|
||||
imports: [SharedModule, ValidatedInputComponent, ValidatedTextareaComponent, ShareButton, ValidatedNgSelectComponent, LeafletModule, BreadcrumbsComponent],
|
||||
imports: [SharedModule, ValidatedInputComponent, ValidatedTextareaComponent, ValidatedNgSelectComponent, LeafletModule, BreadcrumbsComponent, ShareButton],
|
||||
providers: [],
|
||||
templateUrl: './details-business-listing.component.html',
|
||||
styleUrl: '../details.scss',
|
||||
@@ -231,28 +231,27 @@ export class DetailsBusinessListingComponent extends BaseDetailsComponent {
|
||||
{ label: 'Category', value: this.selectOptions.getBusiness(this.listing.type) },
|
||||
{
|
||||
label: 'Located in',
|
||||
value: `${this.listing.location.name ? this.listing.location.name : this.listing.location.county ? this.listing.location.county : ''} ${
|
||||
this.listing.location.name || this.listing.location.county ? ', ' : ''
|
||||
}${this.selectOptions.getState(this.listing.location.state)}`,
|
||||
value: `${this.listing.location.name ? this.listing.location.name : this.listing.location.county ? this.listing.location.county : ''} ${this.listing.location.name || this.listing.location.county ? ', ' : ''
|
||||
}${this.selectOptions.getState(this.listing.location.state)}`,
|
||||
},
|
||||
{ label: 'Asking Price', value: `${this.listing.price ? `$${this.listing.price.toLocaleString()}` : 'undisclosed '}` },
|
||||
{ label: 'Sales revenue', value: `${this.listing.salesRevenue ? `$${this.listing.salesRevenue.toLocaleString()}` : 'undisclosed '}` },
|
||||
{ label: 'Cash flow', value: `${this.listing.cashFlow ? `$${this.listing.cashFlow.toLocaleString()}` : 'undisclosed '}` },
|
||||
...(this.listing.ffe
|
||||
? [
|
||||
{
|
||||
label: 'Furniture, Fixtures / Equipment Value (FFE)',
|
||||
value: `$${this.listing.ffe.toLocaleString()}`,
|
||||
},
|
||||
]
|
||||
{
|
||||
label: 'Furniture, Fixtures / Equipment Value (FFE)',
|
||||
value: `$${this.listing.ffe.toLocaleString()}`,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(this.listing.inventory
|
||||
? [
|
||||
{
|
||||
label: 'Inventory at Cost Value',
|
||||
value: `$${this.listing.inventory.toLocaleString()}`,
|
||||
},
|
||||
]
|
||||
{
|
||||
label: 'Inventory at Cost Value',
|
||||
value: `$${this.listing.inventory.toLocaleString()}`,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ label: 'Type of Real Estate', value: typeOfRealEstate },
|
||||
{ label: 'Employees', value: this.listing.employees },
|
||||
@@ -308,6 +307,26 @@ export class DetailsBusinessListingComponent extends BaseDetailsComponent {
|
||||
createEvent(eventType: EventTypeEnum) {
|
||||
this.auditService.createEvent(this.listing.id, eventType, this.user?.email);
|
||||
}
|
||||
|
||||
shareToFacebook() {
|
||||
const url = encodeURIComponent(window.location.href);
|
||||
window.open(`https://www.facebook.com/sharer/sharer.php?u=${url}`, '_blank', 'width=600,height=400');
|
||||
this.createEvent('facebook');
|
||||
}
|
||||
|
||||
shareToTwitter() {
|
||||
const url = encodeURIComponent(window.location.href);
|
||||
const text = encodeURIComponent(this.listing?.title || 'Check out this business listing');
|
||||
window.open(`https://twitter.com/intent/tweet?url=${url}&text=${text}`, '_blank', 'width=600,height=400');
|
||||
this.createEvent('x');
|
||||
}
|
||||
|
||||
shareToLinkedIn() {
|
||||
const url = encodeURIComponent(window.location.href);
|
||||
window.open(`https://www.linkedin.com/sharing/share-offsite/?url=${url}`, '_blank', 'width=600,height=400');
|
||||
this.createEvent('linkedin');
|
||||
}
|
||||
|
||||
getDaysListed() {
|
||||
return dayjs().diff(this.listing.created, 'day');
|
||||
}
|
||||
@@ -330,7 +349,7 @@ export class DetailsBusinessListingComponent extends BaseDetailsComponent {
|
||||
|
||||
// Check if we have valid coordinates (null-safe check)
|
||||
if (latitude !== null && latitude !== undefined &&
|
||||
longitude !== null && longitude !== undefined) {
|
||||
longitude !== null && longitude !== undefined) {
|
||||
|
||||
this.mapCenter = latLng(latitude, longitude);
|
||||
|
||||
@@ -340,23 +359,23 @@ export class DetailsBusinessListingComponent extends BaseDetailsComponent {
|
||||
|
||||
// Fetch city boundary from Nominatim API
|
||||
this.geoService.getCityBoundary(cityName, state).subscribe({
|
||||
next: (data) => {
|
||||
if (data && data.length > 0 && data[0].geojson && data[0].geojson.type === 'Polygon') {
|
||||
const coordinates = data[0].geojson.coordinates[0]; // Get outer boundary
|
||||
next: (data) => {
|
||||
if (data && data.length > 0 && data[0].geojson && data[0].geojson.type === 'Polygon') {
|
||||
const coordinates = data[0].geojson.coordinates[0]; // Get outer boundary
|
||||
|
||||
// Convert GeoJSON coordinates [lon, lat] to Leaflet LatLng [lat, lon]
|
||||
const latlngs = coordinates.map((coord: number[]) => latLng(coord[1], coord[0]));
|
||||
// Convert GeoJSON coordinates [lon, lat] to Leaflet LatLng [lat, lon]
|
||||
const latlngs = coordinates.map((coord: number[]) => latLng(coord[1], coord[0]));
|
||||
|
||||
// Create red outlined polygon for city boundary
|
||||
const cityPolygon = polygon(latlngs, {
|
||||
color: '#ef4444', // Red color (like Google Maps)
|
||||
fillColor: '#ef4444',
|
||||
fillOpacity: 0.1,
|
||||
weight: 2
|
||||
});
|
||||
// Create red outlined polygon for city boundary
|
||||
const cityPolygon = polygon(latlngs, {
|
||||
color: '#ef4444', // Red color (like Google Maps)
|
||||
fillColor: '#ef4444',
|
||||
fillOpacity: 0.1,
|
||||
weight: 2
|
||||
});
|
||||
|
||||
// Add popup to polygon
|
||||
cityPolygon.bindPopup(`
|
||||
// Add popup to polygon
|
||||
cityPolygon.bindPopup(`
|
||||
<div style="padding: 8px;">
|
||||
<strong>General Area:</strong><br/>
|
||||
${cityName}, ${county ? county + ', ' : ''}${state}<br/>
|
||||
@@ -364,74 +383,74 @@ export class DetailsBusinessListingComponent extends BaseDetailsComponent {
|
||||
</div>
|
||||
`);
|
||||
|
||||
this.mapLayers = [
|
||||
tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors',
|
||||
}),
|
||||
cityPolygon
|
||||
];
|
||||
this.mapLayers = [
|
||||
tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors',
|
||||
}),
|
||||
cityPolygon
|
||||
];
|
||||
|
||||
// Fit map to polygon bounds
|
||||
const bounds = cityPolygon.getBounds();
|
||||
this.mapOptions = {
|
||||
...this.mapOptions,
|
||||
center: bounds.getCenter(),
|
||||
zoom: this.mapZoom,
|
||||
};
|
||||
} else if (data && data.length > 0 && data[0].geojson && data[0].geojson.type === 'MultiPolygon') {
|
||||
// Handle MultiPolygon case (cities with multiple areas)
|
||||
const allPolygons: Polygon[] = [];
|
||||
// Fit map to polygon bounds
|
||||
const bounds = cityPolygon.getBounds();
|
||||
this.mapOptions = {
|
||||
...this.mapOptions,
|
||||
center: bounds.getCenter(),
|
||||
zoom: this.mapZoom,
|
||||
};
|
||||
} else if (data && data.length > 0 && data[0].geojson && data[0].geojson.type === 'MultiPolygon') {
|
||||
// Handle MultiPolygon case (cities with multiple areas)
|
||||
const allPolygons: Polygon[] = [];
|
||||
|
||||
data[0].geojson.coordinates.forEach((polygonCoords: number[][][]) => {
|
||||
const latlngs = polygonCoords[0].map((coord: number[]) => latLng(coord[1], coord[0]));
|
||||
const cityPolygon = polygon(latlngs, {
|
||||
color: '#ef4444',
|
||||
fillColor: '#ef4444',
|
||||
fillOpacity: 0.1,
|
||||
weight: 2
|
||||
data[0].geojson.coordinates.forEach((polygonCoords: number[][][]) => {
|
||||
const latlngs = polygonCoords[0].map((coord: number[]) => latLng(coord[1], coord[0]));
|
||||
const cityPolygon = polygon(latlngs, {
|
||||
color: '#ef4444',
|
||||
fillColor: '#ef4444',
|
||||
fillOpacity: 0.1,
|
||||
weight: 2
|
||||
});
|
||||
allPolygons.push(cityPolygon);
|
||||
});
|
||||
allPolygons.push(cityPolygon);
|
||||
});
|
||||
|
||||
// Add popup to first polygon
|
||||
if (allPolygons.length > 0) {
|
||||
allPolygons[0].bindPopup(`
|
||||
// Add popup to first polygon
|
||||
if (allPolygons.length > 0) {
|
||||
allPolygons[0].bindPopup(`
|
||||
<div style="padding: 8px;">
|
||||
<strong>General Area:</strong><br/>
|
||||
${cityName}, ${county ? county + ', ' : ''}${state}<br/>
|
||||
<small style="color: #666;">City boundary shown for privacy.<br/>Exact location provided after contact.</small>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
this.mapLayers = [
|
||||
tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors',
|
||||
}),
|
||||
...allPolygons
|
||||
];
|
||||
this.mapLayers = [
|
||||
tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors',
|
||||
}),
|
||||
...allPolygons
|
||||
];
|
||||
|
||||
// Calculate combined bounds
|
||||
if (allPolygons.length > 0) {
|
||||
const bounds = new LatLngBounds([]);
|
||||
allPolygons.forEach(p => bounds.extend(p.getBounds()));
|
||||
this.mapOptions = {
|
||||
...this.mapOptions,
|
||||
center: bounds.getCenter(),
|
||||
zoom: this.mapZoom,
|
||||
};
|
||||
// Calculate combined bounds
|
||||
if (allPolygons.length > 0) {
|
||||
const bounds = new LatLngBounds([]);
|
||||
allPolygons.forEach(p => bounds.extend(p.getBounds()));
|
||||
this.mapOptions = {
|
||||
...this.mapOptions,
|
||||
center: bounds.getCenter(),
|
||||
zoom: this.mapZoom,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Fallback: Use circle if no polygon data available
|
||||
this.useFallbackCircle(latitude, longitude, cityName, county, state);
|
||||
}
|
||||
} else {
|
||||
// Fallback: Use circle if no polygon data available
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Error fetching city boundary:', err);
|
||||
// Fallback: Use circle on error
|
||||
this.useFallbackCircle(latitude, longitude, cityName, county, state);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Error fetching city boundary:', err);
|
||||
// Fallback: Use circle on error
|
||||
this.useFallbackCircle(latitude, longitude, cityName, county, state);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
// Case 2: Only state available (NEW) - show state-level circle
|
||||
else if (state) {
|
||||
|
||||
Reference in New Issue
Block a user