Lebensphasen-Zellen oeffnen jetzt ein Popup (statt unterem Detail-Panel)
Deploy App / deploy (push) Successful in 1m5s

Klick auf eine Element-Zelle in einer Lebensphase oeffnet neu ein kleines Popup
(PhaseCellDialog mit ElementDetail) - analog zu den Uebergangszellen. Das untere
Detail-Panel dient nur noch der Phasen-Kopf-Bearbeitung. Popup ist per key stabil
(kein Uebertrag des Formularzustands zwischen Elementen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 12:32:45 +02:00
parent 5e17ce4afb
commit 7222faa98c
+75 -45
View File
@@ -75,7 +75,6 @@ type Column =
| { kind: "transition"; fromPhase: PhaseComputed; toPhase: PhaseComputed }; | { kind: "transition"; fromPhase: PhaseComputed; toPhase: PhaseComputed };
type Selection = type Selection =
| { type: "phaseCell"; elementId: string; phaseId: string }
| { type: "phase"; phaseId: string }; | { type: "phase"; phaseId: string };
export function PlanView({ export function PlanView({
@@ -94,6 +93,7 @@ export function PlanView({
const [showSettings, setShowSettings] = useState(false); const [showSettings, setShowSettings] = useState(false);
const [reviewFromPhaseId, setReviewFromPhaseId] = useState<string | null>(null); const [reviewFromPhaseId, setReviewFromPhaseId] = useState<string | null>(null);
const [editTransition, setEditTransition] = useState<{ elementId: string; fromPhaseId: string } | null>(null); const [editTransition, setEditTransition] = useState<{ elementId: string; fromPhaseId: string } | null>(null);
const [editPhaseCell, setEditPhaseCell] = useState<{ elementId: string; phaseId: string } | null>(null);
const columns = useMemo<Column[]>(() => { const columns = useMemo<Column[]>(() => {
const cols: Column[] = []; const cols: Column[] = [];
@@ -389,17 +389,13 @@ export function PlanView({
{columns.map((col) => { {columns.map((col) => {
if (col.kind === "phase") { if (col.kind === "phase") {
const ce = computedElement(col.phase.id, el.id); const ce = computedElement(col.phase.id, el.id);
const isSel =
selected?.type === "phaseCell" &&
selected.elementId === el.id &&
selected.phaseId === col.phase.id;
return ( return (
<td <td
key={col.phase.id} key={col.phase.id}
onClick={() => setSelected({ type: "phaseCell", elementId: el.id, phaseId: col.phase.id })} onClick={() => setEditPhaseCell({ elementId: el.id, phaseId: col.phase.id })}
className={`cursor-pointer border-b border-r border-border px-2 py-1.5 text-center text-xs ${ className={`cursor-pointer border-b border-r border-border px-2 py-1.5 text-center text-xs ${
isSel ? "bg-accent-soft" : "" ce?.locked ? "text-faint" : "text-fg"
} ${ce?.locked ? "text-faint" : "text-fg"}`} }`}
> >
{phaseCellContent(ce)} {phaseCellContent(ce)}
</td> </td>
@@ -443,10 +439,7 @@ export function PlanView({
{/* Detail-Panel. Der key erzwingt beim Wechsel von Zelle/Phase einen Neuaufbau, {/* Detail-Panel. Der key erzwingt beim Wechsel von Zelle/Phase einen Neuaufbau,
damit der lokale Formular-Zustand nicht vom vorher geoeffneten Element uebrig bleibt. */} damit der lokale Formular-Zustand nicht vom vorher geoeffneten Element uebrig bleibt. */}
{selected && ( {selected && (
<div <div key={`ph-${selected.phaseId}`} className="rounded-xl border border-accent bg-surface p-4 shadow-sm">
key={selected.type === "phase" ? `ph-${selected.phaseId}` : `pc-${selected.elementId}-${selected.phaseId}`}
className="rounded-xl border border-accent bg-surface p-4 shadow-sm"
>
{renderDetail()} {renderDetail()}
</div> </div>
)} )}
@@ -526,6 +519,31 @@ export function PlanView({
/> />
); );
})()} })()}
{editPhaseCell && (() => {
const element = plan.elements.find((e) => e.id === editPhaseCell.elementId);
const phase = computed.phases.find((p) => p.id === editPhaseCell.phaseId);
if (!element || !phase) return null;
const context = buildPhaseContext(phase, element);
return (
<PhaseCellDialog
key={`${editPhaseCell.elementId}-${editPhaseCell.phaseId}`}
element={element}
phase={phase}
context={context}
phaseData={element.phaseValues[phase.id] ?? {}}
onClose={() => setEditPhaseCell(null)}
onSaved={() => {
setEditPhaseCell(null);
onChanged();
}}
onDeleted={() => {
setEditPhaseCell(null);
deleteElement(element.id);
}}
/>
);
})()}
</div> </div>
); );
@@ -541,41 +559,21 @@ export function PlanView({
function renderDetail() { function renderDetail() {
if (!selected) return null; if (!selected) return null;
const phase = computed.phases.find((p) => p.id === selected.phaseId);
if (selected.type === "phase") { const phaseInput = plan.phases.find((p) => p.id === selected.phaseId);
const phase = computed.phases.find((p) => p.id === selected.phaseId); if (!phase || !phaseInput) return null;
const phaseInput = plan.phases.find((p) => p.id === selected.phaseId); const isLast = phase.sequenceNumber === computed.phases.length;
if (!phase || !phaseInput) return null;
const isLast = phase.sequenceNumber === computed.phases.length;
return (
<PhaseDetail
phase={phaseInput}
maxDurationYears={phase.maxDurationYears}
isLast={isLast}
inflationDefault={plan.inflationRateDefault}
onSaved={onChanged}
onDeleted={() => {
setSelected(null);
onChanged();
}}
/>
);
}
const element = plan.elements.find((e) => e.id === selected.elementId);
if (!element) return null;
// phaseCell
const phase = computed.phases.find((p) => p.id === selected.phaseId)!;
const context = buildPhaseContext(phase, element);
return ( return (
<ElementDetail <PhaseDetail
element={element} phase={phaseInput}
context={context} maxDurationYears={phase.maxDurationYears}
phaseData={element.phaseValues[phase.id] ?? {}} isLast={isLast}
transitionData={{}} inflationDefault={plan.inflationRateDefault}
onSaved={onChanged} onSaved={onChanged}
onDeleteElement={() => deleteElement(element.id)} onDeleted={() => {
setSelected(null);
onChanged();
}}
/> />
); );
} }
@@ -1040,6 +1038,38 @@ function TransitionReviewDialog({
); );
} }
// --- Dialog: Element-Werte einer Lebensphase (per Klick auf eine Phasenzelle) ---
function PhaseCellDialog({
element,
phase,
context,
phaseData,
onClose,
onSaved,
onDeleted,
}: {
element: ElementInput;
phase: PhaseComputed;
context: CellContext;
phaseData: PhaseData;
onClose: () => void;
onSaved: () => void;
onDeleted: () => void;
}) {
return (
<DialogShell title={`Lebensphase: ${phase.name}`} onClose={onClose} wide>
<ElementDetail
element={element}
context={context}
phaseData={phaseData}
transitionData={{}}
onSaved={onSaved}
onDeleteElement={onDeleted}
/>
</DialogShell>
);
}
// --- Dialog: einzelner Übergangs-Entscheid (per Klick auf eine Übergangszelle) --- // --- Dialog: einzelner Übergangs-Entscheid (per Klick auf eine Übergangszelle) ---
function TransitionCellDialog({ function TransitionCellDialog({
element, element,