feat(windkarte): Stellungnahme-Kriterien per Windvorranggebiet
Jedes Windvorranggebiet kann jetzt 1–n text_fragment-Einträge als Stellungnahme-Kriterien referenzieren. Das Panel lädt diese lazy beim Öffnen und zeigt sie als aufklappbare Accordion-Sektion. Außerdem: alle hardcodierten Strings im Karten-Block (Panel, Map, Block) durch t()-Aufrufe ersetzt; 7 neue Translation-Keys ergänzt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
<script lang="ts">
|
||||
import Icon from "@iconify/svelte";
|
||||
import "$lib/iconify-offline";
|
||||
import type { WindArea } from "$lib/windkarte";
|
||||
import type { WindArea, TextFragment } from "$lib/windkarte";
|
||||
import { useTranslate, T } from "$lib/translations";
|
||||
|
||||
let {
|
||||
area,
|
||||
center,
|
||||
cmsBase,
|
||||
onclose,
|
||||
}: {
|
||||
area: WindArea | null;
|
||||
center: [number, number] | null;
|
||||
cmsBase: string;
|
||||
onclose: () => void;
|
||||
} = $props();
|
||||
|
||||
@@ -34,6 +36,33 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
let kriterien = $state<TextFragment[]>([]);
|
||||
let loadingKriterien = $state(false);
|
||||
let expandedSlug = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
const slugs = (area?.stellungnahme_kriterien ?? [])
|
||||
.map((k) => (typeof k === "string" ? k : k._slug))
|
||||
.filter(Boolean);
|
||||
|
||||
kriterien = [];
|
||||
expandedSlug = null;
|
||||
|
||||
if (slugs.length === 0) return;
|
||||
|
||||
loadingKriterien = true;
|
||||
Promise.all(
|
||||
slugs.map((slug) =>
|
||||
fetch(`${cmsBase}/api/content/text_fragment/${slug}`)
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.catch(() => null),
|
||||
),
|
||||
).then((results) => {
|
||||
kriterien = results.filter(Boolean) as TextFragment[];
|
||||
loadingKriterien = false;
|
||||
});
|
||||
});
|
||||
|
||||
let copied = $state(false);
|
||||
async function copyLink() {
|
||||
await navigator.clipboard.writeText(location.href);
|
||||
@@ -49,10 +78,10 @@
|
||||
const exportInfo = $derived(
|
||||
area
|
||||
? [
|
||||
`Gebiet ${area.gebiets_nr}${area.bezeichnung ? ` – ${area.bezeichnung}` : ''}`,
|
||||
area.flaeche_ha != null ? `Fläche: ${area.flaeche_ha.toFixed(1)} ha` : null,
|
||||
area.anlagen_geplant != null ? `Geplante Anlagen: ${area.anlagen_geplant}` : null,
|
||||
area.hoehe_max_m != null ? `Max. Nabenhöhe: ${area.hoehe_max_m} m` : null,
|
||||
`${area.gebiets_nr}${area.bezeichnung ? ` – ${area.bezeichnung}` : ''}`,
|
||||
area.flaeche_ha != null ? `${t(T.windkarte_label_flaeche)}: ${area.flaeche_ha.toFixed(1)} ha` : null,
|
||||
area.anlagen_geplant != null ? `${t(T.windkarte_label_anlagen_geplant)}: ${area.anlagen_geplant}` : null,
|
||||
area.hoehe_max_m != null ? `${t(T.windkarte_label_hoehe_max)}: ${area.hoehe_max_m} m` : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
@@ -136,6 +165,39 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Stellungnahme-Kriterien -->
|
||||
{#if loadingKriterien}
|
||||
<div class="flex items-center gap-2 text-xs text-stein-400">
|
||||
<Icon icon="mdi:loading" class="size-3.5 animate-spin" />
|
||||
<span>{t(T.windkarte_kriterien_loading)}</span>
|
||||
</div>
|
||||
{:else if kriterien.length > 0}
|
||||
<div>
|
||||
<p class="mb-2 text-xs font-medium uppercase tracking-wide text-stein-500">{t(T.windkarte_label_kriterien)}</p>
|
||||
<div class="divide-y divide-stein-100 rounded-lg border border-stein-100 overflow-hidden">
|
||||
{#each kriterien as k (k._slug)}
|
||||
<div>
|
||||
<button
|
||||
onclick={() => { expandedSlug = expandedSlug === k._slug ? null : k._slug; }}
|
||||
class="flex w-full items-center justify-between gap-2 px-3 py-2 text-left text-sm font-medium text-stein-700 hover:bg-stein-50 transition-colors"
|
||||
>
|
||||
<span class="min-w-0 truncate">{k.title ?? k._slug}</span>
|
||||
<Icon
|
||||
icon={expandedSlug === k._slug ? "mdi:chevron-up" : "mdi:chevron-down"}
|
||||
class="size-4 shrink-0 text-stein-400"
|
||||
/>
|
||||
</button>
|
||||
{#if expandedSlug === k._slug && k.text}
|
||||
<div class="px-3 pb-3 pt-1 text-sm text-stein-700 leading-relaxed markdown prose prose-sm prose-zinc max-w-none border-t border-stein-100">
|
||||
{@html k.text}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Stellungnahme -->
|
||||
{#if stellungnahmeHref(area.stellungnahme)}
|
||||
<a
|
||||
@@ -171,12 +233,12 @@
|
||||
|
||||
<!-- Export-Downloads -->
|
||||
<div>
|
||||
<p class="mb-2 text-xs font-medium uppercase tracking-wide text-stein-500">Geodaten-Export ({area.gebiets_nr})</p>
|
||||
<p class="mb-2 text-xs font-medium uppercase tracking-wide text-stein-500">{t(T.windkarte_label_geodaten_export, { nr: area.gebiets_nr })}</p>
|
||||
<div class="flex gap-2">
|
||||
<a
|
||||
href="/api/vorranggebiete.geojson?id={encodeURIComponent(area.gebiets_nr)}"
|
||||
download
|
||||
title={`GeoJSON herunterladen\n${exportInfo}`}
|
||||
title={`${t(T.windkarte_action_download_geojson)}\n${exportInfo}`}
|
||||
class="flex flex-1 items-center justify-center gap-1.5 rounded-lg border border-stein-200 px-2 py-2 text-xs font-medium text-stein-600 no-underline hover:bg-stein-50 transition-colors whitespace-nowrap"
|
||||
>
|
||||
<Icon icon="mdi:download" class="size-3.5 shrink-0" />
|
||||
@@ -185,7 +247,7 @@
|
||||
<a
|
||||
href="/api/vorranggebiete.kml?id={encodeURIComponent(area.gebiets_nr)}"
|
||||
download
|
||||
title={`KML herunterladen\n${exportInfo}`}
|
||||
title={`${t(T.windkarte_action_download_kml)}\n${exportInfo}`}
|
||||
class="flex flex-1 items-center justify-center gap-1.5 rounded-lg border border-stein-200 px-2 py-2 text-xs font-medium text-stein-600 no-underline hover:bg-stein-50 transition-colors whitespace-nowrap"
|
||||
>
|
||||
<Icon icon="mdi:download" class="size-3.5 shrink-0" />
|
||||
|
||||
Reference in New Issue
Block a user