Detailansichten, Wasserfaelle und vollstaendige Rechenweg-Offenlegung
Deploy App / deploy (push) Successful in 57s
Deploy App / deploy (push) Successful in 57s
Roadmap Nr. 43 (Detailansichten) und Nr. 41 (Berechnungslogiken offenlegen).
Systemparameter-Ansicht:
- SYSTEM_PARAMETERS in constants.ts: Wert, Bedeutung, Herleitung, Quelle,
Stand -- aus derselben Datei, aus der gerechnet wird
Detailansichten (nur lesen, per Expand-Icon):
- Element: Verlauf ueber ALLE Planjahre (neu ElementPhaseComputed.yearly),
bei Immobilien Verkehrswert/Restschuld/Eigenkapital getrennt
- Phase: Vermoegensaufteilung + zwei Wasserfaelle
Zwei getrennte Wasserfaelle (WealthBridge / CashBridge):
- Sparraten, Amortisationen und Investitionen sind UMBUCHUNGEN und erscheinen
nur im Cash-Wasserfall -- als Vermoegensabgang gezeichnet wuerden sie einen
Verlust vortaeuschen, den es nicht gibt
- PK-Beitraege dagegen sind ein echter Vermoegenszugang (belasten kein Cash),
Verrentung ein echter Abgang (Kapital verlaesst die Bilanz)
- residual als Kontrollgroesse fuer die Vollstaendigkeit der Zerlegung
Rechenweg-Protokoll, vollstaendige Abdeckung:
- computePlan(plan, sample?, { explain }) protokolliert die Schritte, die es
ohnehin ausfuehrt -- die Erklaerung IST die Rechnung, statt einer zweiten
Formel-Implementierung im UI, die still abdriften koennte
- standardmaessig aus (Monte Carlo bleibt unberuehrt)
- Arithmetik nicht umgestellt: Zwischengroessen werden als Differenz
abgeleitet, damit die 43 Golden Tests bitgleich bleiben
- jeder Trace verlinkt in die SPEZIFIKATION; ein Test prueft gegen die echte
Datei, dass alle Verweise eine existierende Ueberschrift treffen
SPEZIFIKATION auf 0.11: neue Kapitel 3.6.7, 3.6.8, 4.14, 9.20, 9.21.
12 Tests ergaenzt (80 -> 92). Keine DB-Aenderung.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
BarChart3,
|
||||
BookOpen,
|
||||
Copy,
|
||||
Dices,
|
||||
FileText,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
Menu,
|
||||
PiggyBank,
|
||||
Plus,
|
||||
SlidersHorizontal,
|
||||
Tornado,
|
||||
Trash2,
|
||||
X,
|
||||
@@ -21,6 +23,9 @@ import { Dashboard } from "@/components/Dashboard";
|
||||
import { MonteCarloDialog } from "@/components/MonteCarloDialog";
|
||||
import { SensitivityDialog } from "@/components/SensitivityDialog";
|
||||
import { SpecView } from "@/components/SpecView";
|
||||
import { SystemParametersView } from "@/components/SystemParametersView";
|
||||
import { PlanTraceDialog } from "@/components/DetailView";
|
||||
import { computePlan } from "@/lib/calculations";
|
||||
import { ProfileMenu } from "@/components/ProfileMenu";
|
||||
import { PlanProfileFields, emptyProfileDraft, type ProfileDraft } from "@/components/PlanProfileFields";
|
||||
import { api } from "@/lib/api-client";
|
||||
@@ -47,6 +52,17 @@ export function AppShell({ username }: { username: string }) {
|
||||
const [showCharts, setShowCharts] = useState(false);
|
||||
const [showMonteCarlo, setShowMonteCarlo] = useState(false);
|
||||
const [showSensitivity, setShowSensitivity] = useState(false);
|
||||
const [showSystemParams, setShowSystemParams] = useState(false);
|
||||
const [showPlanTraces, setShowPlanTraces] = useState(false);
|
||||
// Sprungmarke in die SPEZIFIKATION, gesetzt aus einem Rechenweg heraus.
|
||||
const [specAnchor, setSpecAnchor] = useState<string | null>(null);
|
||||
|
||||
function openSpecAt(anchor: string) {
|
||||
setSpecAnchor(anchor);
|
||||
setShowSpec(true);
|
||||
setShowSystemParams(false);
|
||||
setSelectedScenarioId(null);
|
||||
}
|
||||
|
||||
const loadPlans = useCallback(async () => {
|
||||
const data = await api.get<{ plans: PlanListItem[] }>("/api/plans");
|
||||
@@ -86,6 +102,7 @@ export function AppShell({ username }: { username: string }) {
|
||||
function openScenario(id: string) {
|
||||
setSelectedScenarioId(id);
|
||||
setShowSpec(false);
|
||||
setShowSystemParams(false);
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
|
||||
@@ -127,10 +144,11 @@ export function AppShell({ username }: { username: string }) {
|
||||
onClick={() => {
|
||||
setSelectedScenarioId(null);
|
||||
setShowSpec(false);
|
||||
setShowSystemParams(false);
|
||||
setSidebarOpen(false);
|
||||
}}
|
||||
className={`flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium ${
|
||||
selectedScenarioId === null && !showSpec ? "bg-accent-soft text-accent-soft-fg" : "text-muted hover:bg-surface-2"
|
||||
selectedScenarioId === null && !showSpec && !showSystemParams ? "bg-accent-soft text-accent-soft-fg" : "text-muted hover:bg-surface-2"
|
||||
}`}
|
||||
>
|
||||
<LayoutDashboard className="h-4 w-4" />
|
||||
@@ -176,11 +194,28 @@ export function AppShell({ username }: { username: string }) {
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="mt-4 border-t border-border pt-3">
|
||||
<div className="mt-4 flex flex-col gap-1 border-t border-border pt-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowSystemParams(true);
|
||||
setShowSpec(false);
|
||||
setSelectedScenarioId(null);
|
||||
setSidebarOpen(false);
|
||||
}}
|
||||
className={`flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm font-medium ${
|
||||
showSystemParams ? "bg-accent-soft text-accent-soft-fg" : "text-muted hover:bg-surface-2"
|
||||
}`}
|
||||
>
|
||||
<SlidersHorizontal className="h-4 w-4 shrink-0" />
|
||||
Systemparameter
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowSpec(true);
|
||||
setSpecAnchor(null);
|
||||
setShowSystemParams(false);
|
||||
setSelectedScenarioId(null);
|
||||
setSidebarOpen(false);
|
||||
}}
|
||||
@@ -230,7 +265,9 @@ export function AppShell({ username }: { username: string }) {
|
||||
<Menu className="h-4 w-4" />
|
||||
</button>
|
||||
<h1 className="min-w-0 flex-1 truncate text-base font-semibold text-fg">
|
||||
{showSpec
|
||||
{showSystemParams
|
||||
? "Systemparameter"
|
||||
: showSpec
|
||||
? "Spezifikation"
|
||||
: detail
|
||||
? `${detail.meta.planName} · ${detail.meta.name}`
|
||||
@@ -240,11 +277,13 @@ export function AppShell({ username }: { username: string }) {
|
||||
</header>
|
||||
|
||||
<main className="flex-1 px-4 py-6 lg:px-8">
|
||||
{showSpec && <SpecView />}
|
||||
{showSystemParams && <SystemParametersView />}
|
||||
|
||||
{!showSpec && loading && <p className="text-sm text-muted">Laedt…</p>}
|
||||
{showSpec && <SpecView anchor={specAnchor} />}
|
||||
|
||||
{!showSpec && !loading && selectedScenarioId === null && (
|
||||
{!showSpec && !showSystemParams && loading && <p className="text-sm text-muted">Laedt…</p>}
|
||||
|
||||
{!showSpec && !showSystemParams && !loading && selectedScenarioId === null && (
|
||||
<DashboardHome
|
||||
username={username}
|
||||
plans={plans}
|
||||
@@ -254,7 +293,7 @@ export function AppShell({ username }: { username: string }) {
|
||||
/>
|
||||
)}
|
||||
|
||||
{!showSpec && !loading && detail && selectedScenarioId && (
|
||||
{!showSpec && !showSystemParams && !loading && detail && selectedScenarioId && (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
@@ -301,6 +340,15 @@ export function AppShell({ username }: { username: string }) {
|
||||
<Tornado className="h-4 w-4" />
|
||||
Einflussfaktoren berechnen
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPlanTraces(true)}
|
||||
title="Wie wird gerechnet? Plan-weite Grössen wie Deflatoren, AHV-Karriere und Ruinalter"
|
||||
className="flex items-center gap-1.5 rounded-lg border border-border px-3 py-1.5 text-sm font-medium text-muted hover:bg-surface-2"
|
||||
>
|
||||
<BookOpen className="h-4 w-4" />
|
||||
Rechenwege
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{diff && detail.base && (
|
||||
@@ -313,7 +361,13 @@ export function AppShell({ username }: { username: string }) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<PlanView plan={detail.plan} computed={detail.computed} diff={diff} onChanged={refreshCurrent} />
|
||||
<PlanView
|
||||
plan={detail.plan}
|
||||
computed={detail.computed}
|
||||
diff={diff}
|
||||
onChanged={refreshCurrent}
|
||||
onOpenSpec={openSpecAt}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
@@ -358,6 +412,14 @@ export function AppShell({ username }: { username: string }) {
|
||||
<SensitivityDialog plan={detail.plan} onClose={() => setShowSensitivity(false)} />
|
||||
)}
|
||||
|
||||
{showPlanTraces && detail && (
|
||||
<PlanTraceDialog
|
||||
computed={computePlan(detail.plan, undefined, { explain: true })}
|
||||
onClose={() => setShowPlanTraces(false)}
|
||||
onOpenSpec={openSpecAt}
|
||||
/>
|
||||
)}
|
||||
|
||||
{copyFrom && (
|
||||
<CopyScenarioDialog
|
||||
source={copyFrom}
|
||||
|
||||
Reference in New Issue
Block a user