Files
FPT/src/components/SaveAnalysisButton.tsx
T
admGitAICDS fb70781e5b
Deploy App / deploy (push) Successful in 1m45s
Navigation auf Plan-Ebene und gespeicherte Analysen (Roadmap-Redesign)
Sidebar zweistufig: pro Plan die Unterpunkte Szenarien / Effektive Werte /
Analysen; Klick auf Plan-Name oeffnet ein Plan-Dashboard.

Plan-Dashboard: Kennzahlen, gerechnete Werte ausdruecklich "laut
Basisszenario", Ist-Abweichung falls erfasst.

Szenario-Liste: Version, Elementzahl, Endvermoegen, Ruinalter + Aktionen
Historie und Matrix. Baum in der Sidebar bleibt.

Analysen: vier umklappende Kacheln (auch per Antippen). Grafiken oeffnen
neu mit Auswahl EINER Grafik. Szenario-Vergleich zu den Grafiken,
CSV-Export auf die Matrix.

Gespeicherte Analysen: Grafik/MC/Einflussfaktoren als ZAHLEN einfrieren
(read-only, nichts wird neu gerechnet) -- druckfaehig fuer den spaeteren
PDF-Bericht, ohne finalWealthSorted. Einheitliche generische Ergebnisform.

Neue Tabelle SavedAnalysis, Endpunkte /analyses und /dashboard, Module
analyses.ts, Komponenten PlanViews/SavedAnalysisView/SaveAnalysisButton.
Kein Eingriff in den Rechenkern. Spezifikation 0.24 (3.10 und 9.30 neu).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 22:21:02 +02:00

94 lines
3.0 KiB
TypeScript

"use client";
import { useState } from "react";
import { Bookmark, Check } from "lucide-react";
import { useToast } from "@/components/ui";
import { defaultAnalysisName, saveAnalysis, type AnalysisType, type SaveAnalysisPayload } from "@/lib/analyses";
// Einheitlicher Speichern-Knopf für die Analysewerkzeuge. `build` wird erst beim Klick
// aufgerufen -- so wird das (evtl. grosse) Ergebnis nicht bei jedem Render zusammengebaut,
// sondern nur, wenn wirklich gespeichert wird.
export function SaveAnalysisButton({
planId,
type,
scenarioName,
versionLabel,
disabled,
build,
}: {
planId: string;
type: AnalysisType;
scenarioName: string | null;
versionLabel: string | null;
disabled?: boolean;
build: () => Omit<SaveAnalysisPayload, "name" | "type"> | null;
}) {
const [open, setOpen] = useState(false);
const [name, setName] = useState("");
const [busy, setBusy] = useState(false);
const [done, setDone] = useState(false);
const toast = useToast();
function start() {
setName(defaultAnalysisName(type, scenarioName, versionLabel));
setOpen(true);
}
async function save() {
const payload = build();
if (!payload) {
toast("error", "Es gibt noch kein Ergebnis zum Speichern.");
return;
}
setBusy(true);
try {
await saveAnalysis(planId, { ...payload, name: name.trim() || defaultAnalysisName(type, scenarioName, versionLabel), type });
setOpen(false);
setDone(true);
toast("success", "Analyse gespeichert.");
setTimeout(() => setDone(false), 2000);
} catch (e) {
toast("error", e instanceof Error ? e.message : "Speichern fehlgeschlagen.");
} finally {
setBusy(false);
}
}
if (open) {
return (
<div className="flex flex-wrap items-center gap-2 rounded-lg border border-accent bg-accent-soft/20 p-2">
<input
autoFocus
value={name}
onChange={(e) => setName(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && save()}
className="min-w-0 flex-1 rounded border border-border bg-surface px-2 py-1 text-sm text-fg"
/>
<button
type="button"
disabled={busy}
onClick={save}
className="rounded-lg bg-accent px-3 py-1 text-xs font-medium text-accent-fg hover:bg-accent-hover disabled:opacity-50"
>
{busy ? "Speichern…" : "Speichern"}
</button>
<button type="button" onClick={() => setOpen(false)} className="rounded-lg border border-border px-2 py-1 text-xs text-muted hover:bg-surface-2">
Abbrechen
</button>
</div>
);
}
return (
<button
type="button"
disabled={disabled}
onClick={start}
className="flex items-center gap-1.5 rounded-lg border border-border px-3 py-1.5 text-xs font-medium text-muted transition-colors hover:bg-surface-2 hover:text-fg disabled:opacity-40"
>
{done ? <Check className="h-3.5 w-3.5 text-success" /> : <Bookmark className="h-3.5 w-3.5" />}
{done ? "Gespeichert" : "Analyse speichern"}
</button>
);
}