From 826fbb89734d7cf6d2132494acc5d590cae9ff23 Mon Sep 17 00:00:00 2001 From: Johannes Baumeister Date: Thu, 30 Apr 2026 21:52:13 +0200 Subject: [PATCH] Full update of owner status categories and premium legend UI based on user image --- app.js | 82 +++++++++++++++++++++++++++++++++---------------------- style.css | 82 ++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 119 insertions(+), 45 deletions(-) diff --git a/app.js b/app.js index b6fdba4..de765eb 100644 --- a/app.js +++ b/app.js @@ -10,10 +10,23 @@ document.addEventListener('DOMContentLoaded', async () => { activeVariant: 'A', bakedData: {}, // Cache for standalone persistence ownerMapping: { firstName: 'VNA', lastName: 'GNA' }, // Default for ALKIS - ownerStatuses: {}, // { "Name Vorname": "status" } + ownerStatuses: {}, // { "name vorname": { status: "...", notiz: "..." } } showAuxiliary: true }; + const STATUS_MAP = { + 'Ablehnung': { color: '#ff0000', desc: 'Der Eigentümer lehnt das Vorhaben strikt ab.' }, + 'Erwartet Negativ': { color: '#ffa500', desc: 'Erste Signale oder Tendenzen deuten auf eine Ablehnung hin.' }, + 'Unentschlossen': { color: '#ffff00', desc: 'Rückmeldung ist noch offen oder der Eigentümer zögert.' }, + 'Unbekannt': { color: '#cccccc', desc: 'Bisher kein Kontakt erfolgt; Status ist völlig offen.' }, + 'Erwartet Positiv': { color: '#90ee90', desc: 'Eine grundsätzliche Bereitschaft zur Zustimmung wird erwartet.' }, + 'Zusage (mündlich)': { color: '#008000', desc: 'Klare mündliche Zustimmung liegt vor, der schriftliche Vertrag ist noch offen.' }, + 'Vertraglich gesichert': { color: '#006400', desc: 'Der Vertrag liegt unterschrieben vor.' }, + 'In der Projektgesellschaft': { color: '#ff00ff', desc: 'Grundstückseigentümer ist in der Projektgesellschaft.' }, + 'Fremdplanung': { color: '#c71585', desc: 'Anderes Vorhaben (WEA), keine Kooperation.' }, + 'Kooperationspartner': { color: '#ffffff', desc: 'Anderes Vorhaben mit dem kooperiert wird.' } + }; + // Removed fetch for config to prevent CORS errors on file:// protocol console.log("Konfiguration geladen."); @@ -247,8 +260,7 @@ document.addEventListener('DOMContentLoaded', async () => { function updateLegend() { if (!legendContent) return; - // Items to always show if any turbine exists - let html = ''; + let html = '
Anlagen-Geometrien
'; if (state.turbines.length > 0) { html += `
Rotorfläche
@@ -258,22 +270,25 @@ document.addEventListener('DOMContentLoaded', async () => {
Fundament
Kranstellfläche (KSF)
`; + } else { + html += '
Keine Anlagen gesetzt
'; } - // Search for active external layers - Object.keys(overlays).forEach(name => { - const layer = overlays[name]; - if (state.map.hasLayer(layer)) { - // Determine color (heuristic or from layer style) - let color = '#ccc'; - if (name.includes('Eigentümer')) color = '#2ecc71'; - if (name.includes('Hilfs')) color = '#ffcc00'; - - html += `
${name}
`; - } + html += '
Sicherungsstand (ALKIS)
'; + Object.keys(STATUS_MAP).forEach(status => { + const data = STATUS_MAP[status]; + html += ` +
+ +
+
${status}
+
${data.desc}
+
+
+ `; }); - legendContent.innerHTML = html || '
Keine aktiven Layer
'; + legendContent.innerHTML = html; } // Toggle Legend collapse @@ -1210,38 +1225,45 @@ document.addEventListener('DOMContentLoaded', async () => { } async function processALKISData(geojson, layerName) { - const style = getDynamicStyle(layerName) || { color: '#000', weight: 1, fillOpacity: 0.1 }; - const layer = L.geoJSON(geojson, { style: (feature) => { const props = feature.properties; - const firstName = props.VNA || ''; - const lastName = props.GNA || ''; + const firstName = (props.VNA || '').trim(); + const lastName = (props.GNA || '').trim(); const ownerName = `${firstName} ${lastName}`.trim().toLowerCase(); const stored = state.ownerStatuses[ownerName]; - const status = (typeof stored === 'string' ? stored : (stored?.status || props.status || "")).toLowerCase(); + const status = typeof stored === 'object' ? (stored.status || '') : (stored || props.status || ''); let fillColor = 'transparent'; + let opacity = 0.1; - if (status === 'gbr' || status === 'gesichert') fillColor = '#2ecc71'; - else if (status === 'external' || status === 'fremdplanung') fillColor = '#e74c3c'; - else if (status === 'declined' || status === 'ablehnend' || status === 'negative') fillColor = '#e74c3c'; - else if (status === 'undecided' || status === 'unentschlossen') fillColor = '#95a5a6'; - else if (status === 'positive' || status === 'positiv') fillColor = '#5efd9c'; - else if (status === 'in verhandlung') fillColor = '#f1c40f'; + if (STATUS_MAP[status]) { + fillColor = STATUS_MAP[status].color; + opacity = 0.7; + } else if (status === 'none' || status === '') { + fillColor = 'transparent'; + opacity = 0.1; + } return { color: '#000', weight: 1, - fillOpacity: fillColor === 'transparent' ? 0.1 : 0.7, + fillOpacity: opacity, fillColor: fillColor }; }, onEachFeature: (feature, layer) => { if (feature.properties) { + const status = feature.properties.status || 'Kein Status'; + const notiz = feature.properties.notiz || ''; let popup = `${layerName}

`; + popup += `Eigentümer: ${feature.properties.VNA} ${feature.properties.GNA}
`; + popup += `Status: ${status}
`; + if (notiz) popup += `Notiz: ${notiz}
`; + popup += `
`; for (let key in feature.properties) { + if (['VNA', 'GNA', 'status', 'notiz', 'id'].includes(key)) continue; const val = feature.properties[key]; if (val !== null && val !== undefined) popup += `${key}: ${val}
`; } @@ -1432,11 +1454,7 @@ document.addEventListener('DOMContentLoaded', async () => { diff --git a/style.css b/style.css index f78727d..8e4707d 100644 --- a/style.css +++ b/style.css @@ -228,39 +228,95 @@ body { .floating-panel { position: absolute; z-index: 1000; - background: var(--panel-bg); + background: #0a2d2d; /* Dark teal background matching the image */ backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); - border: 1px solid var(--border-color); + border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 12px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6); overflow: hidden; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .panel-header { - background: rgba(255, 255, 255, 0.05); - padding: 10px 15px; + background: rgba(0, 0, 0, 0.2); + padding: 12px 18px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; - border-bottom: 1px solid var(--border-color); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); font-size: 0.85rem; - font-weight: 600; + font-weight: 700; + letter-spacing: 0.5px; + color: white; } .panel-content { - padding: 12px; - max-height: 300px; + padding: 18px; + max-height: 500px; overflow-y: auto; } /* Floating Legend Specific */ #floatingLegend { - bottom: 20px; - right: 20px; - width: 220px; + bottom: 25px; + right: 25px; + width: 320px; +} + +.legend-section-title { + font-size: 0.7rem; + font-weight: 800; + color: var(--primary-color); + margin-bottom: 12px; + text-transform: uppercase; + letter-spacing: 1.2px; + opacity: 0.8; +} + +.legend-item { + display: flex; + align-items: center; + gap: 12px; + font-size: 0.85rem; + margin-bottom: 10px; + color: rgba(255, 255, 255, 0.9); +} + +.legend-item-status { + display: flex; + align-items: flex-start; + gap: 15px; + margin-bottom: 18px; +} + +.status-dot { + width: 20px; + height: 20px; + border-radius: 50%; + flex-shrink: 0; + margin-top: 2px; + box-shadow: 0 0 10px rgba(0,0,0,0.3); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.status-text-container { + display: flex; + flex-direction: column; + gap: 2px; +} + +.status-label { + font-weight: 800; + font-size: 0.95rem; + color: white; +} + +.status-desc { + font-size: 0.75rem; + color: rgba(255, 255, 255, 0.6); + line-height: 1.4; } #floatingLegend.collapsed .panel-content { @@ -270,7 +326,7 @@ body { .toggle-btn { background: transparent; border: none; - color: var(--text-dim); + color: white; cursor: pointer; font-size: 0.7rem; transition: transform 0.3s;