added new adbrimofcp map
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
.preorder-map-container {
|
||||
height: 100%; /* Changed from 85vh to fill parent */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.map-filter-container {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.marker-label {
|
||||
/* This is now the outer, transparent container provided by Leaflet. */
|
||||
/* We remove its background and border to let the inner div's style show through. */
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
padding: 0 !important;
|
||||
|
||||
/* These properties are for Leaflet's positioning and events */
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
transition: visibility 0.2s, opacity 0.2s linear;
|
||||
}
|
||||
|
||||
/* Base style for the new inner div that holds the content */
|
||||
.tooltip-content-wrapper {
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
color: #333;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding: 2px 5px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Style for greenfield markers with at least one preorder */
|
||||
.tooltip-content-wrapper.marker-label-highlight {
|
||||
background-color: rgba(248, 215, 218, 0.9); /* Soft red */
|
||||
border-color: #e57373;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
/* Style for markers where preorders match housing units */
|
||||
.tooltip-content-wrapper.marker-label-saturated {
|
||||
background-color: rgba(212, 237, 218, 0.9); /* Light green */
|
||||
border-color: #81c784;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
/* * This rule is now more specific to ensure it overrides Leaflet's default styles.
|
||||
* It targets the DIV element that has BOTH .leaflet-marker-icon AND .custom-div-icon classes.
|
||||
*/
|
||||
div.leaflet-marker-icon.custom-div-icon {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* Base style for all markers */
|
||||
.rimo-marker {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid white;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
|
||||
clip-path: inset(0 round 50%);
|
||||
}
|
||||
|
||||
.rimo-icon {
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Specific styles for each rimo_type */
|
||||
.marker-greenfield { background-color: #28a745; }
|
||||
.marker-residential { background-color: #007bff; }
|
||||
.marker-company { background-color: #ffc107; }
|
||||
.marker-multiple-dwelling { background-color: #6f42c1; }
|
||||
.marker-public { background-color: #17a2b8; }
|
||||
.marker-other { background-color: #6c757d; }
|
||||
@@ -0,0 +1,236 @@
|
||||
Vue.component('PreorderRimoTypeMap', {
|
||||
data() {
|
||||
return {
|
||||
mapMarkers: [],
|
||||
isLoading: false,
|
||||
window,
|
||||
fetchUrl: window.TT_CONFIG.BASE_PATH + '/Preorder/RimoTypeMapData',
|
||||
selectedCampaign: null,
|
||||
mapConfig: {
|
||||
clusterOptions: {
|
||||
spiderfyOnMaxZoom: false,
|
||||
disableClusteringAtZoom: 17,
|
||||
}
|
||||
},
|
||||
mapInstance: null, // Reference to the map instance
|
||||
activeFilters: [], // To store active rimo_type filters
|
||||
filterOptions: [
|
||||
{ value: 'greenfield', text: 'Greenfield', icon: 'fas fa-tree' },
|
||||
{ value: 'residential', text: 'Wohngebiet', icon: 'fas fa-home' },
|
||||
{ value: 'company', text: 'Gewerbe', icon: 'fas fa-building' },
|
||||
{ value: 'multiple-dwelling', text: 'Mehrfamilienhaus', icon: 'fas fa-city' },
|
||||
{ value: 'public', text: 'Öffentlich', icon: 'fas fa-school' },
|
||||
{ value: 'other', text: 'Andere', icon: 'fas fa-question-circle' }
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Filters markers based on the activeFilters array.
|
||||
* If no filters are active, all markers are shown.
|
||||
*/
|
||||
filteredMapMarkers() {
|
||||
if (this.activeFilters.length === 0) {
|
||||
return this.mapMarkers;
|
||||
}
|
||||
return this.mapMarkers.filter(marker => this.activeFilters.includes(marker.rimoType));
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
this.selectedCampaign = urlParams.get('preordercampaign_id');
|
||||
if (this.selectedCampaign) {
|
||||
await this.fetchAndPrepareData();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
const ttMapComponent = this.$refs.ttMap;
|
||||
if (ttMapComponent && ttMapComponent.map) {
|
||||
this.mapInstance = ttMapComponent.map;
|
||||
this.mapInstance.on('zoomend', this.checkZoomLevel);
|
||||
this.checkZoomLevel();
|
||||
}
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.mapInstance) {
|
||||
this.mapInstance.off('zoomend', this.checkZoomLevel);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchAndPrepareData() {
|
||||
if (!this.selectedCampaign) return;
|
||||
|
||||
this.isLoading = true;
|
||||
this.mapMarkers = [];
|
||||
try {
|
||||
const response = await axios.post(this.fetchUrl, { campaignId: this.selectedCampaign });
|
||||
if (response.data.success && Array.isArray(response.data.data)) {
|
||||
this.mapMarkers = this.processData(response.data.data);
|
||||
} else {
|
||||
window.notify('error', 'Ungültiges Datenformat von der API empfangen.');
|
||||
}
|
||||
} catch (err) {
|
||||
window.notify('error', 'Laden der Kartendaten fehlgeschlagen.');
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
processData(data) {
|
||||
const groupedData = {};
|
||||
|
||||
data.forEach(item => {
|
||||
const latLngKey = `${item.gps_lat},${item.gps_long}`;
|
||||
if (!groupedData[latLngKey]) {
|
||||
groupedData[latLngKey] = {
|
||||
...item,
|
||||
wohneinheit_count: parseInt(item.wohneinheit_count, 10),
|
||||
preorder_count: parseInt(item.preorder_count, 10),
|
||||
original_items: [item],
|
||||
};
|
||||
} else {
|
||||
groupedData[latLngKey].wohneinheit_count += parseInt(item.wohneinheit_count, 10);
|
||||
groupedData[latLngKey].preorder_count += parseInt(item.preorder_count, 10);
|
||||
groupedData[latLngKey].original_items.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return Object.values(groupedData).map(group => {
|
||||
const rimoType = this.getNormalizedRimoType(group.rimo_type);
|
||||
const markerIcon = this.getMarkerIcon(rimoType);
|
||||
|
||||
let tooltipInnerClass = '';
|
||||
if (rimoType !== 'greenfield' && group.wohneinheit_count > 0 && group.wohneinheit_count === group.preorder_count) {
|
||||
tooltipInnerClass = ' marker-label-saturated';
|
||||
} else if (rimoType === 'greenfield' && group.preorder_count > 0) {
|
||||
tooltipInnerClass = ' marker-label-highlight';
|
||||
}
|
||||
|
||||
return {
|
||||
lat: group.gps_lat,
|
||||
lng: group.gps_long,
|
||||
rimoType: rimoType, // Add normalized rimoType for filtering
|
||||
options: {
|
||||
icon: {
|
||||
className: `custom-div-icon marker-${rimoType}`,
|
||||
html: `<div class="rimo-marker ${markerIcon.class}"><i class="${markerIcon.icon} rimo-icon"></i></div>`,
|
||||
iconSize: [30, 30],
|
||||
iconAnchor: [15, 30],
|
||||
},
|
||||
tooltip: {
|
||||
content: `<div class="tooltip-content-wrapper${tooltipInnerClass}">H: ${group.wohneinheit_count}<br>B: ${group.preorder_count}</div>`,
|
||||
direction: 'bottom',
|
||||
className: 'marker-label',
|
||||
permanent: true,
|
||||
},
|
||||
asyncPopupContent: async () => {
|
||||
let content = `<div style="font-size: 0.85rem;">`;
|
||||
group.original_items.forEach(item => {
|
||||
content += `
|
||||
<div class="mb-2">
|
||||
<h5 class="mb-2 mt-1">${item.strasse_name} ${item.hausnummer}, ${item.plz_name} ${item.ortschaft_name}</h5>
|
||||
<strong>Rimo Type:</strong> ${item.rimo_type || 'N/A'}<br>
|
||||
<strong>Rimo Op State:</strong> ${item.rimo_op_state || 'N/A'}<br>
|
||||
<strong>Rimo Ex State:</strong> ${item.rimo_ex_state || 'N/A'}<br>
|
||||
<strong>Wohn. gesamt:</strong> ${item.wohneinheit_count}<br>
|
||||
<strong>Bestellungen:</strong> ${item.preorder_count}<br>
|
||||
<strong>Koordinaten:</strong>
|
||||
<a href="https://www.google.com/maps/search/?api=1&query=${item.gps_lat},${item.gps_long}" target="_blank" class="text-primary">
|
||||
<i class="fas fa-map-marker-alt mr-1"></i>Karte
|
||||
</a>
|
||||
<a href="https://thetool.xinon.at/AddressDB/View?id=${item.hausnummer_id}" target="_blank" class="text-primary ml-2">
|
||||
<i class="fas fa-info-circle mr-1"></i>AddressDB
|
||||
</a>
|
||||
</div><hr class="my-1">`;
|
||||
});
|
||||
return content.slice(0, -16) + `</div>`; // Remove last <hr>
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getNormalizedRimoType(type) {
|
||||
const lowerType = (type || '').toLowerCase();
|
||||
if (lowerType.includes('greenfield')) return 'greenfield';
|
||||
if (lowerType.includes('residential')) return 'residential';
|
||||
if (lowerType.includes('multiple dwelling')) return 'multiple-dwelling';
|
||||
if (lowerType.includes('company') || lowerType.includes('commercial')) return 'company';
|
||||
if (lowerType.includes('public') || lowerType.includes('school')) return 'public';
|
||||
return 'other';
|
||||
},
|
||||
|
||||
getMarkerIcon(rimoType) {
|
||||
const icons = {
|
||||
greenfield: { class: 'marker-greenfield', icon: 'fas fa-tree' },
|
||||
residential: { class: 'marker-residential', icon: 'fas fa-home' },
|
||||
company: { class: 'marker-company', icon: 'fas fa-building' },
|
||||
'multiple-dwelling': { class: 'marker-multiple-dwelling', icon: 'fas fa-city' },
|
||||
public: { class: 'marker-public', icon: 'fas fa-school' },
|
||||
other: { class: 'marker-other', icon: 'fas fa-question-circle' }
|
||||
};
|
||||
return icons[rimoType] || icons.other;
|
||||
},
|
||||
|
||||
checkZoomLevel() {
|
||||
if (!this.mapInstance) return;
|
||||
const currentZoom = this.mapInstance.getZoom();
|
||||
const minZoomForLabel = 16;
|
||||
const visibility = currentZoom >= minZoomForLabel ? 'visible' : 'hidden';
|
||||
const opacity = currentZoom >= minZoomForLabel ? '1' : '0';
|
||||
|
||||
document.querySelectorAll('.marker-label').forEach(el => {
|
||||
el.style.visibility = visibility;
|
||||
el.style.opacity = opacity;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles a filter on or off.
|
||||
* @param {string} filterValue - The rimo_type to toggle.
|
||||
*/
|
||||
toggleFilter(filterValue) {
|
||||
const index = this.activeFilters.indexOf(filterValue);
|
||||
if (index > -1) {
|
||||
this.activeFilters.splice(index, 1);
|
||||
} else {
|
||||
this.activeFilters.push(filterValue);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if a filter is currently active.
|
||||
* @param {string} filterValue - The rimo_type to check.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isFilterActive(filterValue) {
|
||||
return this.activeFilters.includes(filterValue);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<tt-card style="height: 75vh; position: relative; display: flex; flex-direction: column;">
|
||||
<div v-if="!selectedCampaign" class="alert alert-warning m-3">
|
||||
Bitte eine Kampagne über den URL-Parameter 'preordercampaign_id' auswählen (z.B. ?preordercampaign_id=44).
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="map-filter-container">
|
||||
<button v-for="filter in filterOptions"
|
||||
:key="filter.value"
|
||||
@click="toggleFilter(filter.value)"
|
||||
:class="['btn', 'btn-sm', isFilterActive(filter.value) ? 'btn-primary' : 'btn-outline-secondary']"
|
||||
:title="filter.text">
|
||||
<i :class="filter.icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div style="height: 100%; position: relative; flex-grow: 1;">
|
||||
<div v-if="!isLoading && mapMarkers.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>
|
||||
</div>
|
||||
</template>
|
||||
</tt-card>
|
||||
`
|
||||
});
|
||||
Reference in New Issue
Block a user