Personen koennen einen Namen bekommen (Plan-Einstellungen)
Deploy App / deploy (push) Successful in 1m52s
Deploy App / deploy (push) Successful in 1m52s
Person A/B erhalten ein optionales Namensfeld (Migration: Person.name nullable). Fallback im UI bleibt "Person A"/"Person B". Angezeigt in Grundprofil-Box, Zeitachse, Matrix-Elementzuordnung und Element-Erstelldialog; Szenario-Kopie uebernimmt den Namen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
-- Optionaler Anzeigename je Person (Fallback im UI: "Person A" / "Person B").
|
||||||
|
ALTER TABLE "Person" ADD COLUMN "name" TEXT;
|
||||||
@@ -60,6 +60,7 @@ model Person {
|
|||||||
planId String
|
planId String
|
||||||
plan Plan @relation(fields: [planId], references: [id], onDelete: Cascade)
|
plan Plan @relation(fields: [planId], references: [id], onDelete: Cascade)
|
||||||
role PersonRole
|
role PersonRole
|
||||||
|
name String?
|
||||||
age Int
|
age Int
|
||||||
retirementAge Int
|
retirementAge Int
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export async function POST(
|
|||||||
inflationRateDefault: source.inflationRateDefault,
|
inflationRateDefault: source.inflationRateDefault,
|
||||||
parentPlanId: source.id,
|
parentPlanId: source.id,
|
||||||
persons: {
|
persons: {
|
||||||
create: source.persons.map((p) => ({ role: p.role, age: p.age, retirementAge: p.retirementAge })),
|
create: source.persons.map((p) => ({ role: p.role, name: p.name, age: p.age, retirementAge: p.retirementAge })),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { getCurrentUserId } from "@/lib/session";
|
|||||||
|
|
||||||
const personSchema = z.object({
|
const personSchema = z.object({
|
||||||
role: z.enum(["PERSON_A", "PERSON_B"]),
|
role: z.enum(["PERSON_A", "PERSON_B"]),
|
||||||
|
name: z.string().max(60).nullish(),
|
||||||
age: z.number().int().min(0).max(120),
|
age: z.number().int().min(0).max(120),
|
||||||
retirementAge: z.number().int().min(30).max(100),
|
retirementAge: z.number().int().min(30).max(100),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { NumberField, SelectField } from "@/components/FormField";
|
import { NumberField, SelectField, TextField } from "@/components/FormField";
|
||||||
import type { HouseholdType, PersonRole } from "@/lib/types";
|
import type { HouseholdType, PersonRole } from "@/lib/types";
|
||||||
|
|
||||||
export interface ProfileDraft {
|
export interface ProfileDraft {
|
||||||
householdType: HouseholdType;
|
householdType: HouseholdType;
|
||||||
inflationRateDefault: number;
|
inflationRateDefault: number;
|
||||||
persons: { role: PersonRole; age: number; retirementAge: number }[];
|
persons: { role: PersonRole; name: string; age: number; retirementAge: number }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emptyProfileDraft(): ProfileDraft {
|
export function emptyProfileDraft(): ProfileDraft {
|
||||||
return {
|
return {
|
||||||
householdType: "SINGLE",
|
householdType: "SINGLE",
|
||||||
inflationRateDefault: 1.5,
|
inflationRateDefault: 1.5,
|
||||||
persons: [{ role: "PERSON_A", age: 35, retirementAge: 65 }],
|
persons: [{ role: "PERSON_A", name: "", age: 35, retirementAge: 65 }],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ export function PlanProfileFields({
|
|||||||
} else {
|
} else {
|
||||||
const persons =
|
const persons =
|
||||||
draft.persons.length < 2
|
draft.persons.length < 2
|
||||||
? [...draft.persons, { role: "PERSON_B" as PersonRole, age: 35, retirementAge: 65 }]
|
? [...draft.persons, { role: "PERSON_B" as PersonRole, name: "", age: 35, retirementAge: 65 }]
|
||||||
: draft.persons;
|
: draft.persons;
|
||||||
onChange({ ...draft, householdType: type, persons });
|
onChange({ ...draft, householdType: type, persons });
|
||||||
}
|
}
|
||||||
@@ -63,6 +63,14 @@ export function PlanProfileFields({
|
|||||||
<div className="col-span-2 text-xs font-semibold uppercase tracking-wide text-faint">
|
<div className="col-span-2 text-xs font-semibold uppercase tracking-wide text-faint">
|
||||||
{draft.householdType === "COUPLE" ? (person.role === "PERSON_A" ? "Person A" : "Person B") : "Ihre Angaben"}
|
{draft.householdType === "COUPLE" ? (person.role === "PERSON_A" ? "Person A" : "Person B") : "Ihre Angaben"}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-span-2">
|
||||||
|
<TextField
|
||||||
|
label="Name (optional)"
|
||||||
|
value={person.name}
|
||||||
|
placeholder={person.role === "PERSON_A" ? "Person A" : "Person B"}
|
||||||
|
onChange={(v) => updatePerson(index, { name: v })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<NumberField
|
<NumberField
|
||||||
label="Aktuelles Alter"
|
label="Aktuelles Alter"
|
||||||
value={person.age}
|
value={person.age}
|
||||||
|
|||||||
+21
-12
@@ -106,9 +106,16 @@ export function PlanView({
|
|||||||
return cols;
|
return cols;
|
||||||
}, [computed.phases]);
|
}, [computed.phases]);
|
||||||
|
|
||||||
|
// Anzeigename einer Person: eigener Name, sonst Fallback "Person A"/"Person B".
|
||||||
|
function personLabel(role: string): string {
|
||||||
|
const p = plan.persons.find((x) => x.role === role);
|
||||||
|
if (p?.name && p.name.trim()) return p.name.trim();
|
||||||
|
return role === "PERSON_A" ? "Person A" : "Person B";
|
||||||
|
}
|
||||||
|
|
||||||
const personAxes = plan.persons.map((p) => ({
|
const personAxes = plan.persons.map((p) => ({
|
||||||
role: p.role,
|
role: p.role,
|
||||||
label: p.role === "PERSON_A" ? "Person A" : "Person B",
|
label: personLabel(p.role),
|
||||||
currentAge: p.age,
|
currentAge: p.age,
|
||||||
retirementAge: p.retirementAge,
|
retirementAge: p.retirementAge,
|
||||||
color: p.role === "PERSON_A" ? "var(--person-a)" : "var(--person-b)",
|
color: p.role === "PERSON_A" ? "var(--person-a)" : "var(--person-b)",
|
||||||
@@ -242,7 +249,7 @@ export function PlanView({
|
|||||||
<span className="text-xs font-semibold uppercase tracking-wide text-faint">Grundprofil (Plan)</span>
|
<span className="text-xs font-semibold uppercase tracking-wide text-faint">Grundprofil (Plan)</span>
|
||||||
{plan.persons.map((p) => (
|
{plan.persons.map((p) => (
|
||||||
<span key={p.role} className="text-xs text-muted">
|
<span key={p.role} className="text-xs text-muted">
|
||||||
{plan.householdType === "COUPLE" ? (p.role === "PERSON_A" ? "Person A" : "Person B") : "Person"}: {p.age} J., Pension {p.retirementAge}
|
{personLabel(p.role)}: {p.age} J., Pension {p.retirementAge}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
<span className="text-xs text-muted">Inflation {plan.inflationRateDefault}%</span>
|
<span className="text-xs text-muted">Inflation {plan.inflationRateDefault}%</span>
|
||||||
@@ -347,9 +354,7 @@ export function PlanView({
|
|||||||
<td className="sticky left-0 z-10 border-b border-r border-border bg-surface px-3 py-1.5">
|
<td className="sticky left-0 z-10 border-b border-r border-border bg-surface px-3 py-1.5">
|
||||||
<div className="truncate text-xs font-medium text-fg">{el.name}</div>
|
<div className="truncate text-xs font-medium text-fg">{el.name}</div>
|
||||||
{el.ownerRole && el.ownerRole !== "HOUSEHOLD" && (
|
{el.ownerRole && el.ownerRole !== "HOUSEHOLD" && (
|
||||||
<div className="text-[10px] text-faint">
|
<div className="text-[10px] text-faint">{personLabel(el.ownerRole)}</div>
|
||||||
{el.ownerRole === "PERSON_A" ? "Person A" : "Person B"}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
{columns.map((col) => {
|
{columns.map((col) => {
|
||||||
@@ -675,23 +680,27 @@ function AddElementDialog({
|
|||||||
|
|
||||||
const needsPerson = PERSON_ONLY_CATEGORIES.includes(category);
|
const needsPerson = PERSON_ONLY_CATEGORIES.includes(category);
|
||||||
const isCouple = plan.householdType === "COUPLE";
|
const isCouple = plan.householdType === "COUPLE";
|
||||||
|
const pLabel = (role: string) => {
|
||||||
|
const p = plan.persons.find((x) => x.role === role);
|
||||||
|
return p?.name && p.name.trim() ? p.name.trim() : role === "PERSON_A" ? "Person A" : "Person B";
|
||||||
|
};
|
||||||
|
|
||||||
const ownerOptions = needsPerson
|
const ownerOptions = needsPerson
|
||||||
? isCouple
|
? isCouple
|
||||||
? [
|
? [
|
||||||
{ value: "PERSON_A", label: "Person A" },
|
{ value: "PERSON_A", label: pLabel("PERSON_A") },
|
||||||
{ value: "PERSON_B", label: "Person B" },
|
{ value: "PERSON_B", label: pLabel("PERSON_B") },
|
||||||
]
|
]
|
||||||
: [{ value: "PERSON_A", label: "Person A" }]
|
: [{ value: "PERSON_A", label: pLabel("PERSON_A") }]
|
||||||
: isCouple
|
: isCouple
|
||||||
? [
|
? [
|
||||||
{ value: "HOUSEHOLD", label: "Gemeinsam" },
|
{ value: "HOUSEHOLD", label: "Gemeinsam" },
|
||||||
{ value: "PERSON_A", label: "Person A" },
|
{ value: "PERSON_A", label: pLabel("PERSON_A") },
|
||||||
{ value: "PERSON_B", label: "Person B" },
|
{ value: "PERSON_B", label: pLabel("PERSON_B") },
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{ value: "HOUSEHOLD", label: "Gemeinsam" },
|
{ value: "HOUSEHOLD", label: "Gemeinsam" },
|
||||||
{ value: "PERSON_A", label: "Person A" },
|
{ value: "PERSON_A", label: pLabel("PERSON_A") },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Kontext fuer die Phase-1-Felder des neuen Elements.
|
// Kontext fuer die Phase-1-Felder des neuen Elements.
|
||||||
@@ -864,7 +873,7 @@ function PlanSettingsDialog({ plan, onClose, onSaved }: { plan: PlanInput; onClo
|
|||||||
const [draft, setDraft] = useState<ProfileDraft>({
|
const [draft, setDraft] = useState<ProfileDraft>({
|
||||||
householdType: plan.householdType,
|
householdType: plan.householdType,
|
||||||
inflationRateDefault: plan.inflationRateDefault,
|
inflationRateDefault: plan.inflationRateDefault,
|
||||||
persons: plan.persons.map((p) => ({ role: p.role, age: p.age, retirementAge: p.retirementAge })),
|
persons: plan.persons.map((p) => ({ role: p.role, name: p.name ?? "", age: p.age, retirementAge: p.retirementAge })),
|
||||||
});
|
});
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export function toPlanInput(plan: PlanWithRelations): PlanInput {
|
|||||||
persons: plan.persons.map((p) => ({
|
persons: plan.persons.map((p) => ({
|
||||||
id: p.id,
|
id: p.id,
|
||||||
role: p.role,
|
role: p.role,
|
||||||
|
name: p.name,
|
||||||
age: p.age,
|
age: p.age,
|
||||||
retirementAge: p.retirementAge,
|
retirementAge: p.retirementAge,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export type PersonRole = "PERSON_A" | "PERSON_B";
|
|||||||
export interface PersonInput {
|
export interface PersonInput {
|
||||||
id: string;
|
id: string;
|
||||||
role: PersonRole;
|
role: PersonRole;
|
||||||
|
name: string | null;
|
||||||
age: number;
|
age: number;
|
||||||
retirementAge: number;
|
retirementAge: number;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user