Klick auf "Effektive Werte" in der Sidebar oeffnet neu -- analog zu "Szenarien" -- eine Liste im Hauptbereich statt eines Modals. Neuer Plan-Tab "actuals"; ActualsDialog bekommt einen embedded-Modus (ohne Overlay, Backdrop und Schliessen-Knopf). Der Modal-Weg ueber den Matrix-Knopf bleibt bestehen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,7 @@ export function ActualsDialog({
|
|||||||
planName,
|
planName,
|
||||||
scenarioMetas,
|
scenarioMetas,
|
||||||
initial,
|
initial,
|
||||||
|
embedded = false,
|
||||||
onClose,
|
onClose,
|
||||||
onChanged,
|
onChanged,
|
||||||
}: {
|
}: {
|
||||||
@@ -52,6 +53,9 @@ export function ActualsDialog({
|
|||||||
scenarioMetas: { id: string; name: string; isBase: boolean }[];
|
scenarioMetas: { id: string; name: string; isBase: boolean }[];
|
||||||
// Das bereits geöffnete Szenario, damit der Dialog sofort etwas anzeigen kann.
|
// Das bereits geöffnete Szenario, damit der Dialog sofort etwas anzeigen kann.
|
||||||
initial: LoadedScenario;
|
initial: LoadedScenario;
|
||||||
|
// Eingebettet in den Hauptbereich (Plan-Ansicht) statt als Pop-up -- ohne Overlay,
|
||||||
|
// ohne Backdrop, ohne Schliessen-Knopf.
|
||||||
|
embedded?: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onChanged: () => void;
|
onChanged: () => void;
|
||||||
}) {
|
}) {
|
||||||
@@ -252,9 +256,8 @@ export function ActualsDialog({
|
|||||||
const setRow = (rootId: string, patch: { value?: number; mortgage?: number }) =>
|
const setRow = (rootId: string, patch: { value?: number; mortgage?: number }) =>
|
||||||
setValues((prev) => ({ ...prev, [rootId]: { ...prev[rootId], ...patch } }));
|
setValues((prev) => ({ ...prev, [rootId]: { ...prev[rootId], ...patch } }));
|
||||||
|
|
||||||
return (
|
const body = (
|
||||||
<div className="ui-fade fixed inset-0 z-40 flex items-start justify-center overflow-y-auto bg-black/40 px-4 py-8" onClick={onClose}>
|
<>
|
||||||
<div onClick={(e) => e.stopPropagation()} className="ui-pop flex w-full max-w-4xl flex-col gap-4 rounded-2xl border border-border bg-surface p-6 shadow-xl">
|
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="flex items-center gap-2 text-base font-semibold text-fg">
|
<h2 className="flex items-center gap-2 text-base font-semibold text-fg">
|
||||||
@@ -264,9 +267,11 @@ export function ActualsDialog({
|
|||||||
Plan «{planName}». Was tatsächlich eingetreten ist – der Plan selbst bleibt unverändert.
|
Plan «{planName}». Was tatsächlich eingetreten ist – der Plan selbst bleibt unverändert.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
{!embedded && (
|
||||||
<button type="button" onClick={onClose} aria-label="Schliessen" className="rounded-md p-1 text-faint hover:bg-surface-2">
|
<button type="button" onClick={onClose} aria-label="Schliessen" className="rounded-md p-1 text-faint hover:bg-surface-2">
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{mode === "list" && (
|
{mode === "list" && (
|
||||||
@@ -435,6 +440,17 @@ export function ActualsDialog({
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Eingebettet: schlichter Container im Hauptbereich. Sonst: Pop-up mit Overlay.
|
||||||
|
if (embedded) {
|
||||||
|
return <div className="flex flex-col gap-4">{body}</div>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="ui-fade fixed inset-0 z-40 flex items-start justify-center overflow-y-auto bg-black/40 px-4 py-8" onClick={onClose}>
|
||||||
|
<div onClick={(e) => e.stopPropagation()} className="ui-pop flex w-full max-w-4xl flex-col gap-4 rounded-2xl border border-border bg-surface p-6 shadow-xl">
|
||||||
|
{body}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+29
-10
@@ -105,7 +105,7 @@ function AppShellInner({ username }: { username: string }) {
|
|||||||
const [showHistory, setShowHistory] = useState(false);
|
const [showHistory, setShowHistory] = useState(false);
|
||||||
const [showActuals, setShowActuals] = useState(false);
|
const [showActuals, setShowActuals] = useState(false);
|
||||||
// Plan-Ebene: Dashboard / Szenarien-Liste / Analysen. Null = kein Plan-View aktiv.
|
// Plan-Ebene: Dashboard / Szenarien-Liste / Analysen. Null = kein Plan-View aktiv.
|
||||||
const [planNav, setPlanNav] = useState<{ planId: string; tab: "dashboard" | "scenarios" | "analyses" } | null>(null);
|
const [planNav, setPlanNav] = useState<{ planId: string; tab: "dashboard" | "scenarios" | "actuals" | "analyses" } | null>(null);
|
||||||
// Welche Szenario-Bäume in der Seitenleiste aufgeklappt sind. Standard: eingeklappt.
|
// Welche Szenario-Bäume in der Seitenleiste aufgeklappt sind. Standard: eingeklappt.
|
||||||
const [expandedTrees, setExpandedTrees] = useState<Record<string, boolean>>({});
|
const [expandedTrees, setExpandedTrees] = useState<Record<string, boolean>>({});
|
||||||
const [savedAnalysisId, setSavedAnalysisId] = useState<string | null>(null);
|
const [savedAnalysisId, setSavedAnalysisId] = useState<string | null>(null);
|
||||||
@@ -182,7 +182,7 @@ function AppShellInner({ username }: { username: string }) {
|
|||||||
|
|
||||||
// Plan-Ebene öffnen (Dashboard / Szenarien / Analysen). Räumt die Szenario- und Wissens-
|
// Plan-Ebene öffnen (Dashboard / Szenarien / Analysen). Räumt die Szenario- und Wissens-
|
||||||
// Ansichten weg -- es kann immer nur eine Hauptansicht aktiv sein.
|
// Ansichten weg -- es kann immer nur eine Hauptansicht aktiv sein.
|
||||||
function openPlanTab(planId: string, tab: "dashboard" | "scenarios" | "analyses") {
|
function openPlanTab(planId: string, tab: "dashboard" | "scenarios" | "actuals" | "analyses") {
|
||||||
setPlanNav({ planId, tab });
|
setPlanNav({ planId, tab });
|
||||||
setSelectedScenarioId(null);
|
setSelectedScenarioId(null);
|
||||||
setShowSpec(false);
|
setShowSpec(false);
|
||||||
@@ -204,13 +204,13 @@ function AppShellInner({ username }: { username: string }) {
|
|||||||
else setShowLiveSim(true);
|
else setShowLiveSim(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Effektive Werte von der Plan-Ebene aus öffnen (braucht das Basisszenario geladen).
|
// Effektive-Werte-Ansicht (Plan-Ebene, eingebettet). Lädt das Basisszenario, das der
|
||||||
async function openActualsForPlan(planId: string) {
|
// eingebettete Dialog als Ausgangspunkt braucht, und öffnet dann den Tab.
|
||||||
|
async function openActualsTab(planId: string) {
|
||||||
const plan = plans.find((p) => p.id === planId);
|
const plan = plans.find((p) => p.id === planId);
|
||||||
const baseId = plan?.scenarios.find((s) => s.isBase)?.id ?? plan?.scenarios[0]?.id;
|
const baseId = plan?.scenarios.find((s) => s.isBase)?.id ?? plan?.scenarios[0]?.id;
|
||||||
if (!baseId) return;
|
if (baseId && detail?.meta.id !== baseId) await loadDetail(baseId, true);
|
||||||
if (detail?.meta.id !== baseId) await loadDetail(baseId, true);
|
openPlanTab(planId, "actuals");
|
||||||
setShowActuals(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDeletePlan(id: string) {
|
async function handleDeletePlan(id: string) {
|
||||||
@@ -347,7 +347,7 @@ function AppShellInner({ username }: { username: string }) {
|
|||||||
|
|
||||||
{plans.map((p) => {
|
{plans.map((p) => {
|
||||||
const navHere = planNav?.planId === p.id;
|
const navHere = planNav?.planId === p.id;
|
||||||
const subItem = (tab: "dashboard" | "scenarios" | "analyses", label: string, Icon: typeof FolderKanban) => (
|
const subItem = (tab: "dashboard" | "scenarios" | "actuals" | "analyses", label: string, Icon: typeof FolderKanban) => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => openPlanTab(p.id, tab)}
|
onClick={() => openPlanTab(p.id, tab)}
|
||||||
@@ -418,8 +418,10 @@ function AppShellInner({ username }: { username: string }) {
|
|||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => openActualsForPlan(p.id)}
|
onClick={() => void openActualsTab(p.id)}
|
||||||
className="flex w-full items-center gap-2 rounded-lg py-1.5 pl-8 pr-3 text-left text-xs font-medium text-muted transition-colors hover:bg-surface-2"
|
className={`flex w-full items-center gap-2 rounded-lg py-1.5 pl-8 pr-3 text-left text-xs font-medium transition-colors ${
|
||||||
|
navHere && planNav?.tab === "actuals" ? "bg-accent-soft text-accent-soft-fg" : "text-muted hover:bg-surface-2"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<CalendarClock className="h-3.5 w-3.5 shrink-0" />
|
<CalendarClock className="h-3.5 w-3.5 shrink-0" />
|
||||||
Effektive Werte
|
Effektive Werte
|
||||||
@@ -540,6 +542,23 @@ function AppShellInner({ username }: { username: string }) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{!showSpec && !showSystemParams && planNav?.tab === "actuals" && activePlan && (
|
||||||
|
detail && detail.meta.planId === planNav.planId ? (
|
||||||
|
<ActualsDialog
|
||||||
|
key={planNav.planId}
|
||||||
|
embedded
|
||||||
|
planId={planNav.planId}
|
||||||
|
planName={activePlan.name}
|
||||||
|
scenarioMetas={activePlan.scenarios}
|
||||||
|
initial={{ id: detail.meta.id, name: detail.meta.name, isBase: detail.meta.isBase, plan: detail.plan }}
|
||||||
|
onClose={() => {}}
|
||||||
|
onChanged={refreshCurrent}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-muted">Wird geladen…</p>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
|
||||||
{!showSpec && !showSystemParams && planNav?.tab === "analyses" && (
|
{!showSpec && !showSystemParams && planNav?.tab === "analyses" && (
|
||||||
<AnalysesView
|
<AnalysesView
|
||||||
planId={planNav.planId}
|
planId={planNav.planId}
|
||||||
|
|||||||
Reference in New Issue
Block a user