"use client"; import { useCallback, useEffect, useState } from "react"; import { ArrowLeft, Download, FileText, Plus, Trash2 } from "lucide-react"; import { Button, useConfirm, useToast } from "@/components/ui"; import { InfoBubble } from "@/components/InfoBubble"; import { api } from "@/lib/api-client"; import { ANALYSIS_TYPE_LABEL, type AnalysisType, type SavedAnalysisMeta } from "@/lib/analyses"; import { MAX_REPORT_SCENARIOS, type ReportMetric, type ReportSource } from "@/lib/report"; interface StoredReport { id: string; title: string; config: { metric?: string; source?: string; scenarioIds?: string[]; analysisIds?: string[]; comment?: string | null }; pdfBytes: number; createdAt: string; author: string; } const dt = (iso: string) => new Date(iso).toLocaleString("de-CH", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" }); export function ReportsView({ planId, scenarios, hasActuals, }: { planId: string; scenarios: { id: string; name: string; isBase: boolean }[]; hasActuals: boolean; }) { const [reports, setReports] = useState(null); const [analyses, setAnalyses] = useState([]); const [error, setError] = useState(null); const [mode, setMode] = useState<"list" | "new">("list"); const [busy, setBusy] = useState(false); const confirm = useConfirm(); const toast = useToast(); // Formular const [title, setTitle] = useState(""); const [comment, setComment] = useState(""); const [metric, setMetric] = useState("nominal"); const [source, setSource] = useState("PLAN"); const [scenarioIds, setScenarioIds] = useState([]); const [analysisIds, setAnalysisIds] = useState([]); const load = useCallback(async () => { const [r, a] = await Promise.all([ api.get<{ reports: StoredReport[] }>(`/api/plans/${planId}/reports`), api.get<{ analyses: SavedAnalysisMeta[] }>(`/api/plans/${planId}/analyses`), ]); setReports(r.reports); setAnalyses(a.analyses); }, [planId]); useEffect(() => { let cancelled = false; (async () => { try { const [r, a] = await Promise.all([ api.get<{ reports: StoredReport[] }>(`/api/plans/${planId}/reports`), api.get<{ analyses: SavedAnalysisMeta[] }>(`/api/plans/${planId}/analyses`), ]); if (cancelled) return; setReports(r.reports); setAnalyses(a.analyses); } catch (e) { if (!cancelled) setError(e instanceof Error ? e.message : "Konnte nicht geladen werden."); } })(); return () => { cancelled = true; }; }, [planId]); function startNew() { const base = scenarios.find((s) => s.isBase) ?? scenarios[0]; setTitle(`Finanzplanung ${new Date().getFullYear()}`); setComment(""); setMetric("nominal"); setSource("PLAN"); setScenarioIds(base ? [base.id] : []); setAnalysisIds([]); setMode("new"); } function toggleScenario(id: string) { setScenarioIds((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : prev.length >= MAX_REPORT_SCENARIOS ? prev : [...prev, id] ); } async function create() { setBusy(true); try { await api.post(`/api/plans/${planId}/reports`, { title: title.trim() || "Finanzplanung", metric, source, scenarioIds, analysisIds, comment: comment.trim() || undefined, }); await load(); setMode("list"); toast("success", "Bericht erstellt."); } catch (e) { toast("error", e instanceof Error ? e.message : "Bericht konnte nicht erstellt werden."); } finally { setBusy(false); } } async function remove(r: StoredReport) { const ok = await confirm({ title: "Bericht löschen?", message: `«${r.title}» wird endgültig entfernt. Die Datei lässt sich danach nicht mehr herunterladen.`, confirmLabel: "Löschen", danger: true, }); if (!ok) return; try { await api.delete(`/api/plans/${planId}/reports/${r.id}`); await load(); toast("success", "Bericht gelöscht."); } catch (e) { toast("error", e instanceof Error ? e.message : "Löschen fehlgeschlagen."); } } if (mode === "new") { return (

Neuer Bericht

Der Bericht wird als PDF erzeugt und abgelegt. Er hält den Stand von heute fest – spätere Änderungen am Plan verändern ihn nicht mehr.

setTitle(e.target.value)} className="w-full rounded-lg border border-border bg-surface px-2.5 py-1.5 text-sm text-fg" />
setComment(e.target.value)} placeholder="z. B. «Stand nach Beratungsgespräch»" className="w-full rounded-lg border border-border bg-surface px-2.5 py-1.5 text-sm text-fg" />
Szenarien (max. {MAX_REPORT_SCENARIOS})
{scenarios.map((s) => { const checked = scenarioIds.includes(s.id); const full = !checked && scenarioIds.length >= MAX_REPORT_SCENARIOS; return ( ); })}
Gespeicherte Analysen einbinden
{analyses.length === 0 ? (

Noch keine Analysen gespeichert. Unter «Analysen» kannst du Ergebnisse festhalten.

) : (
{analyses.map((a) => ( ))}
)}
); } return (

Berichte

Ein Bericht ist ein festes Dokument: Die erzeugte PDF-Datei wird abgelegt und lässt sich jederzeit unverändert wieder herunterladen – auch wenn du den Plan danach weiterentwickelst.

{error &&

{error}

} {!reports && !error &&

Wird geladen…

} {reports && reports.length === 0 && (

Noch kein Bericht erstellt.

)} {reports && reports.length > 0 && (
{reports.map((r) => (
{r.title}
{dt(r.createdAt)} · {r.author} · {r.config.metric === "real" ? "real" : "nominal"} ·{" "} {r.config.source === "ACTUAL" ? "effektiv" : "Plan"} · {(r.config.scenarioIds ?? []).length} Szenario {(r.config.scenarioIds ?? []).length === 1 ? "" : "s"} · {Math.round(r.pdfBytes / 1024)} KB
PDF
))}
)}
); }