added new adbrimofcp map
This commit is contained in:
@@ -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; }
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user