Modul-Review 4: Matrix -- Kapitalverwendung, Phasendauer, Bedienung
Deploy App / deploy (push) Successful in 1m10s
Deploy App / deploy (push) Successful in 1m10s
Kapitalverwendung (Punkt C) am richtigen Ort: - Prozent-Aufteilung des bezogenen Alterskapitals wandert vom Cash-Uebergang zum Bezugs-Entscheid der PK (nur bei Kapitalbezug) bzw. der Saeule 3a -- mit zwei Guthaben liess sie sich vorher gar nicht getrennt beantworten - Dialoge fuehren neu brutto -> Steuersatz -> netto -> Verteilung - BUGFIX: Der zugeteilte Betrag erhoehte still den internen Bestand, deshalb zeigten Ziel-Element und "Kapital verteilen" eine 0. Er laeuft jetzt ueber Carry.capitalIn als Zusatzeinlage der Folgephase und ist ueberall sichtbar - Saeule 3a ist am Pensions-Uebergang neu ein offener Entscheid Phasendauer (gemeldeter Fehler): - Die Folgephase gleicht eine geaenderte Dauer aus; Gesamtdauer bleibt gleich - Vorher kappte das Tool nur die bearbeitete Phase -> Phase 2 ueberspannte danach die Pensionierung und die Invariante aus Punkt 44 kippte - Rueckfrage vorher, Blockade wenn die Folgephase unter 1 Jahr fiele - neue reine Funktion planDurationChange Bedienung: - Element-Zeile und Phasenkopf: Stift (umbenennen, beim Element inkl. Zuordnung), Papierkorb, Expand -- alle immer sichtbar - PATCH /api/elements/<id> nimmt neu auch ownerRole - Hilfetexte via Portal (wurden in scrollenden Dialogen abgeschnitten) - Verteil-Dialoge: Zuordnung je Zeile, nach vom/ins Cash gruppiert, Vorbelegung mit dem EFFEKTIVEN Wert inkl. Vererbung (zeigte vorher 0) - Matrix: gleiche Spaltenbreiten + horizontales Scrollen, "Alle auf-/ zuklappen", Kategorie-Summe in der zugeklappten Zeile - Phasen-Detailansicht nutzt die neue Aufteilungs-Grafik - Uebersicht: "Leer starten" auch im leeren Zustand Nebenbei: dritte verstuemmelte Hex-Farbe (#7c3aed) repariert, Phasen-Panel nutzt den eigenen Bestaetigungs-Dialog statt window.confirm; mehrere veraltete Referenzen und die buildCarryData-Tabelle in der Spez nachgezogen. SPEZIFIKATION 0.33. 278 -> 288 Tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,11 +4,14 @@ import { useState } from "react";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { NumberField, TextField } from "@/components/FormField";
|
||||
import { CarryWarning } from "@/components/ElementDetail";
|
||||
import { Button, useConfirm, useToast } from "@/components/ui";
|
||||
import { api } from "@/lib/api-client";
|
||||
import { planDurationChange } from "@/lib/phaseplan";
|
||||
import type { PhaseInput } from "@/lib/types";
|
||||
|
||||
export function PhaseDetail({
|
||||
phase,
|
||||
allPhases,
|
||||
maxDurationYears,
|
||||
isLast,
|
||||
laterPhaseCount,
|
||||
@@ -16,6 +19,9 @@ export function PhaseDetail({
|
||||
onDeleted,
|
||||
}: {
|
||||
phase: PhaseInput;
|
||||
// Alle Phasen des Szenarios -- nötig, um die Auswirkung einer geänderten Dauer auf die
|
||||
// Folgephase VORHER zu zeigen (siehe planDurationChange).
|
||||
allPhases: PhaseInput[];
|
||||
maxDurationYears: number | null;
|
||||
isLast: boolean;
|
||||
laterPhaseCount: number;
|
||||
@@ -26,10 +32,32 @@ export function PhaseDetail({
|
||||
const [durationYears, setDurationYears] = useState(phase.durationYears);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const confirm = useConfirm();
|
||||
const toast = useToast();
|
||||
|
||||
const cap = maxDurationYears;
|
||||
// Vorschau: Was passiert mit der Folgephase? Wird live berechnet, damit die Auswirkung
|
||||
// sichtbar ist, BEVOR gespeichert wird.
|
||||
const change = planDurationChange(allPhases, phase.id, durationYears);
|
||||
|
||||
async function save() {
|
||||
// Verschiebt sich eine Folgephase, wird das vorher bestätigt -- sonst ändert ein Klick
|
||||
// stillschweigend zwei Phasen.
|
||||
if (change.ok && change.neighbourId && change.delta !== 0) {
|
||||
const jahre = Math.abs(change.delta);
|
||||
const ok = await confirm({
|
||||
title: "Folgephase wird angepasst",
|
||||
message:
|
||||
`Wenn du «${phase.name}» um ${jahre} Jahr${jahre === 1 ? "" : "e"} ` +
|
||||
`${change.delta > 0 ? "verlängerst" : "verkürzst"}, wird «${change.neighbourName}» automatisch auf ` +
|
||||
`${change.neighbourNewDuration} Jahr${change.neighbourNewDuration === 1 ? "" : "e"} ` +
|
||||
`${change.delta > 0 ? "gekürzt" : "verlängert"} – die Gesamtdauer des Plans bleibt gleich. ` +
|
||||
`Ohne diesen Ausgleich würde eine spätere Lebensphase eine Pensionierung überspannen.`,
|
||||
confirmLabel: "Fortfahren",
|
||||
});
|
||||
if (!ok) return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
try {
|
||||
@@ -43,21 +71,25 @@ export function PhaseDetail({
|
||||
}
|
||||
|
||||
async function remove() {
|
||||
if (!confirm(`Phase "${phase.name}" wirklich löschen?`)) return;
|
||||
const ok = await confirm({
|
||||
title: "Lebensphase löschen?",
|
||||
message: `«${phase.name}» wird mit allen erfassten Werten und Übergangs-Entscheiden entfernt.`,
|
||||
confirmLabel: "Endgültig löschen",
|
||||
danger: true,
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
await api.delete(`/api/phases/${phase.id}`);
|
||||
onDeleted();
|
||||
} catch (e) {
|
||||
alert(e instanceof Error ? e.message : "Löschen fehlgeschlagen.");
|
||||
toast("error", e instanceof Error ? e.message : "Löschen fehlgeschlagen.");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-accent-soft-fg">
|
||||
Lebensphase
|
||||
</div>
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-accent-soft-fg">Lebensphase</div>
|
||||
{isLast && (
|
||||
<button
|
||||
type="button"
|
||||
@@ -82,18 +114,27 @@ export function PhaseDetail({
|
||||
onChange={(v) => setDurationYears(cap != null ? Math.min(v, cap) : v)}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-faint">Die Inflationsrate gilt plan-weit und wird in den Plan-Einstellungen gesetzt.</p>
|
||||
|
||||
{/* Auswirkung auf die Folgephase -- sichtbar, bevor gespeichert wird. */}
|
||||
{change.error && (
|
||||
<p className="rounded-lg border border-danger bg-danger-soft px-3 py-2 text-xs text-danger">{change.error}</p>
|
||||
)}
|
||||
{change.ok && change.neighbourId && change.delta !== 0 && (
|
||||
<p className="rounded-lg border border-attention bg-attention-soft px-3 py-2 text-xs text-attention-soft-fg">
|
||||
«{change.neighbourName}» wird dadurch auf <strong>{change.neighbourNewDuration} Jahre</strong>{" "}
|
||||
{change.delta > 0 ? "gekürzt" : "verlängert"} – die Gesamtdauer des Plans bleibt gleich.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<p className="text-xs text-faint">
|
||||
Die Inflationsrate gilt für das ganze Szenario und wird im Grundprofil gesetzt.
|
||||
</p>
|
||||
|
||||
{error && <p className="text-sm text-danger">{error}</p>}
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={saving}
|
||||
onClick={save}
|
||||
className="rounded-lg bg-accent px-4 py-2 text-sm font-medium text-accent-fg shadow-sm hover:bg-accent-hover disabled:opacity-50"
|
||||
>
|
||||
{saving ? "Speichern..." : "Speichern"}
|
||||
</button>
|
||||
<Button disabled={saving || !!change.error} onClick={save}>
|
||||
{saving ? "Speichern…" : "Speichern"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user