diff --git a/backend/check_status_columns.js b/backend/check_status_columns.js new file mode 100644 index 0000000..027af9a --- /dev/null +++ b/backend/check_status_columns.js @@ -0,0 +1,27 @@ +const { Client } = require('pg'); + +async function check() { + const client = new Client({ + host: '87.106.21.21', + port: 5432, + user: 'enwelo_admin', + password: 'WX1t1cgP1qK09', + database: 'enwelo' + }); + + try { + await client.connect(); + const res = await client.query(` + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_schema = 'geodaten' AND table_name = 'flaecheneigentuemer_status'; + `); + console.log("Columns in flaecheneigentuemer_status:"); + res.rows.forEach(r => console.log(`${r.column_name}: ${r.data_type}`)); + } catch (e) { + console.error("Error:", e); + } finally { + await client.end(); + } +} +check(); diff --git a/backend/test_db_logic.js b/backend/test_db_logic.js new file mode 100644 index 0000000..412a05e --- /dev/null +++ b/backend/test_db_logic.js @@ -0,0 +1,68 @@ +const { Client } = require('pg'); + +async function testFullLogic() { + const client = new Client({ + host: '87.106.21.21', + port: 5432, + user: 'enwelo_admin', + password: 'WX1t1cgP1qK09', + database: 'enwelo' + }); + + try { + await client.connect(); + + const vorname = 'Anja'; + const nachname = ''; + const projekt_name = 'bw_samern-ohne'; // Try the exact DB name or 'BWSamern-Ohne' + + console.log(`--- Testing Project Resolution ---`); + const pRes = await client.query( + 'SELECT id FROM geodaten.projekte WHERE LOWER(name) = $1 OR LOWER(REPLACE(name, \'-\', \'_\')) = $1', + [projekt_name.toLowerCase().replace(/-/g, '_')] + ); + const resolvedPid = pRes.rows.length > 0 ? pRes.rows[0].id : null; + console.log(`Projekt ID für '${projekt_name}':`, resolvedPid); + + if (!resolvedPid) return; + + console.log(`\n--- Testing ALKIS Search ---`); + const ownerRes = await client.query( + `SELECT "FSK", "VNA", "GNA" FROM geodaten.flaecheneigentuemer_alkis + WHERE ("GNA" = $1 OR ("GNA" IS NULL AND $1 = '')) + AND ("VNA" = $2 OR ("VNA" IS NULL AND $2 = ''))`, + [nachname, vorname] + ); + const fsks = ownerRes.rows.map(r => r.FSK); + console.log(`Found ${fsks.length} FSKs for ${vorname} ${nachname}:`, fsks); + + if (fsks.length === 0) return; + + console.log(`\n--- Testing Zuweisung Check ---`); + let validFsks = []; + for (const fsk of fsks) { + const assignmentRes = await client.query( + `SELECT 1 FROM geodaten.flaecheneigentuemer_alkis_zuweisung WHERE fsk = $1 AND projekt_id = $2`, + [fsk, resolvedPid] + ); + console.log(`FSK ${fsk} is assigned to project:`, assignmentRes.rowCount > 0); + if (assignmentRes.rowCount > 0) validFsks.push(fsk); + } + + console.log(`\n--- Overall result ---`); + console.log(`If this were the API, it would save ${validFsks.length} statuses.`); + + // Also let's check the zuweisung table generally for this project to see how many exist + const allZuw = await client.query( + `SELECT COUNT(*) FROM geodaten.flaecheneigentuemer_alkis_zuweisung WHERE projekt_id = $1`, + [resolvedPid] + ); + console.log(`Total FSKs assigned to this project in zuweisung table:`, allZuw.rows[0].count); + + } catch (e) { + console.error("Error:", e); + } finally { + await client.end(); + } +} +testFullLogic(); diff --git a/backend/test_insert_status.js b/backend/test_insert_status.js new file mode 100644 index 0000000..16fdae9 --- /dev/null +++ b/backend/test_insert_status.js @@ -0,0 +1,30 @@ +const { Client } = require('pg'); + +async function testInsert() { + const client = new Client({ + host: '87.106.21.21', + port: 5432, + user: 'enwelo_admin', + password: 'WX1t1cgP1qK09', + database: 'enwelo' + }); + + try { + await client.connect(); + + await client.query('BEGIN'); + const res = await client.query( + `INSERT INTO geodaten.flaecheneigentuemer_status (id, fsk, projekt_id, status, datum) + VALUES (gen_random_uuid(), '034212009000090003__', '5bb4e049-85f2-4433-b38e-6a66b81e9f06', 'Gesichert', NOW()) RETURNING *` + ); + console.log("Insert success:", res.rows[0]); + await client.query('ROLLBACK'); + console.log("Rollback success"); + + } catch (e) { + console.error("Insert failed:", e); + } finally { + await client.end(); + } +} +testInsert(); diff --git a/backend/test_null_query.js b/backend/test_null_query.js new file mode 100644 index 0000000..1548c0a --- /dev/null +++ b/backend/test_null_query.js @@ -0,0 +1,39 @@ +const { Client } = require('pg'); + +async function testQuery() { + const client = new Client({ + host: '87.106.21.21', + port: 5432, + user: 'enwelo_admin', + password: 'WX1t1cgP1qK09', + database: 'enwelo' + }); + + try { + await client.connect(); + const nachname = ''; + const vorname = 'Anja'; + + console.log(`Querying for GNA='${nachname}' AND VNA='${vorname}'`); + const res1 = await client.query( + `SELECT "FSK" FROM geodaten.flaecheneigentuemer_alkis WHERE "GNA" = $1 AND "VNA" = $2`, + [nachname, vorname] + ); + console.log(`Found: ${res1.rowCount} rows.`); + + console.log(`Querying for (GNA=$1 OR (GNA IS NULL AND $1='')) AND VNA=$2`); + const res2 = await client.query( + `SELECT "FSK" FROM geodaten.flaecheneigentuemer_alkis + WHERE ("GNA" = $1 OR ("GNA" IS NULL AND $1 = '')) + AND ("VNA" = $2 OR ("VNA" IS NULL AND $2 = ''))`, + [nachname, vorname] + ); + console.log(`Found: ${res2.rowCount} rows.`); + + } catch (e) { + console.error("Error:", e); + } finally { + await client.end(); + } +} +testQuery(); diff --git a/backend/test_online_api.js b/backend/test_online_api.js new file mode 100644 index 0000000..4193a7b --- /dev/null +++ b/backend/test_online_api.js @@ -0,0 +1,17 @@ +const https = require('https'); + +https.get('https://bw-samern-ohne.enwelo-serverumgebung.cloud/api/sicherung/bw_samern-ohne', (res) => { + console.log('statusCode:', res.statusCode); + + let data = ''; + res.on('data', (d) => { + data += d; + }); + + res.on('end', () => { + console.log(data.substring(0, 500)); + }); + +}).on('error', (e) => { + console.error(e); +}); diff --git a/backend/test_project_id.js b/backend/test_project_id.js new file mode 100644 index 0000000..09f219a --- /dev/null +++ b/backend/test_project_id.js @@ -0,0 +1,27 @@ +const { Client } = require('pg'); + +async function testProject() { + const client = new Client({ + host: '87.106.21.21', + port: 5432, + user: 'enwelo_admin', + password: 'WX1t1cgP1qK09', + database: 'enwelo' + }); + + try { + await client.connect(); + const input = 'BWSamern-Ohne'; + const normalized = input.toLowerCase().replace(/[-_]/g, ''); + const res = await client.query( + 'SELECT id FROM geodaten.projekte WHERE LOWER(REPLACE(REPLACE(name, \'-\', \'\'), \'_\', \'\')) = $1', + [normalized] + ); + console.log(`Projekt ID für '${input}':`, res.rows.length > 0 ? res.rows[0].id : null); + } catch (e) { + console.error("Error:", e); + } finally { + await client.end(); + } +} +testProject(); diff --git a/server.js b/server.js index a8e4249..79e8d42 100644 --- a/server.js +++ b/server.js @@ -229,6 +229,41 @@ app.get('/api/sicherung/:projekt_id', async (req, res) => { } }); +// API für Projekt-Statistiken (Fortschrittsanzeige) +app.get('/api/stats/:projekt_id', async (req, res) => { + const { projekt_id } = req.params; + log(`Lade Statistiken für Projekt: ${projekt_id}`); + + 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.`); + } + + // Verwende die vom User vorgeschlagene Logik, aber mit resolvedPid + // Wir gruppieren nach aktueller_status (da dies in der View v_projekt_sicherung so heißt) + const result = await client.query( + `SELECT + aktueller_status as status, + count(*) as anzahl, + ROUND(SUM(ST_Area(geom)) / 10000, 2) as hektar + FROM geodaten.v_projekt_sicherung + WHERE projekt_id = $1 + GROUP BY aktueller_status`, + [resolvedPid] + ); + + log(`Statistiken berechnet: ${result.rows.length} Status-Gruppen.`); + res.json(result.rows); + } catch (e) { + log(`FEHLER beim Laden der Statistiken: ${e.message}`); + res.status(500).json({ error: e.message }); + } finally { + client.release(); + } +}); + // Catch-all route to serve index.html for any other request (optional, good for SPAs) app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'index.html')); diff --git a/style.css b/style.css index d18a929..f78727d 100644 --- a/style.css +++ b/style.css @@ -1,626 +1,626 @@ -:root { - --primary-color: rgb(115, 176, 118); - --primary-hover: rgb(48, 119, 37); - --bg-dark: #001e2e; - --panel-bg: rgba(0, 30, 46, 0.95); - --text-main: rgb(215, 234, 216); - --text-dim: rgb(151, 197, 212); - --border-color: rgba(151, 197, 212, 0.4); - --accent-glow: 0 0 15px rgba(115, 176, 118, 0.4); - --danger-color: rgb(238, 2, 45); - --font-family: 'Inter', system-ui, -apple-system, sans-serif; -} - -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -body { - font-family: var(--font-family); - background-color: var(--bg-dark); - color: var(--text-main); - overflow: hidden; - height: 100vh; - display: flex; -} - -#map { - flex-grow: 1; - height: 100%; - z-index: 1; -} - -/* Sidebar Styling */ -.sidebar { - width: 260px; - height: 100%; - background: var(--panel-bg); - backdrop-filter: blur(12px); - border-right: 1px solid var(--border-color); - z-index: 100; - display: flex; - flex-direction: column; - box-shadow: 4px 0 15px rgba(0, 0, 0, 0.5); -} - -.sidebar-header { - padding: 15px; - border-bottom: 1px solid var(--border-color); -} - -.sidebar-header h1 { - font-size: 1.2rem; - font-weight: 700; - background: linear-gradient(135deg, #fff 0%, var(--primary-color) 100%); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; -} - -.sidebar-content { - flex-grow: 1; - overflow-y: auto; - padding: 12px; -} - -.section { - margin-bottom: 15px; -} - -.section h2 { - font-size: 0.75rem; - text-transform: uppercase; - letter-spacing: 0.5px; - color: var(--text-dim); - margin-bottom: 8px; -} - -/* Control Elements */ -.control-group { - margin-bottom: 12px; -} - -.control-group label { - display: block; - font-size: 0.8rem; - margin-bottom: 4px; - color: var(--text-main); -} - -.input-styled { - width: 100%; - background: rgba(255, 255, 255, 0.05); - border: 1px solid var(--border-color); - padding: 6px 8px; - border-radius: 6px; - color: white; - font-size: 0.85rem; - transition: all 0.3s ease; -} - -.input-styled option { - background: #1e1e1e; - color: white; -} - -/* Compact number inputs */ -.input-number { - width: 80px !important; -} - -.input-styled:focus { - outline: none; - border-color: var(--primary-color); - box-shadow: var(--accent-glow); -} - -.btn-primary { - width: 100%; - background: var(--primary-color); - color: #000; - border: none; - padding: 8px; - border-radius: 6px; - font-weight: 600; - font-size: 0.85rem; - cursor: pointer; - transition: all 0.3s ease; - display: flex; - align-items: center; - justify-content: center; - gap: 6px; -} - -.btn-primary:hover { - background: var(--primary-hover); - transform: translateY(-1px); -} - -.btn-secondary { - width: 100%; - background: transparent; - border: 1px solid var(--primary-color); - color: var(--primary-color); - padding: 6px; - border-radius: 6px; - font-weight: 500; - font-size: 0.8rem; - cursor: pointer; - transition: all 0.3s ease; - margin-top: 5px; -} - -.btn-secondary:hover { - background: rgba(115, 176, 118, 0.1); -} - -/* Variant Switcher */ -.variant-tabs { - display: flex; - background: rgba(255, 255, 255, 0.05); - border-radius: 10px; - padding: 4px; - margin-bottom: 20px; -} - -.variant-tab { - flex: 1; - text-align: center; - padding: 8px; - border-radius: 6px; - cursor: pointer; - font-size: 0.85rem; - transition: all 0.2s ease; -} - -.variant-tab.active { - background: var(--primary-color); - color: #000; - font-weight: 600; -} - -/* Legend */ -.legend-item { - display: flex; - align-items: center; - gap: 10px; - font-size: 0.85rem; - margin-bottom: 8px; - color: var(--text-main); -} - -.color-box { - width: 14px; - height: 14px; - border-radius: 3px; - display: inline-block; -} - -/* WEA Labels */ -.wea-label { - background: var(--panel-bg) !important; - backdrop-filter: blur(4px); - border: 1px solid var(--primary-color) !important; - color: white !important; - border-radius: 4px !important; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3) !important; - padding: 4px 8px !important; -} - -.wea-label::before { - border-right-color: var(--primary-color) !important; -} - -/* UI States */ -.btn-secondary.active { - color: var(--primary-color) !important; - border-color: var(--primary-color) !important; -} - -/* Disable interaction with other layers during turbine placement to allow clicking 'through' them */ -#map.placement-active .leaflet-interactive:not(.turbine-icon-container, .turbine-icon-container *) { - pointer-events: none !important; -} - -/* Floating Panels General */ -.floating-panel { - position: absolute; - z-index: 1000; - background: var(--panel-bg); - backdrop-filter: blur(16px); - -webkit-backdrop-filter: blur(16px); - border: 1px solid var(--border-color); - border-radius: 12px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); - 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; - display: flex; - justify-content: space-between; - align-items: center; - cursor: pointer; - border-bottom: 1px solid var(--border-color); - font-size: 0.85rem; - font-weight: 600; -} - -.panel-content { - padding: 12px; - max-height: 300px; - overflow-y: auto; -} - -/* Floating Legend Specific */ -#floatingLegend { - bottom: 20px; - right: 20px; - width: 220px; -} - -#floatingLegend.collapsed .panel-content { - display: none; -} - -.toggle-btn { - background: transparent; - border: none; - color: var(--text-dim); - cursor: pointer; - font-size: 0.7rem; - transition: transform 0.3s; -} - -.collapsed .toggle-btn { - transform: rotate(180deg); -} - -/* Floating Edit Panel Tweak */ -#editPanel { - position: absolute; - z-index: 2000; - display: none; -} - -.edit-panel-content { - background: var(--panel-bg); - backdrop-filter: blur(16px); - -webkit-backdrop-filter: blur(16px); - border: 1px solid var(--primary-color); - border-radius: 8px; - padding: 10px; - width: 210px; - /* Slimmer */ - max-height: 85vh; - /* DON'T CUT OFF */ - overflow-y: auto; - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5), var(--accent-glow); -} - -.edit-rows-container { - display: flex; - flex-direction: column; - gap: 4px; -} - -.edit-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - margin-bottom: 2px; -} - -.edit-row.separator { - margin-top: 12px; - border-top: 1px solid rgba(255, 255, 255, 0.1); - padding-top: 8px; -} - -.edit-row label { - font-size: 0.75rem; - color: var(--text-dim); - margin: 0; - white-space: nowrap; - width: 65px; - text-align: left; -} - -.edit-input { - flex: 1; - background: rgba(255, 255, 255, 0.1); - border: 1px solid var(--border-color); - padding: 4px 8px; - border-radius: 4px; - color: white; - font-size: 0.85rem; - text-align: left; - width: 100px; -} - -.btn-primary-mini { - background: var(--primary-color); - color: #000; - border: none; - padding: 10px; - border-radius: 6px; - font-weight: 700; - font-size: 0.85rem; - cursor: pointer; - transition: all 0.2s; - margin-top: 8px; -} - -.btn-danger-mini { - background: rgba(238, 2, 45, 0.1); - border: 1px solid var(--danger-color); - color: var(--danger-color); - padding: 8px; - border-radius: 6px; - font-size: 0.8rem; - cursor: pointer; - transition: all 0.2s; -} - -/* Scrollbar styling */ -::-webkit-scrollbar { - width: 6px; -} - -.leaflet-control-layers { - background: var(--panel-bg) !important; - backdrop-filter: blur(12px); - border: 1px solid var(--border-color) !important; - color: white !important; - border-radius: 12px !important; - padding: 10px !important; -} - -/* Proximity Tooltips - Compact & Integrated */ -.proximity-tooltip { - background: rgba(255, 136, 0, 0.8) !important; - border: 1px solid rgba(255, 255, 255, 0.5) !important; - color: #000 !important; - font-weight: 700 !important; - font-size: 0.7rem !important; - border-radius: 3px !important; - padding: 1px 4px !important; - pointer-events: none !important; - box-shadow: none !important; -} - -.proximity-tooltip::before { - display: none !important; -} - -.proximity-tooltip-red { - background: rgba(220, 0, 0, 0.8) !important; - border: 1px solid rgba(255, 255, 255, 0.5) !important; - color: #fff !important; - font-weight: 700 !important; - font-size: 0.7rem !important; - border-radius: 3px !important; - padding: 1px 4px !important; - pointer-events: none !important; - box-shadow: none !important; -} - -.proximity-tooltip-red::before { - display: none !important; -} - -.proximity-tooltip-yellow { - background: rgba(204, 180, 0, 0.8) !important; - border: 1px solid rgba(255, 255, 255, 0.5) !important; - color: #000 !important; - font-weight: 700 !important; - font-size: 0.7rem !important; - border-radius: 3px !important; - padding: 1px 4px !important; - pointer-events: none !important; - box-shadow: none !important; -} - -.proximity-tooltip-yellow::before { - display: none !important; -} - -.proximity-tooltip-violet { - background: rgba(148, 0, 211, 0.8) !important; - border: 1px solid rgba(255, 255, 255, 0.5) !important; - color: #fff !important; - font-weight: 700 !important; - font-size: 0.7rem !important; - border-radius: 3px !important; - padding: 1px 4px !important; - pointer-events: none !important; - box-shadow: none !important; -} - -.proximity-tooltip-violet::before { - display: none !important; -} - -/* Custom Turbine Symbol (Professional SVG) */ -.turbine-icon-container { - display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - transition: transform 0.2s ease; -} - -.turbine-icon-container svg { - filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5)); - width: 100%; - height: 100%; -} - -.turbine-icon-container:hover { - transform: scale(1.15); - cursor: grab; -} - -.turbine-icon-container:active { - cursor: grabbing; -} - -/* Modal System */ -.modal-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.75); - backdrop-filter: blur(8px); - display: flex; - align-items: center; - justify-content: center; - z-index: 2000; -} - -.modal-content { - background: var(--panel-bg); - border: 1px solid var(--border-color); - border-radius: 16px; - width: 800px; - max-width: 90%; - max-height: 85vh; - display: flex; - flex-direction: column; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6); - overflow: hidden; - /* Contain children and scrollbars */ -} - -.modal-header { - padding: 20px; - border-bottom: 1px solid var(--border-color); - display: flex; - justify-content: space-between; - align-items: center; -} - -.modal-header h2 { - font-size: 1.3rem; - color: var(--primary-color); -} - -.modal-close { - background: transparent; - border: none; - color: var(--text-dim); - font-size: 1.5rem; - cursor: pointer; - transition: color 0.2s; -} - -.modal-close:hover { - color: white; -} - -/* Owner Table & Mapping Scrollability */ -#ownerListSection { - display: none; - /* Controlled by JS */ - flex-direction: column; - overflow: hidden; - flex-grow: 1; - min-height: 0; - /* Critical for flex scrolling */ -} - -.owner-table-container, -#ownerMappingSection { - padding: 0; - overflow-y: auto; - flex-grow: 1; -} - -#ownerMappingSection { - display: none; - /* Controlled by JS */ - flex-direction: column; - align-items: center; - padding: 20px; - min-height: 0; - /* Critical for flex scrolling */ -} - -#ownerTable { - width: 100%; - border-collapse: collapse; - font-size: 0.9rem; -} - -#ownerTable th { - text-align: left; - padding: 12px 15px; - background: rgba(255, 255, 255, 0.03); - color: var(--text-dim); - text-transform: uppercase; - font-size: 0.75rem; - letter-spacing: 0.5px; - border-bottom: 1px solid var(--border-color); -} - -#ownerTable td { - padding: 8px 15px; - border-bottom: 1px solid var(--border-color); -} - -#ownerTable tr:hover { - background: rgba(255, 255, 255, 0.02); -} - -/* Status Selector */ -.status-select { - padding: 5px 8px; - border-radius: 6px; - background: rgba(255, 255, 255, 0.05); - border: 1px solid var(--border-color); - color: white; - font-size: 0.8rem; - cursor: pointer; - width: 100%; -} - -/* Status Indicators (UI & Legend) */ -.status-gbr { - color: rgb(115, 176, 118); -} - -/* Green */ -.status-external { - color: rgb(238, 2, 45); -} - -/* Red */ -.status-declined { - color: rgb(238, 2, 45); -} - -/* Yellow -> Red */ -.status-positive { - color: rgb(48, 119, 37); -} - -/* Light Green -> Dark Green */ -.status-undecided { - color: rgb(151, 197, 212); -} - -/* Grey -> Light Blue */ - -/* Selection specific styles */ -select.status-select option { - background: var(--bg-dark); - color: white; -} \ No newline at end of file +:root { + --primary-color: rgb(115, 176, 118); + --primary-hover: rgb(48, 119, 37); + --bg-dark: #001e2e; + --panel-bg: rgba(0, 30, 46, 0.95); + --text-main: rgb(215, 234, 216); + --text-dim: rgb(151, 197, 212); + --border-color: rgba(151, 197, 212, 0.4); + --accent-glow: 0 0 15px rgba(115, 176, 118, 0.4); + --danger-color: rgb(238, 2, 45); + --font-family: 'Inter', system-ui, -apple-system, sans-serif; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: var(--font-family); + background-color: var(--bg-dark); + color: var(--text-main); + overflow: hidden; + height: 100vh; + display: flex; +} + +#map { + flex-grow: 1; + height: 100%; + z-index: 1; +} + +/* Sidebar Styling */ +.sidebar { + width: 260px; + height: 100%; + background: var(--panel-bg); + backdrop-filter: blur(12px); + border-right: 1px solid var(--border-color); + z-index: 100; + display: flex; + flex-direction: column; + box-shadow: 4px 0 15px rgba(0, 0, 0, 0.5); +} + +.sidebar-header { + padding: 15px; + border-bottom: 1px solid var(--border-color); +} + +.sidebar-header h1 { + font-size: 1.2rem; + font-weight: 700; + background: linear-gradient(135deg, #fff 0%, var(--primary-color) 100%); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} + +.sidebar-content { + flex-grow: 1; + overflow-y: auto; + padding: 12px; +} + +.section { + margin-bottom: 15px; +} + +.section h2 { + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text-dim); + margin-bottom: 8px; +} + +/* Control Elements */ +.control-group { + margin-bottom: 12px; +} + +.control-group label { + display: block; + font-size: 0.8rem; + margin-bottom: 4px; + color: var(--text-main); +} + +.input-styled { + width: 100%; + background: rgba(255, 255, 255, 0.05); + border: 1px solid var(--border-color); + padding: 6px 8px; + border-radius: 6px; + color: white; + font-size: 0.85rem; + transition: all 0.3s ease; +} + +.input-styled option { + background: #1e1e1e; + color: white; +} + +/* Compact number inputs */ +.input-number { + width: 80px !important; +} + +.input-styled:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: var(--accent-glow); +} + +.btn-primary { + width: 100%; + background: var(--primary-color); + color: #000; + border: none; + padding: 8px; + border-radius: 6px; + font-weight: 600; + font-size: 0.85rem; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; +} + +.btn-primary:hover { + background: var(--primary-hover); + transform: translateY(-1px); +} + +.btn-secondary { + width: 100%; + background: transparent; + border: 1px solid var(--primary-color); + color: var(--primary-color); + padding: 6px; + border-radius: 6px; + font-weight: 500; + font-size: 0.8rem; + cursor: pointer; + transition: all 0.3s ease; + margin-top: 5px; +} + +.btn-secondary:hover { + background: rgba(115, 176, 118, 0.1); +} + +/* Variant Switcher */ +.variant-tabs { + display: flex; + background: rgba(255, 255, 255, 0.05); + border-radius: 10px; + padding: 4px; + margin-bottom: 20px; +} + +.variant-tab { + flex: 1; + text-align: center; + padding: 8px; + border-radius: 6px; + cursor: pointer; + font-size: 0.85rem; + transition: all 0.2s ease; +} + +.variant-tab.active { + background: var(--primary-color); + color: #000; + font-weight: 600; +} + +/* Legend */ +.legend-item { + display: flex; + align-items: center; + gap: 10px; + font-size: 0.85rem; + margin-bottom: 8px; + color: var(--text-main); +} + +.color-box { + width: 14px; + height: 14px; + border-radius: 3px; + display: inline-block; +} + +/* WEA Labels */ +.wea-label { + background: var(--panel-bg) !important; + backdrop-filter: blur(4px); + border: 1px solid var(--primary-color) !important; + color: white !important; + border-radius: 4px !important; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3) !important; + padding: 4px 8px !important; +} + +.wea-label::before { + border-right-color: var(--primary-color) !important; +} + +/* UI States */ +.btn-secondary.active { + color: var(--primary-color) !important; + border-color: var(--primary-color) !important; +} + +/* Disable interaction with other layers during turbine placement to allow clicking 'through' them */ +#map.placement-active .leaflet-interactive:not(.turbine-icon-container, .turbine-icon-container *) { + pointer-events: none !important; +} + +/* Floating Panels General */ +.floating-panel { + position: absolute; + z-index: 1000; + background: var(--panel-bg); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid var(--border-color); + border-radius: 12px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); + 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; + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + border-bottom: 1px solid var(--border-color); + font-size: 0.85rem; + font-weight: 600; +} + +.panel-content { + padding: 12px; + max-height: 300px; + overflow-y: auto; +} + +/* Floating Legend Specific */ +#floatingLegend { + bottom: 20px; + right: 20px; + width: 220px; +} + +#floatingLegend.collapsed .panel-content { + display: none; +} + +.toggle-btn { + background: transparent; + border: none; + color: var(--text-dim); + cursor: pointer; + font-size: 0.7rem; + transition: transform 0.3s; +} + +.collapsed .toggle-btn { + transform: rotate(180deg); +} + +/* Floating Edit Panel Tweak */ +#editPanel { + position: absolute; + z-index: 2000; + display: none; +} + +.edit-panel-content { + background: var(--panel-bg); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid var(--primary-color); + border-radius: 8px; + padding: 10px; + width: 210px; + /* Slimmer */ + max-height: 85vh; + /* DON'T CUT OFF */ + overflow-y: auto; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5), var(--accent-glow); +} + +.edit-rows-container { + display: flex; + flex-direction: column; + gap: 4px; +} + +.edit-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-bottom: 2px; +} + +.edit-row.separator { + margin-top: 12px; + border-top: 1px solid rgba(255, 255, 255, 0.1); + padding-top: 8px; +} + +.edit-row label { + font-size: 0.75rem; + color: var(--text-dim); + margin: 0; + white-space: nowrap; + width: 65px; + text-align: left; +} + +.edit-input { + flex: 1; + background: rgba(255, 255, 255, 0.1); + border: 1px solid var(--border-color); + padding: 4px 8px; + border-radius: 4px; + color: white; + font-size: 0.85rem; + text-align: left; + width: 100px; +} + +.btn-primary-mini { + background: var(--primary-color); + color: #000; + border: none; + padding: 10px; + border-radius: 6px; + font-weight: 700; + font-size: 0.85rem; + cursor: pointer; + transition: all 0.2s; + margin-top: 8px; +} + +.btn-danger-mini { + background: rgba(238, 2, 45, 0.1); + border: 1px solid var(--danger-color); + color: var(--danger-color); + padding: 8px; + border-radius: 6px; + font-size: 0.8rem; + cursor: pointer; + transition: all 0.2s; +} + +/* Scrollbar styling */ +::-webkit-scrollbar { + width: 6px; +} + +.leaflet-control-layers { + background: var(--panel-bg) !important; + backdrop-filter: blur(12px); + border: 1px solid var(--border-color) !important; + color: white !important; + border-radius: 12px !important; + padding: 10px !important; +} + +/* Proximity Tooltips - Compact & Integrated */ +.proximity-tooltip { + background: rgba(255, 136, 0, 0.8) !important; + border: 1px solid rgba(255, 255, 255, 0.5) !important; + color: #000 !important; + font-weight: 700 !important; + font-size: 0.7rem !important; + border-radius: 3px !important; + padding: 1px 4px !important; + pointer-events: none !important; + box-shadow: none !important; +} + +.proximity-tooltip::before { + display: none !important; +} + +.proximity-tooltip-red { + background: rgba(220, 0, 0, 0.8) !important; + border: 1px solid rgba(255, 255, 255, 0.5) !important; + color: #fff !important; + font-weight: 700 !important; + font-size: 0.7rem !important; + border-radius: 3px !important; + padding: 1px 4px !important; + pointer-events: none !important; + box-shadow: none !important; +} + +.proximity-tooltip-red::before { + display: none !important; +} + +.proximity-tooltip-yellow { + background: rgba(204, 180, 0, 0.8) !important; + border: 1px solid rgba(255, 255, 255, 0.5) !important; + color: #000 !important; + font-weight: 700 !important; + font-size: 0.7rem !important; + border-radius: 3px !important; + padding: 1px 4px !important; + pointer-events: none !important; + box-shadow: none !important; +} + +.proximity-tooltip-yellow::before { + display: none !important; +} + +.proximity-tooltip-violet { + background: rgba(148, 0, 211, 0.8) !important; + border: 1px solid rgba(255, 255, 255, 0.5) !important; + color: #fff !important; + font-weight: 700 !important; + font-size: 0.7rem !important; + border-radius: 3px !important; + padding: 1px 4px !important; + pointer-events: none !important; + box-shadow: none !important; +} + +.proximity-tooltip-violet::before { + display: none !important; +} + +/* Custom Turbine Symbol (Professional SVG) */ +.turbine-icon-container { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + transition: transform 0.2s ease; +} + +.turbine-icon-container svg { + filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5)); + width: 100%; + height: 100%; +} + +.turbine-icon-container:hover { + transform: scale(1.15); + cursor: grab; +} + +.turbine-icon-container:active { + cursor: grabbing; +} + +/* Modal System */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.75); + backdrop-filter: blur(8px); + display: flex; + align-items: center; + justify-content: center; + z-index: 2000; +} + +.modal-content { + background: var(--panel-bg); + border: 1px solid var(--border-color); + border-radius: 16px; + width: 800px; + max-width: 90%; + max-height: 85vh; + display: flex; + flex-direction: column; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6); + overflow: hidden; + /* Contain children and scrollbars */ +} + +.modal-header { + padding: 20px; + border-bottom: 1px solid var(--border-color); + display: flex; + justify-content: space-between; + align-items: center; +} + +.modal-header h2 { + font-size: 1.3rem; + color: var(--primary-color); +} + +.modal-close { + background: transparent; + border: none; + color: var(--text-dim); + font-size: 1.5rem; + cursor: pointer; + transition: color 0.2s; +} + +.modal-close:hover { + color: white; +} + +/* Owner Table & Mapping Scrollability */ +#ownerListSection { + display: none; + /* Controlled by JS */ + flex-direction: column; + overflow: hidden; + flex-grow: 1; + min-height: 0; + /* Critical for flex scrolling */ +} + +.owner-table-container, +#ownerMappingSection { + padding: 0; + overflow-y: auto; + flex-grow: 1; +} + +#ownerMappingSection { + display: none; + /* Controlled by JS */ + flex-direction: column; + align-items: center; + padding: 20px; + min-height: 0; + /* Critical for flex scrolling */ +} + +#ownerTable { + width: 100%; + border-collapse: collapse; + font-size: 0.9rem; +} + +#ownerTable th { + text-align: left; + padding: 12px 15px; + background: rgba(255, 255, 255, 0.03); + color: var(--text-dim); + text-transform: uppercase; + font-size: 0.75rem; + letter-spacing: 0.5px; + border-bottom: 1px solid var(--border-color); +} + +#ownerTable td { + padding: 8px 15px; + border-bottom: 1px solid var(--border-color); +} + +#ownerTable tr:hover { + background: rgba(255, 255, 255, 0.02); +} + +/* Status Selector */ +.status-select { + padding: 5px 8px; + border-radius: 6px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid var(--border-color); + color: white; + font-size: 0.8rem; + cursor: pointer; + width: 100%; +} + +/* Status Indicators (UI & Legend) */ +.status-gbr { + color: rgb(115, 176, 118); +} + +/* Green */ +.status-external { + color: rgb(238, 2, 45); +} + +/* Red */ +.status-declined { + color: rgb(238, 2, 45); +} + +/* Yellow -> Red */ +.status-positive { + color: rgb(48, 119, 37); +} + +/* Light Green -> Dark Green */ +.status-undecided { + color: rgb(151, 197, 212); +} + +/* Grey -> Light Blue */ + +/* Selection specific styles */ +select.status-select option { + background: var(--bg-dark); + color: white; +} \ No newline at end of file