added new adbrimofcp map

This commit is contained in:
2025-09-16 12:32:06 +02:00
parent 76a9fd5a02
commit 219c42a1f6
2 changed files with 148 additions and 43 deletions

View File

@@ -12,7 +12,6 @@
flex-wrap: wrap;
}
/* Added hover effect for custom-styled buttons */
.map-filter-container .btn {
transition: opacity 0.15s ease-in-out;
}
@@ -20,7 +19,6 @@
opacity: 0.85;
}
.marker-label {
background: transparent !important;
border: none !important;
@@ -61,7 +59,6 @@ div.leaflet-marker-icon.custom-div-icon {
border: none !important;
}
/* Base style for all RIMO markers */
.rimo-marker {
display: flex;
justify-content: center;
@@ -79,14 +76,13 @@ div.leaflet-marker-icon.custom-div-icon {
color: white;
}
/* --- New Styles for FCP Markers --- */
.fcp-marker {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background-color: #ffc107; /* Yellow */
background-color: #ffc107;
color: #333;
font-size: 11px;
font-weight: bold;
@@ -95,7 +91,67 @@ div.leaflet-marker-icon.custom-div-icon {
box-shadow: 0 0 5px rgba(0, 0, 0, 0.4);
}
/* Specific styles for each rimo_type */
/* --- Styles for FCP Popup --- */
.fcp-popup-content {
font-family: Arial, sans-serif;
width: 320px;
font-size: 0.8rem;
line-height: 1.4;
}
.fcp-popup-content h5 {
margin-top: 0;
margin-bottom: 10px;
color: #333;
border-bottom: 1px solid #ddd;
padding-bottom: 5px;
font-size: 1rem;
font-weight: 600;
}
.fcp-popup-content a {
color: #007bff;
text-decoration: none;
margin-bottom: 15px;
display: inline-block;
}
.fcp-popup-content a:hover {
text-decoration: underline;
}
.fcp-popup-content .summary-block {
margin-bottom: 15px;
}
.fcp-popup-content .summary-block strong {
display: block;
margin-bottom: 5px;
color: #555;
font-weight: 600;
}
.fcp-popup-content table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
margin-top: 10px;
}
.fcp-popup-content th,
.fcp-popup-content td {
padding: 6px 8px;
border: 1px solid #ddd;
text-align: left;
vertical-align: middle;
}
.fcp-popup-content th {
font-weight: bold;
}
.fcp-popup-content thead tr {
background-color: #f2f2f2;
}
.fcp-popup-content tbody tr:nth-child(even) {
background-color: #f9f9f9;
}
.fcp-popup-content .text-center {
text-align: center;
}
/* RIMO Marker Colors */
.marker-greenfield { background-color: #28a745; }
.marker-residential { background-color: #007bff; }
.marker-company { background-color: #ffc107; }

View File

@@ -2,7 +2,7 @@ Vue.component('PreorderRimoTypeMap', {
data() {
return {
mapMarkers: [],
fcpMarkers: [], // For FCP data
fcpMarkers: [],
isLoading: false,
window,
fetchUrl: window.TT_CONFIG.BASE_PATH + '/Preorder/RimoTypeMapData',
@@ -15,7 +15,6 @@ Vue.component('PreorderRimoTypeMap', {
},
mapInstance: null,
activeFilters: [],
// Single source of truth for RIMO type definitions
rimoTypeDefs: {
greenfield: { text: 'Greenfield', icon: 'fas fa-tree', color: '#28a745' },
residential: { text: 'Wohngebiet', icon: 'fas fa-home', color: '#007bff' },
@@ -27,9 +26,6 @@ Vue.component('PreorderRimoTypeMap', {
};
},
computed: {
/**
* Dynamically generates filter options from the rimoTypeDefs object.
*/
filterOptions() {
return Object.entries(this.rimoTypeDefs).map(([value, defs]) => ({
value,
@@ -37,10 +33,6 @@ Vue.component('PreorderRimoTypeMap', {
icon: defs.icon
}));
},
/**
* Combines filtered RIMO markers with all FCP markers for map display.
* FCPs are always visible, regardless of filters.
*/
filteredMapMarkers() {
const rimoMarkers = this.activeFilters.length === 0
? this.mapMarkers
@@ -72,9 +64,6 @@ Vue.component('PreorderRimoTypeMap', {
}
},
methods: {
/**
* Main data fetching orchestrator. Fetches RIMO and FCP data in parallel.
*/
async fetchAllMapData() {
if (!this.selectedCampaign) return;
this.isLoading = true;
@@ -87,15 +76,11 @@ Vue.component('PreorderRimoTypeMap', {
]);
} catch (err) {
console.error("Failed to load map data:", err);
// A general error message is sufficient as specific ones are shown by sub-methods.
} finally {
this.isLoading = false;
}
},
/**
* Fetches and processes RIMO location data.
*/
async fetchRimoData() {
try {
const response = await axios.post(this.fetchUrl, { campaignId: this.selectedCampaign });
@@ -106,19 +91,33 @@ Vue.component('PreorderRimoTypeMap', {
}
} catch (err) {
window.notify('error', 'Laden der RIMO-Kartendaten fehlgeschlagen.');
throw err; // Re-throw to be caught by the orchestrator
throw err;
}
},
/**
* Fetches and processes FCP (Fiber Connection Point) data.
*/
async fetchFCPData() {
try {
const url = `${window.TT_CONFIG.BASE_PATH}/Preorder/Api?do=getFCPsForCampaign&campaign_id=${this.selectedCampaign}`;
const response = await axios.get(url);
if (response.data.status === 'OK' && Array.isArray(response.data.result)) {
this.fcpMarkers = response.data.result.map(fcp => ({
// Step 1: Fetch FCP locations
const fcpLocationUrl = `${window.TT_CONFIG.BASE_PATH}/Preorder/Api?do=getFCPsForCampaign&campaign_id=${this.selectedCampaign}`;
const fcpResponse = await axios.get(fcpLocationUrl);
if (fcpResponse.data.status !== "OK" || !fcpResponse.data.result?.length) {
console.warn('No FCP locations found or API error.');
return;
}
const fcpLocations = fcpResponse.data.result;
// Step 2: Fetch FCP stats using the IDs from the first call
const fcpIds = fcpLocations.map(fcp => fcp.real_id);
const statsUrl = `${window.TT_CONFIG.BASE_PATH}/Preorder/Api?do=getRimoFcpStats`;
const statsResponse = await axios.post(statsUrl, { fcp_ids: fcpIds });
const fcpStats = statsResponse.data.status === "OK" ? statsResponse.data.result : [];
const statsMap = new Map(fcpStats.map(s => [s.fcp_id, s]));
// Step 3: Create markers with detailed popup content
this.fcpMarkers = fcpLocations.map(fcp => {
const stat = statsMap.get(String(fcp.real_id));
return {
lat: fcp.lat,
lng: fcp.lng,
options: {
@@ -128,22 +127,71 @@ Vue.component('PreorderRimoTypeMap', {
iconSize: [28, 28],
iconAnchor: [14, 14],
},
tooltip: {
content: `FCP: ${fcp.text}`,
direction: 'top',
},
zIndexOffset: 500 // Keep FCPs slightly above other markers
asyncPopupContent: () => this.generateFcpPopupHtml(fcp, stat),
zIndexOffset: 500
},
}));
} else {
console.warn('Could not retrieve FCP data or data format is invalid.');
}
};
});
} catch (err) {
window.notify('warning', 'Laden der FCP-Daten fehlgeschlagen. Die Karte wird ohne sie angezeigt.');
// Do not re-throw; failing to load FCPs should not block the entire map.
console.error("FCP data fetch failed:", err);
}
},
generateFcpPopupHtml(fcp, fcpStat) {
const googleMapsLink = `https://www.google.com/maps/search/?api=1&query=${fcp.lat},${fcp.lng}`;
let statsHtml;
if (!fcpStat) {
statsHtml = `<p>Keine Statistiken für diesen FCP gefunden.</p>`;
} else {
const tableRows = Object.entries(fcpStat.counts_by_rimo_type || {})
.map(([type, counts]) => {
const normalizedType = this.getNormalizedRimoType(type);
const typeDef = this.rimoTypeDefs[normalizedType] || this.rimoTypeDefs.other;
const typeDisplay = `<i class="${typeDef.icon} mr-2" style="color: ${typeDef.color};"></i>${typeDef.text}`;
return `
<tr>
<td>${typeDisplay}</td>
<td class="text-center">${counts.hausnummer_count}</td>
<td class="text-center">${counts.wohneinheit_count}</td>
<td class="text-center">${counts.preorder_count}</td>
</tr>`;
}).join('');
const table = tableRows ? `
<table>
<thead>
<tr>
<th>Typ</th>
<th class="text-center" title="Gebäude">GEB</th>
<th class="text-center" title="Wohneinheiten">WE</th>
<th class="text-center" title="Bestellungen">BE</th>
</tr>
</thead>
<tbody>${tableRows}</tbody>
</table>` : '<p>Keine Detail-Statistiken verfügbar.</p>';
statsHtml = `
<div class="summary-block">
<strong>Zusammenfassung</strong>
<span>Gebäude: <b>${fcpStat.total_hausnummer_count}</b></span><br>
<span>Wohneinheiten: <b>${fcpStat.total_wohneinheit_count}</b></span><br>
<span>Bestellungen: <b>${fcpStat.total_active_preorders}</b></span>
</div>
${table}`;
}
return `
<div class="fcp-popup-content">
<h5><i class="fas fa-broadcast-tower mr-2"></i>FCP: ${fcp.text}</h5>
<a href='${googleMapsLink}' target='_blank'>
<i class="fas fa-map-marker-alt mr-1"></i> In Google Maps anzeigen
</a>
${statsHtml}
</div>`;
},
processData(data) {
const groupedData = {};
@@ -220,12 +268,13 @@ Vue.component('PreorderRimoTypeMap', {
getNormalizedRimoType(type) {
const lowerType = (type || '').toLowerCase();
if (lowerType.includes('2/3')) return 'residential'; // Catches '2/3' as residential
if (lowerType.includes('2/3')) return 'residential';
if (lowerType.includes('greenfield')) return 'greenfield';
if (lowerType.includes('residential')) return 'residential';
if (lowerType.includes('multiple dwelling')) return 'multiple-dwelling';
if (lowerType.includes('multiple dwelling') || lowerType.includes('multiple dwellings')) return 'multiple-dwelling';
if (lowerType.includes('company') || lowerType.includes('commercial')) return 'company';
if (lowerType.includes('public') || lowerType.includes('school')) return 'public';
if (lowerType.includes('unknown')) return 'other';
return 'other';
},
@@ -299,7 +348,7 @@ Vue.component('PreorderRimoTypeMap', {
</button>
</div>
<div style="height: 100%; position: relative; flex-grow: 1;">
<div v-if="!isLoading && mapMarkers.length === 0" class="alert alert-info m-3">
<div v-if="!isLoading && mapMarkers.length === 0 && fcpMarkers.length === 0" class="alert alert-info m-3">
Keine Standorte für die ausgewählte Kampagne gefunden.
</div>
<tt-map ref="ttMap" :markers-data="filteredMapMarkers" :loading="isLoading" :config="mapConfig" class="preorder-map-container"></tt-map>