Files
FPT/src/components/TransitionForm.tsx
T
Damian Kellenberger 1e8f1772d9
Deploy App / deploy (push) Failing after 1m1s
First Commit
2026-07-08 00:13:32 +02:00

240 lines
7.3 KiB
TypeScript

"use client";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { InfoBubble, TAX_INFO } from "./InfoBubble";
type TransitionFormProps = {
fromPhaseId: string;
toPhaseId: string;
planId: string;
scenarioId: string;
securities: Array<{ id: string; name: string }>;
properties: Array<{ id: string; name: string }>;
initial?: {
securityActions: Array<{
securityId: string;
action: "carry_over" | "payout";
payoutAmount?: number;
}>;
propertyActions: Array<{
propertyId: string;
action: "carry_over" | "sell";
saleProceeds?: number;
saleTaxRatePercent?: number;
}>;
};
};
export function TransitionForm({
fromPhaseId,
toPhaseId,
planId,
scenarioId,
securities,
properties,
initial,
}: TransitionFormProps) {
const router = useRouter();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [securityActions, setSecurityActions] = useState(
securities.map((s) => {
const existing = initial?.securityActions.find(
(a) => a.securityId === s.id
);
return {
securityId: s.id,
name: s.name,
action: existing?.action ?? ("carry_over" as const),
payoutAmount: existing?.payoutAmount ?? 0,
};
})
);
const [propertyActions, setPropertyActions] = useState(
properties.map((p) => {
const existing = initial?.propertyActions.find(
(a) => a.propertyId === p.id
);
return {
propertyId: p.id,
name: p.name,
action: existing?.action ?? ("carry_over" as const),
saleProceeds: existing?.saleProceeds ?? 0,
saleTaxRatePercent: existing?.saleTaxRatePercent ?? 15,
};
})
);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setError(null);
const res = await fetch("/api/transitions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
fromPhaseId,
toPhaseId,
securityActions: securityActions.map(
({ securityId, action, payoutAmount }) => ({
securityId,
action,
payoutAmount: action === "payout" ? payoutAmount : undefined,
})
),
propertyActions: propertyActions.map(
({ propertyId, action, saleProceeds, saleTaxRatePercent }) => ({
propertyId,
action,
saleProceeds: action === "sell" ? saleProceeds : undefined,
saleTaxRatePercent:
action === "sell" ? saleTaxRatePercent : undefined,
})
),
}),
});
setLoading(false);
if (!res.ok) {
const data = await res.json().catch(() => ({}));
setError(data.error ?? "Speichern fehlgeschlagen");
return;
}
router.push(`/plans/${planId}/scenarios/${scenarioId}`);
router.refresh();
}
return (
<form onSubmit={handleSubmit} className="card space-y-6">
<div>
<h2 className="text-lg font-semibold">Phasenübergang</h2>
<p className="text-sm text-slate-600">
Legen Sie fest, welche Wertschriften übernommen oder ausbezahlt werden
und ob Immobilien verkauft werden.
</p>
</div>
{error && (
<p className="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700">
{error}
</p>
)}
{securities.length > 0 && (
<div className="space-y-3">
<h3 className="font-medium">Wertschriften</h3>
{securityActions.map((item, idx) => (
<div
key={item.securityId}
className="grid gap-3 rounded-lg border border-slate-100 p-3 md:grid-cols-3"
>
<span className="self-center text-sm font-medium">{item.name}</span>
<select
value={item.action}
onChange={(e) => {
const next = [...securityActions];
next[idx] = {
...next[idx],
action: e.target.value as "carry_over" | "payout",
};
setSecurityActions(next);
}}
>
<option value="carry_over">Übernehmen</option>
<option value="payout">Auszahlen</option>
</select>
{item.action === "payout" && (
<input
type="number"
placeholder="Auszahlungsbetrag"
value={item.payoutAmount}
onChange={(e) => {
const next = [...securityActions];
next[idx] = {
...next[idx],
payoutAmount: Number(e.target.value),
};
setSecurityActions(next);
}}
/>
)}
</div>
))}
</div>
)}
{properties.length > 0 && (
<div className="space-y-3">
<h3 className="font-medium">
Immobilien
<InfoBubble title="Verkauf">{TAX_INFO.propertySale}</InfoBubble>
</h3>
{propertyActions.map((item, idx) => (
<div
key={item.propertyId}
className="grid gap-3 rounded-lg border border-slate-100 p-3 md:grid-cols-4"
>
<span className="self-center text-sm font-medium">{item.name}</span>
<select
value={item.action}
onChange={(e) => {
const next = [...propertyActions];
next[idx] = {
...next[idx],
action: e.target.value as "carry_over" | "sell",
};
setPropertyActions(next);
}}
>
<option value="carry_over">Übernehmen</option>
<option value="sell">Verkaufen</option>
</select>
{item.action === "sell" && (
<>
<input
type="number"
placeholder="Verkaufserlös"
value={item.saleProceeds}
onChange={(e) => {
const next = [...propertyActions];
next[idx] = {
...next[idx],
saleProceeds: Number(e.target.value),
};
setPropertyActions(next);
}}
/>
<input
type="number"
step="0.1"
placeholder="Steuer %"
value={item.saleTaxRatePercent}
onChange={(e) => {
const next = [...propertyActions];
next[idx] = {
...next[idx],
saleTaxRatePercent: Number(e.target.value),
};
setPropertyActions(next);
}}
/>
</>
)}
</div>
))}
</div>
)}
<button type="submit" className="btn-primary" disabled={loading}>
{loading ? "Speichern…" : "Übergang speichern"}
</button>
</form>
);
}