"use client"; import { useEffect, useState } from "react"; import { api } from "@/lib/api-client"; import type { PhaseInput, TransitionDecision } from "@/lib/types"; import type { PhaseComputed } from "@/lib/calculations"; interface ItemDraft { positionType: "SECURITY" | "REAL_ESTATE"; id: string; name: string; decision: TransitionDecision; salePrice: number | null; // Referenzwerte fuer die Anzeige/Berechnung des verfuegbaren Startkapitals carryOverValue: number; originalValue: number; saleTaxRate: number; } export function TransitionPanel({ phase, computed, nextPhaseName, onChanged, }: { phase: PhaseInput; computed: PhaseComputed; nextPhaseName: string; onChanged: () => void; }) { const [items, setItems] = useState([]); const [loaded, setLoaded] = useState(false); const [saving, setSaving] = useState(false); const [saved, setSaved] = useState(false); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; async function load() { const initial: ItemDraft[] = [ ...phase.securities.map((s) => { const c = computed.securities.find((cs) => cs.id === s.id); return { positionType: "SECURITY" as const, id: s.id, name: s.name, decision: "CARRY_OVER" as TransitionDecision, salePrice: null, carryOverValue: c?.endValue ?? 0, originalValue: c?.startValue ?? 0, saleTaxRate: s.saleTaxRate, }; }), ...phase.realEstates.map((re) => { const c = computed.realEstates.find((cr) => cr.id === re.id); return { positionType: "REAL_ESTATE" as const, id: re.id, name: re.name, decision: "CARRY_OVER" as TransitionDecision, salePrice: re.marketValue, carryOverValue: c?.endNetIfKept ?? 0, originalValue: re.marketValue, saleTaxRate: re.saleTaxRate, }; }), ]; try { const data = await api.get<{ transition: { items: { positionType: string; securityId: string | null; realEstateId: string | null; decision: TransitionDecision; salePrice: number | null }[] } | null }>( `/api/phases/${phase.id}/transition` ); if (cancelled) return; if (data.transition) { for (const savedItem of data.transition.items) { const target = initial.find( (it) => it.id === (savedItem.securityId ?? savedItem.realEstateId) ); if (target) { target.decision = savedItem.decision; if (savedItem.salePrice != null) target.salePrice = savedItem.salePrice; } } } setItems(initial); setLoaded(true); } catch { setItems(initial); setLoaded(true); } } load(); return () => { cancelled = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [phase.id]); if (!loaded) { return (
Uebergang wird geladen…
); } const totalAvailableCapital = items.reduce((sum, it) => { if (it.positionType === "SECURITY") { if (it.decision === "CARRY_OVER") return sum; const gain = Math.max(0, it.carryOverValue - it.originalValue); const tax = gain * (it.saleTaxRate / 100); return sum + (it.carryOverValue - tax); } if (it.decision === "CARRY_OVER") return sum; const salePrice = it.salePrice ?? 0; const gain = Math.max(0, salePrice - it.originalValue); const tax = gain * (it.saleTaxRate / 100); return sum + (salePrice - tax); }, 0); async function handleSave() { setSaving(true); setError(null); setSaved(false); try { await api.put(`/api/phases/${phase.id}/transition`, { items: items.map((it) => ({ positionType: it.positionType, securityId: it.positionType === "SECURITY" ? it.id : null, realEstateId: it.positionType === "REAL_ESTATE" ? it.id : null, decision: it.decision, salePrice: it.positionType === "REAL_ESTATE" && it.decision === "SELL" ? it.salePrice : null, })), }); setSaved(true); onChanged(); } catch (e) { setError(e instanceof Error ? e.message : "Speichern fehlgeschlagen."); } finally { setSaving(false); } } if (items.length === 0) { return null; } return (
Uebergang → {nextPhaseName}
{items.map((it, i) => ( ))}
Position Entscheidung Verkaufspreis / Wert
{it.name} {it.positionType === "REAL_ESTATE" && it.decision === "SELL" ? ( setItems((prev) => prev.map((x, idx) => (idx === i ? { ...x, salePrice: e.target.valueAsNumber || 0 } : x)) ) } /> ) : ( {it.decision === "CARRY_OVER" ? it.carryOverValue.toLocaleString("de-CH") : it.carryOverValue.toLocaleString("de-CH")} CHF )}
Verfuegbares Startkapital fuer neue Phase (aus Verkaeufen): {totalAvailableCapital.toLocaleString("de-CH")} CHF

Wird beim Speichern automatisch in "{nextPhaseName}" als verfuegbares Startkapital hinterlegt. Uebernommene Wertschriften erscheinen dort automatisch mit ihrem Endwert als neuer Startwert.

{error &&

{error}

}
{saved && Gespeichert.}
); }