2026-04-01 18:38:49 +02:00
< script lang = "ts" >
import { onMount } from 'svelte';
2026-04-01 21:21:01 +02:00
import { fetchKetten , fetchKette , fetchVorlage , reevalVorlage , fetchJobStatus , type KetteKurz , type KetteDetail , type VorlageDetail , type Paginated } from '$lib/api';
2026-04-01 18:38:49 +02:00
import { formatDate , typLabel } from '$lib/status';
import { filters , mergeFilterParams , filterVersion } from '$lib/filters.svelte';
import Ampel from '$lib/components/Ampel.svelte';
import StatusBadge from '$lib/components/StatusBadge.svelte';
// State
let ketten: KetteKurz[] = $state([]);
let kettenTotal = $state(0);
let selectedKette: KetteDetail | null = $state(null);
let selectedVorlage: VorlageDetail | null = $state(null);
let loading = $state(true);
let ketteLoading = $state(false);
let vorlageLoading = $state(false);
let error = $state('');
// Filters
let suche = $state('');
let strangFilter = $state('');
2026-04-01 20:25:25 +02:00
let statusFilter = $state('');
2026-04-01 18:38:49 +02:00
let currentPage = $state(1);
const PAGE_SIZE = 30;
// Active IDs
let activeKetteId = $state< number | null > (null);
let activeVorlageId = $state< number | null > (null);
// Mobile tab
let mobileTab = $state< 'liste' | 'kette' | 'detail'>('liste');
let showVolltext = $state(false);
2026-04-01 21:15:17 +02:00
let showVersionen = $state(false);
2026-04-01 23:42:02 +02:00
let showUmsetzungVersionen = $state(false);
2026-04-01 21:21:01 +02:00
let showReeval = $state(false);
let reevalAnmerkung = $state('');
let reevalStatus = $state< 'idle' | 'running' | 'done' | 'error'>('idle');
let reevalError = $state('');
async function triggerReeval() {
if (!selectedVorlage) return;
reevalStatus = 'running';
reevalError = '';
try {
const { job_id } = await reevalVorlage(selectedVorlage.id, reevalAnmerkung);
for (let i = 0; i < 60 ; i ++) {
await new Promise(r => setTimeout(r, 3000));
const status = await fetchJobStatus(job_id);
if (status.status === 'done') {
reevalStatus = 'done';
selectedVorlage = await fetchVorlage(selectedVorlage!.id);
showReeval = false;
reevalAnmerkung = '';
return;
}
if (status.status === 'error') {
reevalStatus = 'error';
reevalError = status.error || 'Unbekannter Fehler';
return;
}
}
reevalStatus = 'error';
reevalError = 'Timeout nach 3 Minuten';
} catch (e) {
reevalStatus = 'error';
reevalError = e instanceof Error ? e.message : 'Fehler';
}
}
2026-04-01 18:38:49 +02:00
const STRANG_TABS = [
{ value : '' , label : 'Alle' } ,
{ value : 'antrag' , label : 'Anträge' } ,
{ value : 'anfrage' , label : 'Anfragen' } ,
{ value : 'beschlussvorlage' , label : 'Beschlussvorlagen' } ,
{ value : 'mitteilung' , label : 'Mitteilungen' } ,
];
2026-04-01 20:25:25 +02:00
const STATUS_OPTIONS = [
{ value : '' , label : 'Alle Status' } ,
{ value : 'in_beratung' , label : '⏳ In Beratung' } ,
{ value : 'beschlossen' , label : '🟡 Beschlossen' } ,
{ value : 'umgesetzt' , label : '🟢 Umgesetzt' } ,
{ value : 'teilweise_umgesetzt' , label : '🟡 Teilweise' } ,
{ value : 'versandet' , label : '🔴 Versandet' } ,
{ value : 'abgelehnt' , label : '🔴 Abgelehnt' } ,
{ value : 'beantwortet' , label : '🟢 Beantwortet' } ,
{ value : 'angefragt' , label : '⏳ Angefragt' } ,
];
2026-04-01 18:38:49 +02:00
const FARB_MAP: Record< string , string > = {
gruen: '#22c55e',
gelb: '#eab308',
rot: '#ef4444',
amber: '#f59e0b',
grau: '#d1d5db',
blau: '#3b82f6',
};
async function loadKetten() {
loading = true;
try {
let params: Record< string , string > = {
page: String(currentPage),
page_size: String(PAGE_SIZE),
};
if (suche) params.suche = suche;
if (strangFilter) params.typ = strangFilter;
2026-04-01 20:25:25 +02:00
if (statusFilter) params.status = statusFilter;
2026-04-01 18:38:49 +02:00
params = mergeFilterParams(params);
const data = await fetchKetten(params);
ketten = data.items;
kettenTotal = data.total;
} catch (e) {
error = e instanceof Error ? e.message : 'Fehler';
} finally {
loading = false;
}
}
async function selectKette(id: number) {
if (activeKetteId === id) return;
activeKetteId = id;
activeVorlageId = null;
selectedVorlage = null;
ketteLoading = true;
mobileTab = 'kette';
try {
selectedKette = await fetchKette(id);
// Auto-select the first glied (most recent = last position)
if (selectedKette.glieder.length > 0) {
const sorted = [...selectedKette.glieder].sort((a, b) => b.position - a.position);
selectVorlage(sorted[0].vorlage.id);
}
} catch (e) {
error = e instanceof Error ? e.message : 'Fehler';
} finally {
ketteLoading = false;
}
}
async function selectVorlage(id: number) {
if (activeVorlageId === id) return;
activeVorlageId = id;
vorlageLoading = true;
showVolltext = false;
mobileTab = 'detail';
try {
selectedVorlage = await fetchVorlage(id);
} catch (e) {
error = e instanceof Error ? e.message : 'Fehler';
} finally {
vorlageLoading = false;
}
}
function handleSearch(e: KeyboardEvent) {
if (e.key === 'Enter') {
currentPage = 1;
loadKetten();
}
}
function changeStrang(value: string) {
strangFilter = value;
currentPage = 1;
loadKetten();
}
function goPage(p: number) {
currentPage = p;
loadKetten();
}
onMount(() => {
loadKetten();
});
// Reload on global filter change
$effect(() => {
filterVersion();
currentPage = 1;
loadKetten();
});
// Sorted glieder for timeline (newest first)
let sortedGlieder = $derived(
selectedKette ? [...selectedKette.glieder].sort((a, b) => b.position - a.position) : []
);
let totalPages = $derived(Math.ceil(kettenTotal / PAGE_SIZE));
< / script >
< svelte:head >
< title > Explorer - Antragstracker Hagen< / title >
< / svelte:head >
<!-- Mobile Tabs -->
< div class = "lg:hidden flex border-b border-gray-200 mb-4 bg-white rounded-t-lg" >
< button
onclick={() => mobileTab = 'liste' }
class="flex-1 py-3 text-sm font-medium text-center border-b-2 transition-colors
{ mobileTab === 'liste' ? 'border-green-600 text-green-700' : 'border-transparent text-gray-500 hover:text-gray-700' } ">
Liste
< / button >
< button
onclick={() => mobileTab = 'kette' }
class="flex-1 py-3 text-sm font-medium text-center border-b-2 transition-colors
{ mobileTab === 'kette' ? 'border-green-600 text-green-700' : 'border-transparent text-gray-500 hover:text-gray-700' } "
disabled={ ! selectedKette } >
Kette
< / button >
< button
onclick={() => mobileTab = 'detail' }
class="flex-1 py-3 text-sm font-medium text-center border-b-2 transition-colors
{ mobileTab === 'detail' ? 'border-green-600 text-green-700' : 'border-transparent text-gray-500 hover:text-gray-700' } "
disabled={ ! selectedVorlage } >
Detail
< / button >
< / div >
<!-- 3 - Panel Layout -->
< div class = "flex gap-0 lg:gap-0 h-[calc(100vh-12rem)] lg:h-[calc(100vh-11rem)]" >
<!-- Panel 1: Ketten - Liste -->
< div class = "w-full lg:w- [ 280px ] lg:shrink-0 lg:border-r border-gray-200 flex flex-col bg-white rounded-l-lg lg:rounded-l-xl overflow-hidden
{ mobileTab !== 'liste' ? 'hidden lg:flex' : 'flex' } ">
<!-- Search & Filters -->
< div class = "p-3 border-b border-gray-100 space-y-2" >
< input
type="text"
bind:value={ suche }
placeholder="Suche..."
onkeydown={ handleSearch }
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-green-500 focus:border-green-500"
/>
< div class = "flex flex-wrap gap-1" >
{ #each STRANG_TABS as tab }
< button
onclick={() => changeStrang ( tab . value )}
class="px-2 py-1 rounded text-xs font-medium transition-all
{ strangFilter === tab . value
? 'bg-green-600 text-white'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'}">
{ tab . label }
< / button >
{ /each }
< / div >
2026-04-01 20:25:25 +02:00
< select
bind:value={ statusFilter }
onchange={() => { currentPage = 1 ; loadKetten (); }}
class="w-full border border-gray-300 rounded-lg px-2 py-1.5 text-xs focus:ring-2 focus:ring-green-500 bg-white">
{ #each STATUS_OPTIONS as opt }
< option value = { opt . value } > { opt . label } </option >
{ /each }
< / select >
2026-04-01 18:38:49 +02:00
< / div >
<!-- Count -->
< div class = "px-3 py-1.5 text-xs text-gray-400 border-b border-gray-50" >
{ kettenTotal } Ketten
< / div >
<!-- List -->
< div class = "flex-1 overflow-y-auto" >
{ #if loading && ketten . length === 0 }
< div class = "flex justify-center py-10" >
< div class = "animate-spin rounded-full h-8 w-8 border-b-2 border-green-600" > < / div >
< / div >
{ :else if ketten . length === 0 }
< div class = "p-4 text-sm text-gray-500 text-center" > Keine Ketten gefunden< / div >
{ : else }
{ #each ketten as kette }
< button
onclick={() => selectKette ( kette . id )}
class="w-full text-left px-3 py-2.5 border-b border-gray-50 hover:bg-gray-50 transition-colors
{ activeKetteId === kette . id ? 'bg-green-50 border-l-2 border-l-green-600' : 'border-l-2 border-l-transparent' } ">
< div class = "flex items-center justify-between gap-2 mb-0.5" >
< span class = "font-mono text-xs font-medium text-green-700 truncate" >
{ kette . ursprung ? . aktenzeichen || `#$ { kette . id } ` }
< / span >
{ #if kette . ampel }
2026-04-01 20:25:25 +02:00
< span class = "flex items-center gap-1 shrink-0" >
< span
class="w-3 h-3 rounded-full"
style="background-color: { FARB_MAP [ kette . ampel . farbe ] || FARB_MAP . grau } "
>< / span >
< span class = "text-[10px] text-gray-500" > { kette . ampel . label } </ span >
< / span >
2026-04-01 18:38:49 +02:00
{ /if }
< / div >
< p class = "text-xs text-gray-600 line-clamp-2 leading-snug" >
{ kette . thema || kette . ursprung ? . betreff || '-' }
< / p >
< / button >
{ /each }
<!-- Pagination -->
{ #if totalPages > 1 }
< div class = "flex items-center justify-center gap-2 p-3 border-t border-gray-100" >
< button
disabled={ currentPage <= 1 }
onclick={() => goPage ( currentPage - 1 )}
class="px-2 py-1 rounded text-xs border border-gray-300 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed">
‹
< / button >
< span class = "text-xs text-gray-500" > { currentPage } /{ totalPages } </ span >
< button
disabled={ currentPage >= totalPages }
onclick={() => goPage ( currentPage + 1 )}
class="px-2 py-1 rounded text-xs border border-gray-300 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed">
›
< / button >
< / div >
{ /if }
{ /if }
< / div >
< / div >
<!-- Panel 2: Kette Detail -->
< div class = "w-full lg:w- [ 220px ] lg:shrink-0 lg:border-r border-gray-200 flex flex-col bg-white overflow-hidden
{ mobileTab !== 'kette' ? 'hidden lg:flex' : 'flex' } ">
{ #if ketteLoading }
< div class = "flex justify-center py-10" >
< div class = "animate-spin rounded-full h-8 w-8 border-b-2 border-green-600" > < / div >
< / div >
{ :else if selectedKette }
< div class = "flex-1 overflow-y-auto" >
<!-- Ampel -->
< div class = "p-4 border-b border-gray-100" >
< div class = "text-xs font-medium text-gray-500 uppercase mb-3" >
{ selectedKette . ampel ? . strang_label || selectedKette . typ || 'Status' }
< / div >
{ #if selectedKette . ampel }
< Ampel ampel = { selectedKette . ampel } vertical />
{ : else }
< StatusBadge status = { selectedKette . status } / >
{ /if }
< / div >
2026-04-01 23:23:25 +02:00
<!-- Umsetzungsgrad -->
{ #if selectedKette . umsetzung }
{ @const u = selectedKette . umsetzung }
< div class = "px-4 pb-3 border-b border-gray-100" >
< div class = "flex items-center gap-2 mb-1.5" >
< div class = "w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold
{ u . score >= 0.7 ? 'bg-green-200 text-green-800' : u . score >= 0.4 ? 'bg-amber-200 text-amber-800' : 'bg-red-200 text-red-800' } ">
{ Math . round (( u . score || 0 ) * 100 )} %
< / div >
< div >
< div class = "text-xs font-semibold { u . score >= 0.7 ? 'text-green-800' : u . score >= 0.4 ? 'text-amber-800' : 'text-red-800' } " >
{ u . bewertung || ( u . score >= 0.7 ? 'Umgesetzt' : u . score >= 0.4 ? 'Teilweise' : 'Kaum umgesetzt' )}
< / div >
{ #if u . kernpunkt_erfuellt !== null && u . kernpunkt_erfuellt !== undefined }
< div class = "text-[10px] text-gray-500" >
Kernpunkt: { u . kernpunkt_erfuellt ? '✅ erfüllt' : '❌ nicht erfüllt' }
< / div >
{ /if }
< / div >
< / div >
{ #if u . begruendung }
< p class = "text-[11px] text-gray-600 leading-snug" > { u . begruendung } </ p >
{ /if }
{ #if u . details }
< p class = "text-[10px] text-gray-500 mt-1 leading-snug" > { u . details } </ p >
{ /if }
2026-04-01 23:42:02 +02:00
{ #if selectedKette . umsetzung_versionen ? . length }
< button onclick = {() => showUmsetzungVersionen = ! showUmsetzungVersionen }
class="text-[10px] text-gray-400 hover:text-gray-600 mt-2 flex items-center gap-1">
< span > { showUmsetzungVersionen ? '▼' : '▶' } </ span >
{ selectedKette . umsetzung_versionen . length } vorherige Bewertung{ selectedKette . umsetzung_versionen . length > 1 ? 'en' : '' }
< / button >
{ #if showUmsetzungVersionen }
< div class = "mt-2 space-y-2" >
{ #each selectedKette . umsetzung_versionen as v }
< div class = "rounded border border-gray-200 bg-gray-50 p-2" >
< div class = "flex items-center gap-2 mb-1" >
< span class = "text-[10px] font-bold { v . score >= 0.7 ? 'text-green-700' : v . score >= 0.4 ? 'text-amber-700' : 'text-red-700' } " >
{ Math . round (( v . score || 0 ) * 100 )} %
< / span >
< span class = "text-[10px] text-gray-400" > { v . erstellt_at || '' } </ span >
< / div >
< p class = "text-[10px] text-gray-500" > { v . begruendung } </ p >
< / div >
{ /each }
< / div >
{ /if }
{ /if }
2026-04-01 23:23:25 +02:00
< / div >
{ /if }
2026-04-01 18:38:49 +02:00
<!-- Timeline -->
< div class = "p-3" >
< div class = "text-xs font-medium text-gray-500 uppercase mb-3" > Ketten-Glieder< / div >
< div class = "relative" >
<!-- Vertical line -->
< div class = "absolute left-3 top-2 bottom-2 w-0.5 bg-gray-200" > < / div >
{ #each sortedGlieder as glied , i }
< button
onclick={() => selectVorlage ( glied . vorlage . id )}
class="relative w-full text-left pl-8 pr-2 py-2 rounded-lg hover:bg-gray-50 transition-colors mb-1
{ activeVorlageId === glied . vorlage . id ? 'bg-green-50' : '' } ">
<!-- Dot -->
< div class = "absolute left-1 . 5 top-3 . 5 w-3 h-3 rounded-full border-2 transition-colors
{ activeVorlageId === glied . vorlage . id
? 'bg-green-600 border-green-600'
: 'bg-white border-gray-300'}">
< / div >
<!-- Content -->
< div class = "flex items-center gap-1.5 mb-0.5" >
< span class = "font-mono text-[11px] font-medium text-green-700 truncate" >
{ glied . vorlage . aktenzeichen || `#$ { glied . vorlage . id } ` }
< / span >
< / div >
{ #if glied . rolle }
< span class = "inline-block text-[10px] px-1.5 py-0.5 rounded bg-gray-100 text-gray-600 mb-0.5" >
{ glied . rolle }
< / span >
{ /if }
{ #if glied . vorlage . datum_eingang }
< div class = "text-[10px] text-gray-400" >
{ formatDate ( glied . vorlage . datum_eingang )}
< / div >
{ /if }
< / button >
{ /each }
< / div >
< / div >
< / div >
{ : else }
< div class = "flex items-center justify-center h-full text-sm text-gray-400 p-4 text-center" >
← Kette auswählen
< / div >
{ /if }
< / div >
<!-- Panel 3: Vorlage Detail -->
< div class = "w-full lg:flex-1 lg:min-w-0 flex flex-col bg-white rounded-r-lg lg:rounded-r-xl overflow-hidden
{ mobileTab !== 'detail' ? 'hidden lg:flex' : 'flex' } ">
{ #if vorlageLoading }
< div class = "flex justify-center py-10" >
< div class = "animate-spin rounded-full h-8 w-8 border-b-2 border-green-600" > < / div >
< / div >
{ :else if selectedVorlage }
< div class = "flex-1 overflow-y-auto p-4 sm:p-6 space-y-5" >
<!-- Header -->
< div >
< div class = "flex flex-wrap items-center gap-2 mb-2" >
{ #if selectedVorlage . aktenzeichen }
< h2 class = "text-xl font-bold text-gray-900 font-mono" > { selectedVorlage . aktenzeichen } </ h2 >
{ /if }
{ #if selectedVorlage . typ }
< span class = "text-xs px-2 py-0.5 rounded bg-gray-100 text-gray-600" > { typLabel ( selectedVorlage . typ )} </ span >
{ /if }
{ #if selectedVorlage . ist_verwaltungsvorlage }
< span class = "text-xs px-2 py-0.5 rounded bg-blue-100 text-blue-700" > Verwaltungsvorlage< / span >
{ /if }
< / div >
{ #if selectedVorlage . betreff }
< p class = "text-gray-700" > { selectedVorlage . betreff } </ p >
{ /if }
< div class = "flex flex-wrap items-center gap-3 mt-2 text-sm text-gray-500" >
{ #if selectedVorlage . datum_eingang }
< span > Eingegangen: < strong > { formatDate ( selectedVorlage . datum_eingang )} </ strong ></ span >
{ /if }
{ #if selectedVorlage . web_url }
< a href = { selectedVorlage . web_url } target="_blank" rel = "noopener" class = "text-green-600 hover:underline" > ALLRIS ↗</ a >
{ /if }
{ #if selectedVorlage . pdf_url }
< a href = { selectedVorlage . pdf_url } target="_blank" rel = "noopener" class = "text-green-600 hover:underline" > PDF ↗</ a >
{ /if }
< a href = "/vorlagen/ { selectedVorlage . id } " class = "text-green-600 hover:underline" > Vollansicht →</ a >
< / div >
<!-- Antragsteller -->
{ #if selectedVorlage . antragsteller ? . length > 0 }
< div class = "mt-2 flex items-center gap-2" >
< span class = "text-xs text-gray-500" > Antragsteller:< / span >
{ #each selectedVorlage . antragsteller as p }
< span class = "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium"
style="background-color: { p . farbe || '#e5e7eb' } 20; color: { p . farbe || '#4b5563' } ; border: 1px solid { p . farbe || '#d1d5db' } ">
{ p . kuerzel }
< / span >
{ /each }
< / div >
{ /if }
< / div >
<!-- KI - Zusammenfassung -->
{ #if selectedVorlage . ki_zusammenfassung }
< div class = "bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl border border-green-200 p-5" >
< h3 class = "text-sm font-semibold text-green-800 mb-2 flex items-center gap-1.5" >
< span > 🤖< / span > KI-Zusammenfassung
< / h3 >
< p class = "text-sm text-gray-700 mb-3" > { selectedVorlage . ki_zusammenfassung . zusammenfassung } </ p >
{ #if selectedVorlage . ki_zusammenfassung . kernforderung }
< div class = "mb-2" >
< span class = "text-xs font-medium text-green-700 uppercase" > Kernforderung:< / span >
< p class = "text-sm text-gray-800 font-medium" > { selectedVorlage . ki_zusammenfassung . kernforderung } </ p >
< / div >
{ /if }
{ #if selectedVorlage . ki_zusammenfassung . begruendung }
< div class = "mb-2" >
< span class = "text-xs font-medium text-green-700 uppercase" > Begründung:< / span >
< p class = "text-xs text-gray-600" > { selectedVorlage . ki_zusammenfassung . begruendung } </ p >
< / div >
{ /if }
< div class = "flex flex-wrap gap-1.5 mt-3" >
{ #if selectedVorlage . ki_zusammenfassung . thema }
< span class = "text-xs px-2 py-0.5 rounded-full bg-green-100 text-green-800" > 📂 { selectedVorlage . ki_zusammenfassung . thema } </ span >
{ /if }
{ #if selectedVorlage . ki_zusammenfassung . partei }
< span class = "text-xs px-2 py-0.5 rounded-full bg-purple-100 text-purple-800" > 🏛️ { selectedVorlage . ki_zusammenfassung . partei } </ span >
{ /if }
{ #each selectedVorlage . ki_zusammenfassung . betroffene_orte || [] as ort }
< span class = "text-xs px-2 py-0.5 rounded-full bg-blue-100 text-blue-800" > 📍 { ort } </ span >
{ /each }
< / div >
< / div >
{ /if }
2026-04-01 21:15:17 +02:00
<!-- Vorherige KI - Versionen -->
{ #if selectedVorlage . ki_versionen ? . length }
< div >
< button onclick = {() => showVersionen = ! showVersionen }
class="text-xs text-gray-500 hover:text-gray-700 flex items-center gap-1">
< span > { showVersionen ? '▼' : '▶' } </ span >
{ selectedVorlage . ki_versionen . length } vorherige Version{ selectedVorlage . ki_versionen . length > 1 ? 'en' : '' }
< / button >
{ #if showVersionen }
< div class = "mt-2 space-y-3" >
{ #each selectedVorlage . ki_versionen as v , i }
< div class = "rounded-lg border border-gray-200 bg-gray-50 p-4" >
< div class = "flex items-center justify-between mb-2" >
< span class = "text-xs text-gray-400" > Version { selectedVorlage . ki_versionen . length - i } · { v . erstellt_at || 'unbekannt' } </ span >
< span class = "text-[10px] px-1.5 py-0.5 rounded bg-gray-200 text-gray-500" > { v . prompt_version || '' } </ span >
< / div >
< p class = "text-sm text-gray-600" > { v . zusammenfassung } </ p >
{ #if v . kernforderung }
< p class = "text-xs text-gray-500 mt-1" >< strong > Kernforderung:</ strong > { v . kernforderung } </ p >
{ /if }
< / div >
{ /each }
< / div >
{ /if }
< / div >
{ /if }
2026-04-01 23:42:02 +02:00
<!-- Umsetzungsbewertung → jetzt in der Ketten - Ansicht (Panel 2) -->
2026-04-01 18:38:49 +02:00
2026-04-01 21:21:01 +02:00
<!-- Neu bewerten -->
< div class = "rounded-xl border border-gray-200 p-4" >
{ #if ! showReeval }
< button onclick = {() => showReeval = true }
class="text-sm text-green-600 hover:text-green-800 font-medium flex items-center gap-1.5">
< span > 🔄< / span > Neu bewerten lassen
< / button >
{ : else }
< h3 class = "text-sm font-semibold text-gray-900 mb-2" > KI-Neubewertung anstoßen< / h3 >
< textarea bind:value = { reevalAnmerkung } placeholder="Anmerkungen für die KI ( optional )"
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm mb-3 h-20 resize-y focus:ring-2 focus:ring-green-500"
disabled={ reevalStatus === 'running' } ></ textarea >
< div class = "flex gap-2 items-center" >
< button onclick = { triggerReeval } disabled= { reevalStatus === 'running' }
class="bg-green-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-green-700 disabled:opacity-50 transition-colors">
{ #if reevalStatus === 'running' }
< span class = "inline-flex items-center gap-2" >
< span class = "animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full" > < / span >
KI bewertet…
< / span >
{ : else }
Bewertung starten
{ /if }
< / button >
{ #if reevalStatus !== 'running' }
< button onclick = {() => { showReeval = false ; reevalStatus = 'idle' ; }}
class="text-sm text-gray-500 hover:text-gray-700">Abbrechen< / button >
{ /if }
< / div >
{ #if reevalStatus === 'done' }
< p class = "mt-2 text-sm text-green-700 font-medium" > ✅ Bewertung aktualisiert!< / p >
{ /if }
{ #if reevalStatus === 'error' }
< p class = "mt-2 text-sm text-red-600" > ❌ { reevalError } </ p >
{ /if }
{ /if }
< / div >
2026-04-01 18:38:49 +02:00
<!-- Volltext -->
{ #if selectedVorlage . volltext_clean }
< div class = "rounded-xl border border-gray-200 p-5" >
< div class = "flex items-center justify-between mb-2" >
< h3 class = "text-sm font-semibold text-gray-900" > Volltext< / h3 >
< button onclick = {() => showVolltext = ! showVolltext } class="text-xs text-green-600 hover:underline " >
{ showVolltext ? 'Einklappen' : 'Aufklappen' }
< / button >
< / div >
{ #if showVolltext }
< div class = "prose prose-sm max-w-none text-gray-700 whitespace-pre-wrap text-xs" > { selectedVorlage . volltext_clean } </ div >
{ : else }
< p class = "text-xs text-gray-500 line-clamp-4" > { selectedVorlage . volltext_clean } </ p >
{ /if }
< / div >
{ /if }
<!-- Beratungen -->
{ #if selectedVorlage . beratungen ? . length > 0 }
< div class = "rounded-xl border border-gray-200 p-5" >
< h3 class = "text-sm font-semibold text-gray-900 mb-3" > Beratungsfolge< / h3 >
< div class = "space-y-2" >
{ #each selectedVorlage . beratungen as b }
< div class = "flex flex-col sm:flex-row sm:items-start sm:justify-between p-2.5 rounded-lg border border-gray-100 gap-1.5" >
< div >
{ #if b . gremium }
< span class = "text-sm font-medium text-gray-900" > { b . gremium . name } </ span >
{ /if }
{ #if b . rolle }
< span class = "text-xs ml-1.5 text-gray-500" > ({ b . rolle } )</ span >
{ /if }
{ #if b . ergebnis }
< div class = "mt-0.5" >
< span class = "text-xs px-2 py-0 . 5 rounded
{ b . ergebnis . includes ( 'angenommen' ) || b . ergebnis . includes ( 'empfohlen' ) ? 'bg-green-100 text-green-700' :
b.ergebnis.includes('abgelehnt') ? 'bg-red-100 text-red-700' :
b.ergebnis.includes('vertagt') ? 'bg-amber-100 text-amber-700' :
'bg-gray-100 text-gray-700'}">
{ b . ergebnis }
< / span >
< / div >
{ /if }
< / div >
< span class = "text-xs text-gray-500 shrink-0" > { formatDate ( b . sitzung_datum )} </ span >
< / div >
{ /each }
< / div >
< / div >
{ /if }
<!-- Referenzen -->
{ #if selectedVorlage . referenzen_ausgehend ? . length > 0 || selectedVorlage . referenzen_eingehend ? . length > 0 }
< div class = "rounded-xl border border-gray-200 p-5" >
< h3 class = "text-sm font-semibold text-gray-900 mb-3" > Referenzen< / h3 >
{ #if selectedVorlage . referenzen_ausgehend ? . length > 0 }
< div class = "mb-3" >
< span class = "text-xs font-medium text-gray-500 uppercase" > Verweist auf< / span >
< div class = "space-y-1 mt-1" >
{ #each selectedVorlage . referenzen_ausgehend as ref }
< a href = "/vorlagen/ { ref . vorlage_id } " class = "block p-2 rounded border border-gray-100 hover:bg-gray-50 text-xs" >
< span class = "font-mono font-medium text-green-700" > { ref . aktenzeichen || `#$ { ref . vorlage_id } ` } </ span >
{ #if ref . betreff }
< span class = "text-gray-600 ml-1 truncate" > { ref . betreff } </ span >
{ /if }
< / a >
{ /each }
< / div >
< / div >
{ /if }
{ #if selectedVorlage . referenzen_eingehend ? . length > 0 }
< div >
< span class = "text-xs font-medium text-gray-500 uppercase" > Referenziert von< / span >
< div class = "space-y-1 mt-1" >
{ #each selectedVorlage . referenzen_eingehend as ref }
< a href = "/vorlagen/ { ref . vorlage_id } " class = "block p-2 rounded border border-gray-100 hover:bg-gray-50 text-xs" >
< span class = "font-mono font-medium text-green-700" > { ref . aktenzeichen || `#$ { ref . vorlage_id } ` } </ span >
{ #if ref . betreff }
< span class = "text-gray-600 ml-1 truncate" > { ref . betreff } </ span >
{ /if }
< / a >
{ /each }
< / div >
< / div >
{ /if }
< / div >
{ /if }
< / div >
{ : else }
< div class = "flex items-center justify-center h-full text-sm text-gray-400 p-4 text-center" >
← Vorlage auswählen
< / div >
{ /if }
< / div >
< / div >