@@ -0,0 +1,239 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user