c2fb82b0de
Deploy App / deploy (push) Successful in 1m1s
Rein an der Oberflaeche -- Berechnung, Datenmodell und API-Semantik unveraendert (103 Tests unveraendert gruen). Paket A (Fundament): - durchgehend Du-Form und echte Umlaute in allen sichtbaren Texten, inkl. API-Fehlermeldungen (vorher Mix aus Sie/Du und ae/oe/ue) - neue UI-Primitiven (ui.tsx): Button, Modal mit ESC/Fokus-Falle/Animation, Bestaetigungs-Dialog statt window.confirm, Toasts statt alert, Skeleton-Loader, EmptyState - eigene Attention-Farbe (Amber) fuer offene Entscheide, getrennt vom Akzent - Micro-Interactions mit prefers-reduced-motion-Fallback Paket B (Onboarding, Roadmap Nr. 10): - gefuehrter Plan-Assistent in 5 Schritten; Einkommen bewusst pro Person (raeumt die 9.9-AHV-Falle aus); reine Orchestrierung bestehender Endpunkte - Beispielplan mit einem Klick; Uebergaenge absichtlich offen - interaktive Tour ueber die Planansicht (localStorage, jederzeit neu startbar) - abgeleitete "Naechste Schritte"-Karte (offene Entscheide, fehlende Elemente, fehlende Pensionsphase, Ruin -> Einflussfaktoren) Paket C (Struktur): - Inspector-Panel rechts statt Modals fuer alle Einzel-Bearbeitungen; Matrix bleibt sichtbar, Zellklick wechselt den Inhalt - Phasenkopf auf vier Kern-Infos entschlackt (Rest in der 0.11-Detailansicht) - Matrix mit eigenem Scrollbereich, Koepfe beidachsig fixiert - Sidebar-Gruppen "Meine Plaene" / "Wissen"; "So rechnet FPT" statt SPEZIFIKATION; "Szenario-Profil" statt "Plan-Einstellungen" - Aktions-Icons ohne Hover sichtbar (Touch) Paket D (Extras): - Sparklines je Element-Zeile aus den 0.11-Verlaufswerten - Befehls-Palette (Ctrl/Cmd+K) - Ruin-Banner verlinkt auf die Einflussfaktoren Nebenbei: der ProfileMenu-Lint-Fehler und der Selection-Rest (9.17) sind behoben -- npm run lint laeuft erstmals fehlerfrei. SPEZIFIKATION auf 0.13: neue Kapitel 3.2.8, 3.7.6-3.7.9, 9.23, 9.24; 3.6.3 und 3.7.1 ueberarbeitet, 9.17 bereinigt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
93 lines
3.8 KiB
TypeScript
93 lines
3.8 KiB
TypeScript
"use client";
|
||
|
||
import { SlidersHorizontal } from "lucide-react";
|
||
import { formatChf } from "@/lib/format";
|
||
import { SYSTEM_PARAMETERS, type ParameterGroup, type SystemParameter } from "@/lib/constants";
|
||
|
||
const GROUPS: { key: ParameterGroup; title: string; intro: string }[] = [
|
||
{
|
||
key: "AHV",
|
||
title: "AHV",
|
||
intro:
|
||
"Diese Werte legt der Bund fest. Sie ändern sich periodisch – der Stand ist je Parameter ausgewiesen.",
|
||
},
|
||
{
|
||
key: "Vorsorge",
|
||
title: "Berufliche und gebundene Vorsorge",
|
||
intro: "Höchstbeträge und Umwandlung. Der Umwandlungssatz ist ein Vorschlag und pro Ereignis überschreibbar.",
|
||
},
|
||
{
|
||
key: "Steuern",
|
||
title: "Steuersätze",
|
||
intro:
|
||
"Bewusst pauschale Vorschlagswerte, keine Steuerberechnung: Die tatsächlichen Sätze sind kantonal, progressiv und vom Einzelfall abhängig. Sie lassen sich pro Ereignis überschreiben.",
|
||
},
|
||
];
|
||
|
||
function formatValue(p: SystemParameter): string {
|
||
switch (p.unit) {
|
||
case "CHF":
|
||
return `${formatChf(p.value)} CHF`;
|
||
case "%":
|
||
return `${p.value} %`;
|
||
case "Jahre":
|
||
return `${p.value} Jahre`;
|
||
default:
|
||
return String(p.value);
|
||
}
|
||
}
|
||
|
||
// Zeigt die Systemparameter mit Wert, Bedeutung, Herleitung und Quelle (Roadmap Nr. 41).
|
||
// Die Einträge stammen direkt aus constants.ts -- dieselbe Datenquelle, aus der auch
|
||
// gerechnet wird. Ein Abdriften zwischen Anzeige und Rechnung ist damit ausgeschlossen.
|
||
export function SystemParametersView() {
|
||
return (
|
||
<div className="flex flex-col gap-5">
|
||
<div className="flex items-center gap-2">
|
||
<SlidersHorizontal className="h-5 w-5 text-accent" />
|
||
<h2 className="text-lg font-semibold text-fg">Systemparameter</h2>
|
||
</div>
|
||
|
||
<p className="max-w-3xl text-sm text-muted">
|
||
Alle fest hinterlegten Grössen, mit denen das Tool rechnet – inklusive Herleitung und Quelle. Sie stammen aus
|
||
derselben Datei, aus der auch die Berechnung liest; was hier steht, ist also garantiert das, was gerechnet wird.
|
||
</p>
|
||
|
||
{GROUPS.map((g) => {
|
||
const rows = SYSTEM_PARAMETERS.filter((p) => p.group === g.key);
|
||
if (rows.length === 0) return null;
|
||
return (
|
||
<section key={g.key} className="rounded-xl border border-border bg-surface p-4 shadow-sm">
|
||
<h3 className="text-sm font-semibold text-fg">{g.title}</h3>
|
||
<p className="mb-3 mt-0.5 text-xs text-muted">{g.intro}</p>
|
||
<div className="flex flex-col gap-3">
|
||
{rows.map((p) => (
|
||
<div key={p.key} className="rounded-lg border border-border bg-surface-2 p-3">
|
||
<div className="flex flex-wrap items-baseline justify-between gap-2">
|
||
<span className="text-sm font-medium text-fg">{p.label}</span>
|
||
<span className="text-sm font-semibold text-accent">{formatValue(p)}</span>
|
||
</div>
|
||
<p className="mt-1 text-xs text-muted">{p.meaning}</p>
|
||
{p.derivation && (
|
||
<p className="mt-1 font-mono text-[11px] text-faint">Herleitung: {p.derivation}</p>
|
||
)}
|
||
<div className="mt-1.5 flex flex-wrap gap-x-4 gap-y-1 text-[11px] text-faint">
|
||
<span>Quelle: {p.source}</span>
|
||
<span>Stand: {p.validFrom}</span>
|
||
{p.editablePerEvent && <span className="text-accent">pro Ereignis überschreibbar</span>}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
);
|
||
})}
|
||
|
||
<p className="max-w-3xl text-xs text-faint">
|
||
Die drei Steuersätze werden sowohl als Vorschlag im Eingabefeld als auch in der Berechnung als Rückfallwert
|
||
verwendet – damit ein nicht angetippter Wert nicht fälschlich als 0 % gerechnet wird.
|
||
</p>
|
||
</div>
|
||
);
|
||
}
|