diff --git a/frontend/src/client/fristenrechner.ts b/frontend/src/client/fristenrechner.ts index 85c6886..d8dfd40 100644 --- a/frontend/src/client/fristenrechner.ts +++ b/frontend/src/client/fristenrechner.ts @@ -494,7 +494,22 @@ function renderProcedureResults(data: DeadlineResponse) { container.innerHTML = headerHtml + bodyHtml; printBtn.style.display = "block"; - if (saveBtn) saveBtn.style.display = "block"; + if (saveBtn) { + // Ad-hoc explore-mode has no project to save against — show the + // CTA disabled with a hint so the user understands why the action + // is blocked (m's 2026-05-08 Slice 1 lock #2). Hiding it would + // leave the user wondering where the save went. + saveBtn.style.display = "block"; + if (isAdhocMode()) { + saveBtn.disabled = true; + saveBtn.title = t("deadlines.save.cta.adhoc.hint"); + saveBtn.dataset.adhocDisabled = "true"; + } else { + saveBtn.disabled = false; + saveBtn.removeAttribute("title"); + delete saveBtn.dataset.adhocDisabled; + } + } if (toggle) toggle.style.display = ""; applyPendingFocus(); @@ -2325,12 +2340,31 @@ function setPathwayURL(path: Pathway, mode?: BMode, replace = false) { } function showPathway(path: Pathway, mode?: BMode) { - const fork = document.getElementById("fristen-pathway-fork"); + // m's 2026-05-08 18:08 redesign retired the legacy fork; Step 1 and + // Step 2 sit where it used to live. The "fork" Pathway value now + // means "show Step 1 + Step 2 (Step 2 visibility gated by whether a + // project / ad-hoc context is selected — managed by initStep1Step2)"; + // "a" / "b" still drive the existing wizard / cascade shells. + const step1 = document.getElementById("fristen-step1"); + const step1Summary = document.getElementById("fristen-step1-summary"); + const step2 = document.getElementById("fristen-step2"); const a = document.getElementById("fristen-pathway-a"); const b = document.getElementById("fristen-pathway-b"); - if (!fork || !a || !b) return; + if (!a || !b) return; - fork.hidden = path !== "fork"; + // Step 1 + 2 stay mounted under "fork". Step 2 visibility is also + // gated by the Step 1 context state — initStep1Step2 owns the + // toggle between Step 1 expanded vs collapsed-with-summary, and + // the "show Step 2" gate. We just hide them wholesale when not on + // the fork. + if (step1) step1.style.display = path === "fork" ? "" : "none"; + if (step1Summary) { + // Summary stays visible from Step 2 onward so the user always + // sees their selected Akte. Hidden only when no context is set. + const ctx = readStep1ContextFromURL(); + step1Summary.style.display = (ctx.kind !== "none" && path !== "fork") ? "" : step1Summary.style.display; + } + if (step2) step2.hidden = path !== "fork"; a.hidden = path !== "a"; b.hidden = path !== "b"; @@ -2374,29 +2408,226 @@ function navigateToPathway(path: Pathway, mode?: BMode) { } } +// ============================================================================ +// m's 2026-05-08 18:08 Determinator redesign — Step 1 + Step 2 state +// ============================================================================ +// Step 1: pick the project (Akte) that scopes everything downstream, OR +// pick an ad-hoc explore-mode chip (4 jurisdictions). Step 2: choose +// between outgoing intent (Pathway A / Verfahrensablauf) and incoming +// event (Pathway B / cascade). Step 3+ stay as today (Pathway A wizard, +// B1 cascade, B2 search). The legacy "Was möchten Sie tun?" fork is +// retired; back-buttons inside Pathway A/B return to Step 2. + +type Step1ContextKind = "project" | "adhoc" | "none"; +type AdhocForum = "upc" | "de" | "epa" | "dpma"; + +interface Step1Context { + kind: Step1ContextKind; + projectId?: string; + project?: ProjectOption; + adhocForum?: AdhocForum; +} + +let currentStep1Context: Step1Context = { kind: "none" }; +let cachedAkten: ProjectOption[] = []; + +function readStep1ContextFromURL(): Step1Context { + const sp = new URLSearchParams(window.location.search); + const project = sp.get("project"); + const adhoc = sp.get("ad_hoc"); + if (project) return { kind: "project", projectId: project }; + if (adhoc === "upc" || adhoc === "de" || adhoc === "epa" || adhoc === "dpma") { + return { kind: "adhoc", adhocForum: adhoc }; + } + return { kind: "none" }; +} + +function writeStep1ContextToURL(ctx: Step1Context, replace = false) { + const url = new URL(window.location.href); + if (ctx.kind === "project" && ctx.projectId) { + url.searchParams.set("project", ctx.projectId); + url.searchParams.delete("ad_hoc"); + } else if (ctx.kind === "adhoc" && ctx.adhocForum) { + url.searchParams.set("ad_hoc", ctx.adhocForum); + url.searchParams.delete("project"); + } else { + url.searchParams.delete("project"); + url.searchParams.delete("ad_hoc"); + } + if (replace) window.history.replaceState({}, "", url.toString()); + else window.history.pushState({}, "", url.toString()); +} + +// isAdhocMode is read by the save-to-project CTA — ad-hoc has no +// project to save against, so the CTA disables and renders a hint. +function isAdhocMode(): boolean { + return currentStep1Context.kind === "adhoc"; +} + +function adhocSummaryLabel(forum: AdhocForum): string { + switch (forum) { + case "upc": return "Ad-hoc UPC"; + case "de": return "Ad-hoc DE"; + case "epa": return "Ad-hoc EPA"; + case "dpma": return "Ad-hoc DPMA"; + } +} + +function renderAkteList(query: string) { + const list = document.getElementById("fristen-akte-list"); + if (!list) return; + const q = query.trim().toLowerCase(); + const filtered = q === "" + ? cachedAkten.slice(0, 12) + : cachedAkten.filter((p) => + (p.title || "").toLowerCase().includes(q) || + (p.reference || "").toLowerCase().includes(q)); + if (filtered.length === 0) { + list.innerHTML = `