diff --git a/app.js b/app.js
index 9ab7d1a..b1976ae 100644
--- a/app.js
+++ b/app.js
@@ -1192,12 +1192,65 @@ document.addEventListener('DOMContentLoaded', async () => {
await loadShapefileLayer(l);
}
}
+
+ // NEU: ALKIS aus Datenbank laden, falls kein Eigentümer-Layer da ist
+ const hasOwnerLayer = Object.keys(overlays).some(k => k.toLowerCase().includes('eigentümer'));
+ if (!hasOwnerLayer) {
+ console.log("Kein lokaler Eigentümer-Layer. Lade ALKIS aus Datenbank...");
+ const resp = await fetch('/api/layers/alkis').catch(() => null);
+ if (resp && resp.ok) {
+ const data = await resp.json();
+ await processALKISData(data, "Eigentümer (ALKIS DB)");
+ }
+ }
+
statusEl.innerText = "Alle konfigurierten Layer geladen.";
} catch (e) {
if (!isLocalFile) console.error("Layer-Init fehlgeschlagen:", e);
}
}
+ async function processALKISData(geojson, layerName) {
+ const style = getDynamicStyle(layerName) || { color: '#000', weight: 1, fillOpacity: 0.1 };
+
+ const layer = L.geoJSON(geojson, {
+ style: (feature) => {
+ const statusRaw = feature.properties.status;
+ const status = statusRaw ? statusRaw.toLowerCase() : "";
+ let fillColor = 'transparent';
+
+ 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';
+
+ return {
+ color: '#000',
+ weight: 1,
+ fillOpacity: fillColor === 'transparent' ? 0.1 : 0.7,
+ fillColor: fillColor
+ };
+ },
+ onEachFeature: (feature, layer) => {
+ if (feature.properties) {
+ let popup = `${layerName}
`;
+ for (let key in feature.properties) {
+ const val = feature.properties[key];
+ if (val !== null && val !== undefined) popup += `${key}: ${val}
`;
+ }
+ layer.bindPopup(popup);
+ }
+ }
+ });
+
+ overlays[layerName] = layer;
+ state.map.addLayer(layer);
+ layerControl.addOverlay(layer, layerName);
+ layer.bringToBack();
+ }
+
// Manual Import & Bundling
const btnManualImport = document.getElementById('btnManualImport');
const manualShpInput = document.getElementById('manualShpInput');
@@ -1363,7 +1416,10 @@ document.addEventListener('DOMContentLoaded', async () => {
ownerTableBody.innerHTML = '';
Object.keys(owners).sort().forEach(name => {
const data = owners[name];
- const status = state.ownerStatuses[name] || 'none';
+ const stored = state.ownerStatuses[name.toLowerCase()] || { status: 'none', notiz: '' };
+ const status = typeof stored === 'string' ? stored : (stored.status || 'none');
+ const notiz = typeof stored === 'string' ? '' : (stored.notiz || '');
+
const row = document.createElement('tr');
row.innerHTML = `
${name} |
@@ -1379,7 +1435,10 @@ document.addEventListener('DOMContentLoaded', async () => {
-
+
+ |
+
+
|
`;
ownerTableBody.appendChild(row);
@@ -1388,21 +1447,20 @@ document.addEventListener('DOMContentLoaded', async () => {
// Add event listeners to dropdowns
document.querySelectorAll('.status-select').forEach(sel => {
sel.onchange = async (e) => {
- const name = e.target.dataset.owner; // original name for display
+ const name = e.target.dataset.owner;
const status = e.target.value;
- state.ownerStatuses[name.toLowerCase()] = status;
+ const notizInput = document.querySelector(`.notiz-input[data-owner="${name}"]`);
+ const notiz = notizInput ? notizInput.value : "";
+
+ state.ownerStatuses[name.toLowerCase()] = { status, notiz };
// Sync with DB
const data = owners[name];
if (data) {
- await secureOwner(data.first, data.last, e.target, status);
+ await secureOwner(data.first, data.last, e.target, status, notiz);
}
- // Refresh map style
- const ownerLayerName = Object.keys(overlays).find(k => k.toLowerCase().includes('eigentümer'));
- if (ownerLayerName) {
- overlays[ownerLayerName].setStyle(overlays[ownerLayerName].options.style);
- }
+ refreshOwnerLayerStyle();
};
});
@@ -1411,12 +1469,18 @@ document.addEventListener('DOMContentLoaded', async () => {
btn.onclick = async (e) => {
const first = e.target.dataset.first;
const last = e.target.dataset.last;
- await secureOwner(first, last, e.target);
+ const name = e.target.dataset.owner;
+ const sel = document.querySelector(`.status-select[data-owner="${name}"]`);
+ const status = sel ? sel.value : 'Gesichert';
+ const notizInput = document.querySelector(`.notiz-input[data-owner="${name}"]`);
+ const notiz = notizInput ? notizInput.value : "";
+
+ await secureOwner(first, last, e.target, status, notiz);
};
});
}
- async function secureOwner(vorname, nachname, element, status = 'Gesichert') {
+ async function secureOwner(vorname, nachname, element, status = 'Gesichert', notiz = '') {
const isButton = element.tagName === 'BUTTON';
const originalText = isButton ? element.innerText : "";
if (isButton) {
@@ -1430,26 +1494,28 @@ document.addEventListener('DOMContentLoaded', async () => {
const response = await fetch('/api/sicherung', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ vorname, nachname, projekt_id, status })
+ body: JSON.stringify({ vorname, nachname, projekt_id, status, notiz })
});
const result = await response.json();
if (response.ok) {
if (isButton) {
element.style.background = '#2ecc71';
- element.innerText = "✓ Gesichert";
+ element.innerText = "✓ Gespeichert";
+ setTimeout(() => {
+ element.style.background = '';
+ element.innerText = "Speichern";
+ element.disabled = false;
+ }, 2000);
}
console.log(result.message);
const fullName = `${vorname || ''} ${nachname || ''}`.trim();
if (fullName) {
- state.ownerStatuses[fullName.toLowerCase()] = status;
+ state.ownerStatuses[fullName.toLowerCase()] = { status, notiz };
}
- const ownerLayerName = Object.keys(overlays).find(k => k.toLowerCase().includes('eigentümer'));
- if (ownerLayerName) {
- overlays[ownerLayerName].setStyle(overlays[ownerLayerName].options.style);
- }
+ refreshOwnerLayerStyle();
const select = document.querySelector(`.status-select[data-owner="${fullName}"]`);
if (select) select.value = status;
@@ -1527,22 +1593,20 @@ document.addEventListener('DOMContentLoaded', async () => {
try {
const response = await fetch(`/api/sicherung/${projekt_id}`);
if (response.ok) {
- const statuses = await response.json();
- statuses.forEach(s => {
+ const entries = await response.json();
+ entries.forEach(s => {
const first = s.vorname || '';
const last = s.nachname || '';
const fullName = `${first} ${last}`.trim().toLowerCase();
if (fullName) {
- state.ownerStatuses[fullName] = s.status;
+ state.ownerStatuses[fullName] = {
+ status: s.status,
+ notiz: s.notiz
+ };
}
});
- console.log(`${statuses.length} Eigentümer-Status geladen.`);
-
- // Refresh map style if owner layer exists
- const ownerLayerName = Object.keys(overlays).find(k => k.toLowerCase().includes('eigentümer'));
- if (ownerLayerName && overlays[ownerLayerName]) {
- overlays[ownerLayerName].setStyle(overlays[ownerLayerName].options.style);
- }
+ console.log(`${entries.length} Eigentümer-Status geladen.`);
+ refreshOwnerLayerStyle();
} else {
console.warn("API Fehler beim Laden der Status:", response.status);
}
diff --git a/index.html b/index.html
index e39ca7b..9613964 100644
--- a/index.html
+++ b/index.html
@@ -232,10 +232,11 @@
- | Name |
- Flächen |
- Status |
- Aktion |
+ Name |
+ Flächen |
+ Status |
+ Notiz |
+ Aktion |
diff --git a/server.js b/server.js
index f093f9e..2679832 100644
--- a/server.js
+++ b/server.js
@@ -133,71 +133,55 @@ async function resolveProjectId(client, input) {
// API zur Flächensicherung von Eigentümern
app.post('/api/sicherung', async (req, res) => {
- const { nachname, vorname, projekt_id, status } = req.body;
+ const { nachname, vorname, projekt_id, status, notiz } = req.body;
const targetStatus = status || 'Gesichert';
const schema = process.env.DB_SCHEMA || 'geodaten';
- log(`Sicherungs-Request: ${vorname} ${nachname} Status='${targetStatus}' für Projekt ${projekt_id}`);
+ log(`Sicherungs-Request: ${vorname} ${nachname} Status='${targetStatus}' Notiz='${notiz}'`);
const client = await pool.connect();
try {
await client.query('BEGIN');
- // 1. Projekt-ID auflösen
- const resolvedPid = await resolveProjectId(client, projekt_id);
- if (!resolvedPid) {
- throw new Error(`Projekt '${projekt_id}' konnte nicht gefunden werden.`);
- }
+ // Suche nach dem Eigentümer in flaecheneigentuemer_alkis und aktualisiere status und notiz
+ const searchNachname = (nachname || '').trim();
+ const searchVorname = (vorname || '').trim();
- // 2. FSKs für den Namen finden (Behandlung von NULL vs leerem String)
- // Wir suchen flexibel: entweder exakter Match oder der Name ist Teil eines kombinierten Feldes
- const searchNachname = (nachname || '').trim().toLowerCase();
- const searchVorname = (vorname || '').trim().toLowerCase();
- const searchFull = `${searchVorname} ${searchNachname}`.trim().toLowerCase();
-
- const ownerRes = await client.query(
- `SELECT "FSK" FROM ${schema}.flaecheneigentuemer_alkis
+ // Wir aktualisieren alle Einträge, die auf diesen Namen passen
+ const updateRes = await client.query(
+ `UPDATE ${schema}.flaecheneigentuemer_alkis
+ SET status = $1, notiz = $2
WHERE
- (LOWER("GNA") = $1 AND LOWER("VNA") = $2) OR
- (LOWER("VNA") = $3 AND "GNA" IS NULL) OR
- (LOWER("GNA") = $3 AND "VNA" IS NULL) OR
- (LOWER("VNA") LIKE '%' || $1 || '%' AND LOWER("VNA") LIKE '%' || $2 || '%')`,
- [searchNachname, searchVorname, searchFull]
+ ("GNA" = $3 AND "VNA" = $4) OR
+ ("VNA" = $5 AND "GNA" IS NULL) OR
+ ("GNA" = $5 AND "VNA" IS NULL)`,
+ [targetStatus, notiz, searchNachname, searchVorname, `${searchVorname} ${searchNachname}`.trim()]
);
-
- const fsks = ownerRes.rows.map(r => r.FSK);
- if (fsks.length === 0) {
- log(`Keine Flurstücke für ${vorname} ${nachname} gefunden.`);
- await client.query('ROLLBACK');
- return res.status(404).json({ error: 'Keine Flurstücke für diesen Namen gefunden.' });
- }
- log(`Gefunden: ${fsks.length} Flurstücke für ${vorname} ${nachname}. Prüfe Zuweisung...`);
-
- // 3. Zuweisung prüfen und Status schreiben
- let securedCount = 0;
- for (const fsk of fsks) {
- const assignmentRes = await client.query(
- `SELECT 1 FROM ${schema}.flaecheneigentuemer_alkis_zuweisung WHERE fsk = $1 AND projekt_id = $2`,
- [fsk, resolvedPid]
+ if (updateRes.rowCount === 0) {
+ log(`Kein Eigentümer für ${vorname} ${nachname} zum Aktualisieren gefunden.`);
+ // Optional: Fallback Suche mit LOWER
+ const updateResLower = await client.query(
+ `UPDATE ${schema}.flaecheneigentuemer_alkis
+ SET status = $1, notiz = $2
+ WHERE
+ (LOWER("GNA") = LOWER($3) AND LOWER("VNA") = LOWER($4)) OR
+ (LOWER("VNA") = LOWER($5) AND "GNA" IS NULL) OR
+ (LOWER("GNA") = LOWER($5) AND "VNA" IS NULL)`,
+ [targetStatus, notiz, searchNachname, searchVorname, `${searchVorname} ${searchNachname}`.trim()]
);
- if (assignmentRes.rowCount > 0) {
- await client.query(
- `INSERT INTO ${schema}.flaecheneigentuemer_status (id, fsk, projekt_id, status, datum)
- VALUES (gen_random_uuid(), $1, $2, $3, NOW())`,
- [fsk, resolvedPid, targetStatus]
- );
- securedCount++;
+ if (updateResLower.rowCount === 0) {
+ await client.query('ROLLBACK');
+ return res.status(404).json({ error: 'Eigentümer nicht in der Datenbank gefunden.' });
}
}
await client.query('COMMIT');
- log(`Erfolgreich: ${securedCount} von ${fsks.length} Flurstücken als '${targetStatus}' markiert.`);
+ log(`Erfolgreich: ${updateRes.rowCount || 1} Einträge in ALKIS aktualisiert.`);
res.json({
- message: `${securedCount} Flurstücke für ${vorname} ${nachname} erfolgreich als '${targetStatus}' markiert.`,
- count: securedCount,
- total_found: fsks.length
+ message: `Eigentümer ${vorname} ${nachname} erfolgreich aktualisiert.`,
+ count: updateRes.rowCount
});
} catch (err) {
await client.query('ROLLBACK');
@@ -215,19 +199,14 @@ app.get('/api/sicherung/:projekt_id', async (req, res) => {
const client = await pool.connect();
try {
- const resolvedPid = await resolveProjectId(client, projekt_id);
- if (!resolvedPid) {
- throw new Error(`Projekt '${projekt_id}' konnte nicht gefunden werden.`);
- }
-
+ // Wir laden nun direkt aus der ALKIS Tabelle, wie vom User gewünscht
const result = await client.query(
- `SELECT vorname, nachname, aktueller_status as status
- FROM geodaten.v_projekt_sicherung
- WHERE projekt_id = $1`,
- [resolvedPid]
+ `SELECT "VNA" as vorname, "GNA" as nachname, status, notiz
+ FROM geodaten.flaecheneigentuemer_alkis
+ WHERE status IS NOT NULL OR notiz IS NOT NULL`
);
- log(`Geladen: ${result.rowCount} Status-Einträge.`);
+ log(`Geladen: ${result.rowCount} Status-Einträge aus ALKIS.`);
res.json(result.rows);
} catch (e) {
log(`FEHLER beim Laden der Stände: ${e.message}`);
@@ -237,6 +216,42 @@ app.get('/api/sicherung/:projekt_id', async (req, res) => {
}
});
+// NEU: API zum Laden des ALKIS-Layers als GeoJSON
+app.get('/api/layers/alkis', async (req, res) => {
+ log("Lade ALKIS-Layer aus Datenbank...");
+ try {
+ const result = await pool.query(
+ `SELECT jsonb_build_object(
+ 'type', 'FeatureCollection',
+ 'features', jsonb_agg(features.feature)
+ )
+ FROM (
+ SELECT jsonb_build_object(
+ 'type', 'Feature',
+ 'id', id,
+ 'geometry', ST_AsGeoJSON(ST_Transform(geom, 4326))::jsonb,
+ 'properties', jsonb_build_object(
+ 'id', id,
+ 'VNA', "VNA",
+ 'GNA', "GNA",
+ 'FSK', "FSK",
+ 'PLZ', "PLZ",
+ 'ORP', "ORP",
+ 'STR', "STR",
+ 'status', status,
+ 'notiz', notiz
+ )
+ ) AS feature
+ FROM geodaten.flaecheneigentuemer_alkis
+ ) features`
+ );
+ res.json(result.rows[0].jsonb_build_object);
+ } catch (err) {
+ log(`FEHLER beim Laden des ALKIS-Layers: ${err.message}`);
+ res.status(500).json({ error: err.message });
+ }
+});
+
// API für Projekt-Statistiken (Fortschrittsanzeige)
app.get('/api/stats/:projekt_id', async (req, res) => {
const { projekt_id } = req.params;