Full update of owner status categories and premium legend UI based on user image
Deploy Bürgerwind / deploy (push) Successful in 16s
Details
Deploy Bürgerwind / deploy (push) Successful in 16s
Details
This commit is contained in:
parent
b03a7b2568
commit
826fbb8973
82
app.js
82
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 = '<div class="legend-section-title">Anlagen-Geometrien</div>';
|
||||
if (state.turbines.length > 0) {
|
||||
html += `
|
||||
<div class="legend-item"><span class="color-box" style="background: #00c8ff;"></span> Rotorfläche</div>
|
||||
|
|
@ -258,22 +270,25 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
<div class="legend-item"><span class="color-box" style="background: rgba(52, 152, 219, 0.3); border: 1px solid #3498db;"></span> Fundament</div>
|
||||
<div class="legend-item"><span class="color-box" style="background: #e74c3c; opacity: 0.6;"></span> Kranstellfläche (KSF)</div>
|
||||
`;
|
||||
} else {
|
||||
html += '<div style="font-size: 0.7rem; opacity: 0.6; padding-left: 20px;">Keine Anlagen gesetzt</div>';
|
||||
}
|
||||
|
||||
// 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 += `<div class="legend-item"><span class="color-box" style="background: ${color}; opacity: 0.8;"></span> ${name}</div>`;
|
||||
}
|
||||
html += '<div class="legend-section-title" style="margin-top: 15px;">Sicherungsstand (ALKIS)</div>';
|
||||
Object.keys(STATUS_MAP).forEach(status => {
|
||||
const data = STATUS_MAP[status];
|
||||
html += `
|
||||
<div class="legend-item-status">
|
||||
<span class="status-dot" style="background: ${data.color};"></span>
|
||||
<div class="status-text-container">
|
||||
<div class="status-label">${status}</div>
|
||||
<div class="status-desc">${data.desc}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
legendContent.innerHTML = html || '<div style="font-size: 0.75rem; color: var(--text-dim); text-align: center;">Keine aktiven Layer</div>';
|
||||
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 = `<b>${layerName}</b><br><hr style="margin: 5px 0; border: 0; border-top: 1px solid #444;">`;
|
||||
popup += `<b>Eigentümer:</b> ${feature.properties.VNA} ${feature.properties.GNA}<br>`;
|
||||
popup += `<b>Status:</b> ${status}<br>`;
|
||||
if (notiz) popup += `<b>Notiz:</b> ${notiz}<br>`;
|
||||
popup += `<hr style="margin: 5px 0; border: 0; border-top: 1px solid #444;">`;
|
||||
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 += `<b>${key}:</b> ${val}<br>`;
|
||||
}
|
||||
|
|
@ -1432,11 +1454,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
<td>
|
||||
<select class="status-select" data-owner="${name}">
|
||||
<option value="none" ${status === 'none' ? 'selected' : ''}>Kein Status</option>
|
||||
<option value="gbr" ${status === 'gbr' ? 'selected' : ''}>Mitglied der GbR</option>
|
||||
<option value="external" ${status === 'external' ? 'selected' : ''}>Fremdplanung</option>
|
||||
<option value="declined" ${status === 'declined' ? 'selected' : ''}>Ablehnend</option>
|
||||
<option value="positive" ${status === 'positive' ? 'selected' : ''}>Positiv</option>
|
||||
<option value="undecided" ${status === 'undecided' ? 'selected' : ''}>Unentschlossen</option>
|
||||
${Object.keys(STATUS_MAP).map(s => `<option value="${s}" ${status === s ? 'selected' : ''}>${s}</option>`).join('')}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
|
|
|
|||
82
style.css
82
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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue