Compare commits
6 Commits
bb3d7aabd7
...
1718ea2eae
| Author | SHA1 | Date | |
|---|---|---|---|
| 1718ea2eae | |||
| 39c8ef343b | |||
| 48a07ef4ef | |||
| c8261da492 | |||
| 0568d340a7 | |||
| 60907e7153 |
@@ -3,8 +3,7 @@ import { join, relative } from "path";
|
||||
import { renderIndex } from "./src/index";
|
||||
import { renderLogin } from "./src/login";
|
||||
import { renderKostenrechner } from "./src/kostenrechner";
|
||||
import { renderFristenrechner } from "./src/fristenrechner";
|
||||
import { renderVerfahrensablauf } from "./src/verfahrensablauf";
|
||||
import { renderProcedures } from "./src/procedures";
|
||||
import { renderDownloads } from "./src/downloads";
|
||||
import { renderLinks } from "./src/links";
|
||||
import { renderGlossary } from "./src/glossary";
|
||||
@@ -241,8 +240,7 @@ async function build() {
|
||||
join(import.meta.dir, "src/client/index.ts"),
|
||||
join(import.meta.dir, "src/client/login.ts"),
|
||||
join(import.meta.dir, "src/client/kostenrechner.ts"),
|
||||
join(import.meta.dir, "src/client/fristenrechner.ts"),
|
||||
join(import.meta.dir, "src/client/verfahrensablauf.ts"),
|
||||
join(import.meta.dir, "src/client/procedures.ts"),
|
||||
join(import.meta.dir, "src/client/downloads.ts"),
|
||||
join(import.meta.dir, "src/client/links.ts"),
|
||||
join(import.meta.dir, "src/client/glossary.ts"),
|
||||
@@ -369,8 +367,7 @@ async function build() {
|
||||
await Bun.write(join(DIST, "index.html"), renderIndex());
|
||||
await Bun.write(join(DIST, "login.html"), renderLogin("login.js"));
|
||||
await Bun.write(join(DIST, "kostenrechner.html"), renderKostenrechner());
|
||||
await Bun.write(join(DIST, "fristenrechner.html"), renderFristenrechner());
|
||||
await Bun.write(join(DIST, "verfahrensablauf.html"), renderVerfahrensablauf());
|
||||
await Bun.write(join(DIST, "procedures.html"), renderProcedures());
|
||||
await Bun.write(join(DIST, "downloads.html"), renderDownloads());
|
||||
await Bun.write(join(DIST, "links.html"), renderLinks());
|
||||
await Bun.write(join(DIST, "glossary.html"), renderGlossary());
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,8 +20,6 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
// Navigation
|
||||
"nav.home": "Home",
|
||||
"nav.kostenrechner": "Kostenrechner",
|
||||
"nav.fristenrechner": "Fristenrechner",
|
||||
"nav.verfahrensablauf": "Verfahrensablauf",
|
||||
"nav.downloads": "Downloads",
|
||||
"nav.links": "Links",
|
||||
"nav.glossar": "Glossar",
|
||||
@@ -200,10 +198,21 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
"deadlines.heading": "Fristenrechner",
|
||||
"deadlines.subtitle": "Berechnung von Verfahrensfristen f\u00fcr UPC-, deutsche und EPA-Verfahren.",
|
||||
|
||||
// Verfahrensablauf (t-paliad-179 Slice 1)
|
||||
"tools.verfahrensablauf.title": "Verfahrensablauf \u2014 Paliad",
|
||||
"tools.verfahrensablauf.heading": "Verfahrensablauf",
|
||||
"tools.verfahrensablauf.subtitle": "Typischen Verfahrensablauf einsehen \u2014 Verfahrensart w\u00e4hlen, Datum optional setzen.",
|
||||
// Unified procedural-events tool (m/paliad#151)
|
||||
"procedures.title": "Verfahren & Fristen \u2014 Paliad",
|
||||
"procedures.heading": "Verfahren & Fristen",
|
||||
"procedures.subtitle": "Verfahrensablauf, Fristenrechner und gef\u00fchrte Suche in einem Tool.",
|
||||
"procedures.filter.search.placeholder": "Klageerhebung, Hinweisbeschluss, oral hearing\u2026",
|
||||
"procedures.filter.axis.forum": "Forum:",
|
||||
"procedures.filter.axis.proc": "Verfahren:",
|
||||
"procedures.filter.axis.kind": "Ereignisart:",
|
||||
"procedures.filter.axis.party": "Partei:",
|
||||
"procedures.tab.proceeding": "Verfahren w\u00e4hlen",
|
||||
"procedures.tab.search": "Direkt suchen",
|
||||
"procedures.tab.wizard": "Gef\u00fchrt",
|
||||
"procedures.tab.akte": "Aus Akte",
|
||||
"procedures.panel.akte.placeholder": "Akten-Einstieg folgt in einem sp\u00e4teren Slice.",
|
||||
"nav.procedures": "Verfahren & Fristen",
|
||||
|
||||
"deadlines.step1": "Verfahrensart w\u00e4hlen",
|
||||
"deadlines.step2": "Ausgangsdatum eingeben",
|
||||
@@ -3217,8 +3226,6 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
// Navigation
|
||||
"nav.home": "Home",
|
||||
"nav.kostenrechner": "Cost Calculator",
|
||||
"nav.fristenrechner": "Deadline Calculator",
|
||||
"nav.verfahrensablauf": "Procedure Roadmap",
|
||||
"nav.downloads": "Downloads",
|
||||
"nav.links": "Links",
|
||||
"nav.glossar": "Glossary",
|
||||
@@ -3395,9 +3402,21 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
"deadlines.subtitle": "Calculate procedural deadlines for UPC, German, and EPA proceedings.",
|
||||
|
||||
// Verfahrensablauf (t-paliad-179 Slice 1)
|
||||
"tools.verfahrensablauf.title": "Procedure Roadmap \u2014 Paliad",
|
||||
"tools.verfahrensablauf.heading": "Procedure Roadmap",
|
||||
"tools.verfahrensablauf.subtitle": "Browse the typical proceeding shape \u2014 pick a proceeding type, optionally set a trigger date.",
|
||||
// Unified procedural-events tool (m/paliad#151)
|
||||
"procedures.title": "Procedures & Deadlines \u2014 Paliad",
|
||||
"procedures.heading": "Procedures & Deadlines",
|
||||
"procedures.subtitle": "Procedure roadmap, deadline calculator, and guided search in one tool.",
|
||||
"procedures.filter.search.placeholder": "Statement of claim, hearing notice, m\u00fcndliche Verhandlung\u2026",
|
||||
"procedures.filter.axis.forum": "Forum:",
|
||||
"procedures.filter.axis.proc": "Proceeding:",
|
||||
"procedures.filter.axis.kind": "Event kind:",
|
||||
"procedures.filter.axis.party": "Party:",
|
||||
"procedures.tab.proceeding": "Pick proceeding",
|
||||
"procedures.tab.search": "Direct search",
|
||||
"procedures.tab.wizard": "Guided",
|
||||
"procedures.tab.akte": "From matter",
|
||||
"procedures.panel.akte.placeholder": "Matter entry ships in a later slice.",
|
||||
"nav.procedures": "Procedures & Deadlines",
|
||||
|
||||
"deadlines.step1": "Select Proceeding Type",
|
||||
"deadlines.step2": "Enter Trigger Date",
|
||||
|
||||
@@ -109,7 +109,7 @@ export function routeNameFor(pathname: string): string {
|
||||
if (pathname === "/links") return "links";
|
||||
if (pathname === "/downloads") return "downloads";
|
||||
if (pathname === "/checklists") return "checklists";
|
||||
if (pathname.startsWith("/tools/fristenrechner")) return "tools.fristenrechner";
|
||||
if (pathname.startsWith("/tools/procedures")) return "tools.procedures";
|
||||
if (pathname.startsWith("/tools/kostenrechner")) return "tools.kostenrechner";
|
||||
if (pathname.startsWith("/tools/gebuehrentabellen")) return "tools.gebuehrentabellen";
|
||||
if (pathname === "/events") return "events";
|
||||
|
||||
150
frontend/src/client/procedures.ts
Normal file
150
frontend/src/client/procedures.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
// /tools/procedures client (m/paliad#151,
|
||||
// docs/design-unified-procedural-events-tool-2026-05-27.md).
|
||||
//
|
||||
// Boot logic + tab switching for the unified procedural-events tool.
|
||||
// Each entry tab mounts its own module; the search box and chip
|
||||
// filters in the top filter strip are wired in U1+ as each slice adds
|
||||
// its dimension-aware behaviour.
|
||||
//
|
||||
// U0 — Skeleton + tab toggling.
|
||||
// U1 — Direkt suchen mounts Mode A.
|
||||
// U2 — Geführt mounts Mode B wizard.
|
||||
// U3 — Verfahren wählen wires the Verfahrensablauf wizard + detail-mode toggle.
|
||||
//
|
||||
// Mode A renders its shell into #fristen-overhaul-root (replacing
|
||||
// children); Mode B renders into #fristen-overhaul-mode-host; the
|
||||
// result view (post-commit) writes into #fristen-overhaul-root. To
|
||||
// keep those IDs unique in the DOM, only the active tab's panel ever
|
||||
// hosts the overhaul scaffold — installOverhaulHost() tears down any
|
||||
// existing host and installs a fresh one inside the target panel
|
||||
// before handing off to the per-mode module.
|
||||
|
||||
import { initI18n } from "./i18n";
|
||||
import { initSidebar } from "./sidebar";
|
||||
import { mountModeA } from "./fristenrechner-mode-a";
|
||||
import { mountResultView } from "./fristenrechner-result";
|
||||
import { mountWizard } from "./fristenrechner-wizard";
|
||||
import { initVerfahrensablauf } from "./verfahrensablauf";
|
||||
|
||||
type ProceduresTab = "proceeding" | "search" | "wizard" | "akte";
|
||||
|
||||
const TABS: ProceduresTab[] = ["proceeding", "search", "wizard", "akte"];
|
||||
|
||||
function readTabFromUrl(): ProceduresTab {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const raw = params.get("mode");
|
||||
if (raw && (TABS as string[]).includes(raw)) return raw as ProceduresTab;
|
||||
return "proceeding";
|
||||
}
|
||||
|
||||
function writeTabToUrl(tab: ProceduresTab): void {
|
||||
const url = new URL(window.location.href);
|
||||
if (tab === "proceeding") {
|
||||
url.searchParams.delete("mode");
|
||||
} else {
|
||||
url.searchParams.set("mode", tab);
|
||||
}
|
||||
history.replaceState(null, "", url.pathname + url.search + url.hash);
|
||||
}
|
||||
|
||||
// installOverhaulHost moves the (legacy) #fristen-overhaul-root /
|
||||
// #fristen-overhaul-mode-host scaffold under `panelId`. Always clears
|
||||
// any existing host first, so the IDs stay unique across the page even
|
||||
// when the user toggles between Direkt-suchen and Geführt — both Mode
|
||||
// A and the wizard read these IDs from document.getElementById which
|
||||
// returns the first match in DOM order, so two parallel hosts would
|
||||
// cross-wire.
|
||||
function installOverhaulHost(panelId: string): HTMLElement | null {
|
||||
document.querySelectorAll("#fristen-overhaul-root").forEach((el) => el.remove());
|
||||
const panel = document.getElementById(panelId);
|
||||
if (!panel) return null;
|
||||
panel.innerHTML = `
|
||||
<div class="procedures-overhaul-host">
|
||||
<div class="fristen-overhaul-root" id="fristen-overhaul-root">
|
||||
<div id="fristen-overhaul-mode-host"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return panel;
|
||||
}
|
||||
|
||||
function setActiveTabUI(tab: ProceduresTab): void {
|
||||
for (const t of TABS) {
|
||||
const btn = document.getElementById(`procedures-tab-${t}`);
|
||||
const panel = document.getElementById(`procedures-panel-${t}`);
|
||||
const active = t === tab;
|
||||
if (btn) {
|
||||
btn.classList.toggle("is-active", active);
|
||||
btn.setAttribute("aria-selected", active ? "true" : "false");
|
||||
}
|
||||
if (panel) panel.hidden = !active;
|
||||
}
|
||||
}
|
||||
|
||||
// Verfahrensablauf wiring is idempotent-unfriendly (module-local
|
||||
// selectedType + lastResponse + listeners that re-bind on every
|
||||
// proceeding click). Wire it exactly once per page load; on subsequent
|
||||
// activations the existing DOM + listeners are reused so picked
|
||||
// proceeding / dates / flags persist across tab switches.
|
||||
let verfahrensablaufWired = false;
|
||||
|
||||
async function activateTab(tab: ProceduresTab): Promise<void> {
|
||||
setActiveTabUI(tab);
|
||||
if (tab === "search") {
|
||||
installOverhaulHost("procedures-panel-search");
|
||||
await mountModeA();
|
||||
return;
|
||||
}
|
||||
if (tab === "wizard") {
|
||||
installOverhaulHost("procedures-panel-wizard");
|
||||
await mountWizard();
|
||||
return;
|
||||
}
|
||||
if (tab === "proceeding") {
|
||||
if (!verfahrensablaufWired) {
|
||||
initVerfahrensablauf();
|
||||
verfahrensablaufWired = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function wireTabs(): void {
|
||||
for (const t of TABS) {
|
||||
const btn = document.getElementById(`procedures-tab-${t}`);
|
||||
if (!btn) continue;
|
||||
btn.addEventListener("click", () => {
|
||||
void activateTab(t);
|
||||
writeTabToUrl(t);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// boot dispatches on the URL: a deep link with `?event=` jumps straight
|
||||
// to the linear result view (the Direkt-suchen tab stays as the visible
|
||||
// context). Otherwise the requested tab — defaulting to "proceeding" —
|
||||
// activates per readTabFromUrl().
|
||||
async function boot(): Promise<void> {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const eventRef = params.get("event") || "";
|
||||
|
||||
if (eventRef) {
|
||||
setActiveTabUI("search");
|
||||
installOverhaulHost("procedures-panel-search");
|
||||
await mountResultView({
|
||||
eventRef,
|
||||
triggerDate: params.get("trigger_date") || undefined,
|
||||
party: params.get("party") || undefined,
|
||||
courtId: params.get("court_id") || undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await activateTab(readTabFromUrl());
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
initI18n();
|
||||
initSidebar();
|
||||
wireTabs();
|
||||
void boot();
|
||||
});
|
||||
@@ -8,8 +8,7 @@
|
||||
// come from ./views/verfahrensablauf-core, which fristenrechner.ts
|
||||
// shares.
|
||||
|
||||
import { initI18n, t, tDyn, getLang, onLangChange } from "./i18n";
|
||||
import { initSidebar } from "./sidebar";
|
||||
import { t, tDyn, getLang, onLangChange } from "./i18n";
|
||||
import {
|
||||
type DeadlineResponse,
|
||||
calculateDeadlines,
|
||||
@@ -1011,10 +1010,14 @@ function initPerspectiveControls() {
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
initI18n();
|
||||
initSidebar();
|
||||
|
||||
// initVerfahrensablauf wires the entire Verfahrensablauf wizard against
|
||||
// whatever DOM is currently present (proceeding-btn buttons,
|
||||
// trigger-date input, flag checkboxes, timeline-container, …).
|
||||
// Re-callable on demand: m/paliad#151 mounts this against the
|
||||
// /tools/procedures "Verfahren wählen" tab the first time it activates.
|
||||
// initI18n() + initSidebar() are NOT included here — both are page-boot
|
||||
// concerns owned by whichever entrypoint hosts the wiring.
|
||||
export function initVerfahrensablauf(): void {
|
||||
document.querySelectorAll<HTMLButtonElement>(".proceeding-btn").forEach((btn) => {
|
||||
btn.addEventListener("click", () => selectProceeding(btn));
|
||||
});
|
||||
@@ -1257,4 +1260,5 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const writeURL = urlProceeding !== "" && !urlHit;
|
||||
selectProceeding(initialBtn, { writeURL });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export function Header({ showLogout }: HeaderProps): string {
|
||||
{showLogout && (
|
||||
<Fragment>
|
||||
<a href="/tools/kostenrechner" className="nav-link" data-i18n="nav.kostenrechner">Kostenrechner</a>
|
||||
<a href="/tools/fristenrechner" className="nav-link" data-i18n="nav.fristenrechner">Fristenrechner</a>
|
||||
<a href="/tools/procedures" className="nav-link" data-i18n="nav.procedures">Verfahren & Fristen</a>
|
||||
<a href="/logout" className="nav-logout" data-i18n="nav.logout">Abmelden</a>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
@@ -177,8 +177,7 @@ export function Sidebar({ currentPath, authenticated = true }: SidebarProps): st
|
||||
brief: calculators first, then reference (Checklisten /
|
||||
Gerichte / Glossar), then content (Links / Downloads). */}
|
||||
{group("nav.group.werkzeuge", "Werkzeuge",
|
||||
navItem("/tools/fristenrechner", ICON_CLOCK, "nav.fristenrechner", "Fristenrechner", currentPath) +
|
||||
navItem("/tools/verfahrensablauf", ICON_BOOK_OPEN, "nav.verfahrensablauf", "Verfahrensablauf", currentPath) +
|
||||
navItem("/tools/procedures", ICON_BOOK_OPEN, "nav.procedures", "Verfahren & Fristen", currentPath) +
|
||||
navItem("/submissions", ICON_FILE_TEXT, "nav.submissions", "Schriftsätze", currentPath) +
|
||||
navItem("/tools/kostenrechner", ICON_CALC, "nav.kostenrechner", "Kostenrechner", currentPath) +
|
||||
navItem("/tools/gebuehrentabellen", ICON_TABLE, "nav.gebuehrentabellen", "Gebührentabellen", currentPath) +
|
||||
|
||||
293
frontend/src/components/VerfahrensablaufBody.tsx
Normal file
293
frontend/src/components/VerfahrensablaufBody.tsx
Normal file
@@ -0,0 +1,293 @@
|
||||
import { h } from "../jsx";
|
||||
|
||||
interface ProceedingDef {
|
||||
code: string;
|
||||
i18nKey: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
function proceedingBtn(p: ProceedingDef): string {
|
||||
return (
|
||||
<button type="button" className="proceeding-btn" data-code={p.code}>
|
||||
<strong data-i18n={p.i18nKey}>{p.name}</strong>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// Slice B1 (m/paliad#124 §18.1): the 3 separate Berufung tiles
|
||||
// (upc.apl.merits / upc.apl.cost / upc.apl.order) collapse into ONE
|
||||
// unified "Berufung" tile (upc.apl). After picking it, the user
|
||||
// selects which decision the appeal is directed AT via the
|
||||
// .appeal-target-row chip group below — the engine then filters
|
||||
// rules whose applies_to_target contains the picked slug.
|
||||
const UPC_TYPES: ProceedingDef[] = [
|
||||
{ code: "upc.inf.cfi", i18nKey: "deadlines.upc.inf.cfi", name: "Verletzungsverfahren" },
|
||||
{ code: "upc.rev.cfi", i18nKey: "deadlines.upc.rev.cfi", name: "Nichtigkeitsklage" },
|
||||
{ code: "upc.ccr.cfi", i18nKey: "deadlines.upc.ccr.cfi", name: "Widerklage auf Nichtigkeit" },
|
||||
{ code: "upc.pi.cfi", i18nKey: "deadlines.upc.pi.cfi", name: "Einstw. Maßnahmen" },
|
||||
{ code: "upc.apl.unified", i18nKey: "deadlines.upc.apl.unified", name: "Berufung" },
|
||||
{ code: "upc.dmgs.cfi", i18nKey: "deadlines.upc.dmgs.cfi", name: "Schadensbemessung" },
|
||||
{ code: "upc.disc.cfi", i18nKey: "deadlines.upc.disc.cfi", name: "Bucheinsicht" },
|
||||
];
|
||||
|
||||
const DE_INF_TYPES: ProceedingDef[] = [
|
||||
{ code: "de.inf.lg", i18nKey: "deadlines.de.inf.lg", name: "LG (1. Instanz)" },
|
||||
{ code: "de.inf.olg", i18nKey: "deadlines.de.inf.olg", name: "OLG (Berufung)" },
|
||||
{ code: "de.inf.bgh", i18nKey: "deadlines.de.inf.bgh", name: "BGH (Revision / NZB)" },
|
||||
];
|
||||
|
||||
const DE_NULL_TYPES: ProceedingDef[] = [
|
||||
{ code: "de.null.bpatg", i18nKey: "deadlines.de.null.bpatg", name: "BPatG (1. Instanz)" },
|
||||
{ code: "de.null.bgh", i18nKey: "deadlines.de.null.bgh", name: "BGH (Berufung)" },
|
||||
];
|
||||
|
||||
const EPA_TYPES: ProceedingDef[] = [
|
||||
{ code: "epa.opp.opd", i18nKey: "deadlines.epa.opp.opd", name: "Einspruchsverfahren" },
|
||||
{ code: "epa.opp.boa", i18nKey: "deadlines.epa.opp.boa", name: "Beschwerdeverfahren" },
|
||||
{ code: "epa.grant.exa", i18nKey: "deadlines.epa.grant.exa", name: "EP-Erteilungsverfahren" },
|
||||
];
|
||||
|
||||
const DPMA_TYPES: ProceedingDef[] = [
|
||||
{ code: "dpma.opp.dpma", i18nKey: "deadlines.dpma.opp.dpma", name: "Einspruch DPMA" },
|
||||
{ code: "dpma.appeal.bpatg", i18nKey: "deadlines.dpma.appeal.bpatg", name: "Beschwerde BPatG (DPMA)" },
|
||||
{ code: "dpma.appeal.bgh", i18nKey: "deadlines.dpma.appeal.bgh", name: "Rechtsbeschwerde BGH" },
|
||||
];
|
||||
|
||||
// Shared Verfahrensablauf wizard body. Renders the proceeding picker,
|
||||
// perspective + date inputs, scenario flag rows, detail-mode toggle,
|
||||
// view toggle, and the timeline-container that client/verfahrensablauf.ts
|
||||
// (via initVerfahrensablauf()) wires against. Used by both
|
||||
// /tools/verfahrensablauf (legacy) and /tools/procedures (unified).
|
||||
export function VerfahrensablaufBody({ todayIso }: { todayIso: string }): string {
|
||||
return (
|
||||
<div className="fristen-wizard" id="verfahrensablauf-wizard" data-mode="procedure">
|
||||
<div className="wizard-step" id="step-1">
|
||||
<h3 className="wizard-step-label">
|
||||
<span className="step-number">1</span>
|
||||
<span data-i18n="deadlines.step1">Verfahrensart wählen</span>
|
||||
</h3>
|
||||
|
||||
<div className="proceeding-group" data-forum="upc">
|
||||
<h4 data-i18n="deadlines.upc">UPC</h4>
|
||||
<div className="proceeding-btns">
|
||||
{UPC_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="proceeding-group" data-forum="de">
|
||||
<h4 data-i18n="deadlines.de">Deutsche Gerichte</h4>
|
||||
<div className="proceeding-subgroup">
|
||||
<h5 className="proceeding-subgroup-heading" data-i18n="deadlines.de.group.inf">Verletzungsverfahren</h5>
|
||||
<div className="proceeding-btns">
|
||||
{DE_INF_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="proceeding-subgroup">
|
||||
<h5 className="proceeding-subgroup-heading" data-i18n="deadlines.de.group.null">Nichtigkeitsverfahren</h5>
|
||||
<div className="proceeding-btns">
|
||||
{DE_NULL_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="proceeding-group" data-forum="epa">
|
||||
<h4 data-i18n="deadlines.epa">EPA</h4>
|
||||
<div className="proceeding-btns">
|
||||
{EPA_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="proceeding-group" data-forum="dpma">
|
||||
<h4 data-i18n="deadlines.dpma">DPMA</h4>
|
||||
<div className="proceeding-btns">
|
||||
{DPMA_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="proceeding-summary" id="proceeding-summary" style="display:none" role="group">
|
||||
<span className="proceeding-summary-label" data-i18n="deadlines.proceeding.selected">Verfahren:</span>
|
||||
<strong className="proceeding-summary-name" id="proceeding-summary-name">—</strong>
|
||||
<button type="button" className="proceeding-summary-reselect" id="proceeding-summary-reselect"
|
||||
data-i18n="deadlines.proceeding.reselect">
|
||||
Anderes Verfahren wählen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="wizard-step" id="step-2" style="display:none">
|
||||
<h3 className="wizard-step-label">
|
||||
<span className="step-number">2</span>
|
||||
<span data-i18n="deadlines.step2.perspective">Perspektive und Datum</span>
|
||||
</h3>
|
||||
|
||||
<div className="verfahrensablauf-perspective" id="verfahrensablauf-perspective">
|
||||
<div className="verfahrensablauf-perspective-row" id="side-row">
|
||||
<span className="date-label" data-i18n="deadlines.side.label">Seite:</span>
|
||||
<div className="side-radio-cluster" id="side-radio-cluster">
|
||||
<div className="fristen-view-toggle" role="radiogroup" aria-label="Side">
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="side" value="claimant" />
|
||||
<span data-i18n="deadlines.side.claimant">Klägerseite</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="side" value="defendant" />
|
||||
<span data-i18n="deadlines.side.defendant">Beklagtenseite</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="side" value="" checked />
|
||||
<span data-i18n="deadlines.side.undefined">Nicht festgelegt</span>
|
||||
</label>
|
||||
</div>
|
||||
<span className="side-hint" id="side-hint"
|
||||
data-i18n="deadlines.side.hint">
|
||||
Wählen Sie eine Seite, um die Spalten zu fokussieren.
|
||||
</span>
|
||||
</div>
|
||||
<div className="side-chip" id="side-chip" style="display:none">
|
||||
<span className="side-chip-tag" data-i18n="deadlines.side.from_project">Aus Akte:</span>
|
||||
<strong className="side-chip-value" id="side-chip-value">—</strong>
|
||||
<button type="button" className="side-chip-override" id="side-chip-override"
|
||||
data-i18n="deadlines.side.override">
|
||||
Andere Seite wählen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="verfahrensablauf-perspective-row" id="appeal-target-row" style="display:none">
|
||||
<span className="date-label" data-i18n="deadlines.appeal_target.label">Worauf richtet sich die Berufung?</span>
|
||||
<div className="fristen-view-toggle" role="radiogroup" aria-label="Appeal target">
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="appeal-target" value="endentscheidung" checked />
|
||||
<span data-i18n="deadlines.appeal_target.endentscheidung">Endentscheidung</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="appeal-target" value="kostenentscheidung" />
|
||||
<span data-i18n="deadlines.appeal_target.kostenentscheidung">Kostenentscheidung</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="appeal-target" value="anordnung" />
|
||||
<span data-i18n="deadlines.appeal_target.anordnung">Anordnung</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="appeal-target" value="schadensbemessung" />
|
||||
<span data-i18n="deadlines.appeal_target.schadensbemessung">Schadensbemessung</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="appeal-target" value="bucheinsicht" />
|
||||
<span data-i18n="deadlines.appeal_target.bucheinsicht">Bucheinsicht</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="verfahrensablauf-perspective-row" id="show-hidden-row" style="display:none">
|
||||
<label className="fristen-view-option">
|
||||
<input type="checkbox" id="show-hidden-toggle" />
|
||||
<span data-i18n="choices.show_hidden.label">Ausgeblendete anzeigen</span>
|
||||
</label>
|
||||
<span className="show-hidden-count" id="show-hidden-count" aria-live="polite"> </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="verfahrensablauf-step2-divider" aria-hidden="true"></div>
|
||||
|
||||
<div className="date-input-group">
|
||||
<div className="date-field-row">
|
||||
<span className="date-label" data-i18n="deadlines.trigger.event">Auslösendes Ereignis:</span>
|
||||
<span id="trigger-event" className="trigger-event-name">—</span>
|
||||
</div>
|
||||
<div className="date-field-row">
|
||||
<label htmlFor="trigger-date" className="date-label" data-i18n="deadlines.trigger.date">Datum:</label>
|
||||
<input type="date" id="trigger-date" className="date-input" value={todayIso} />
|
||||
</div>
|
||||
<div className="date-field-row" id="court-picker-row" style="display:none">
|
||||
<label htmlFor="court-picker" className="date-label" data-i18n="deadlines.court.label">Gericht:</label>
|
||||
<select id="court-picker" className="date-input"></select>
|
||||
</div>
|
||||
<div className="date-field-row" id="ccr-flag-row" style="display:none">
|
||||
<label className="date-label">
|
||||
<input type="checkbox" id="ccr-flag" />
|
||||
<span data-i18n="deadlines.flag.ccr">Mit Widerklage auf Nichtigkeit</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="date-field-row date-field-row--nested" id="inf-amend-flag-row" style="display:none">
|
||||
<label className="date-label">
|
||||
<input type="checkbox" id="inf-amend-flag" />
|
||||
<span data-i18n="deadlines.flag.inf_amend">Mit Antrag auf Patentänderung (R.30)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="date-field-row" id="rev-amend-flag-row" style="display:none">
|
||||
<label className="date-label">
|
||||
<input type="checkbox" id="rev-amend-flag" />
|
||||
<span data-i18n="deadlines.flag.rev_amend">Mit Antrag auf Patentänderung (R.49.2.a)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="date-field-row" id="rev-cci-flag-row" style="display:none">
|
||||
<label className="date-label">
|
||||
<input type="checkbox" id="rev-cci-flag" />
|
||||
<span data-i18n="deadlines.flag.rev_cci">Mit Verletzungswiderklage (R.49.2.b)</span>
|
||||
</label>
|
||||
</div>
|
||||
<button type="button" id="calculate-btn" className="calculate-btn" data-i18n="deadlines.calculate">
|
||||
Fristen berechnen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="wizard-step" id="step-3" style="display:none">
|
||||
<h3 className="wizard-step-label">
|
||||
<span className="step-number">3</span>
|
||||
<span data-i18n="deadlines.step3">Ergebnis</span>
|
||||
</h3>
|
||||
|
||||
<div className="verfahrensablauf-detail-toggle" id="verfahrensablauf-detail-toggle"
|
||||
role="radiogroup" aria-label="Detail">
|
||||
<span className="fristen-view-label" data-i18n="deadlines.detail.label">Anzeige:</span>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="detail-mode" value="mandatory_only" />
|
||||
<span data-i18n="deadlines.detail.mandatory_only">Nur Pflicht</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="detail-mode" value="selected" checked />
|
||||
<span data-i18n="deadlines.detail.selected">Gewählt</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="detail-mode" value="all_options" />
|
||||
<span data-i18n="deadlines.detail.all_options">Alle Optionen</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="fristen-view-toggle" id="fristen-view-toggle" role="radiogroup" aria-label="Ansicht">
|
||||
<span className="fristen-view-label" data-i18n="deadlines.view.label">Ansicht:</span>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="fristen-view" value="columns" checked />
|
||||
<span data-i18n="deadlines.view.columns">Spalten</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="fristen-view" value="timeline" />
|
||||
<span data-i18n="deadlines.view.timeline">Zeitstrahl</span>
|
||||
</label>
|
||||
<label className="fristen-notes-option">
|
||||
<input type="checkbox" id="fristen-notes-show" />
|
||||
<span data-i18n="deadlines.notes.show">Hinweise anzeigen</span>
|
||||
</label>
|
||||
<label className="fristen-notes-option">
|
||||
<input type="checkbox" id="verfahrensablauf-durations-show" />
|
||||
<span data-i18n="deadlines.durations.show">Dauern anzeigen</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="timeline-container">
|
||||
</div>
|
||||
|
||||
<div className="fristen-result-actions">
|
||||
<button type="button" id="fristen-print-btn" className="print-btn" style="display:none">
|
||||
<svg className="print-btn-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<polyline points="6 9 6 2 18 2 18 9"></polyline>
|
||||
<path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path>
|
||||
<rect x="6" y="14" width="12" height="8"></rect>
|
||||
</svg>
|
||||
<span data-i18n="deadlines.print">Drucken</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,666 +0,0 @@
|
||||
import { h } from "./jsx";
|
||||
import { Sidebar } from "./components/Sidebar";
|
||||
import { PaliadinWidget } from "./components/PaliadinWidget";
|
||||
import { BottomNav } from "./components/BottomNav";
|
||||
import { Footer } from "./components/Footer";
|
||||
import { PWAHead } from "./components/PWAHead";
|
||||
|
||||
interface ProceedingDef {
|
||||
code: string;
|
||||
i18nKey: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
function proceedingBtn(p: ProceedingDef): string {
|
||||
return (
|
||||
<button type="button" className="proceeding-btn" data-code={p.code}>
|
||||
<strong data-i18n={p.i18nKey}>{p.name}</strong>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// Quick-pick chip definition. Each chip targets ONE deadline_concepts
|
||||
// slug — clicking sets the search query to the concept's name in the
|
||||
// active language so trigram search lands on the right concept card.
|
||||
// Single source of truth for both fork-shortcut and B2-search-bar
|
||||
// chip rows. Dedup invariant: no two chips share a slug. Label flips
|
||||
// per language via the chip wiring in client/fristenrechner.ts.
|
||||
interface QuickChip {
|
||||
slug: string;
|
||||
name_de: string;
|
||||
name_en: string;
|
||||
}
|
||||
|
||||
const QUICK_CHIPS: QuickChip[] = [
|
||||
{ slug: "statement-of-defence", name_de: "Klageerwiderung", name_en: "Statement of Defence" },
|
||||
{ slug: "notice-of-appeal", name_de: "Berufungsschrift", name_en: "Notice of Appeal" },
|
||||
{ slug: "opposition", name_de: "Einspruchsfrist", name_en: "Opposition" },
|
||||
{ slug: "reply-to-defence", name_de: "Replik", name_en: "Reply to Defence" },
|
||||
{ slug: "nichtzulassungsbeschwerde", name_de: "Nichtzulassungsbeschwerde", name_en: "Non-admission Appeal (NZB)" },
|
||||
{ slug: "application-for-determination-of-damages",name_de: "Antrag auf Schadensbemessung", name_en: "Application for Determination of Damages" },
|
||||
{ slug: "wiedereinsetzung", name_de: "Wiedereinsetzung", name_en: "Re-establishment of Rights" },
|
||||
];
|
||||
|
||||
function quickChip(c: QuickChip): string {
|
||||
return (
|
||||
<button type="button" className="fristen-search-chip"
|
||||
data-chip-slug={c.slug}
|
||||
data-chip-name-de={c.name_de}
|
||||
data-chip-name-en={c.name_en}
|
||||
data-q={c.name_de}>
|
||||
{c.name_de}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
const UPC_TYPES: ProceedingDef[] = [
|
||||
{ code: "upc.inf.cfi", i18nKey: "deadlines.upc.inf.cfi", name: "Verletzungsverfahren" },
|
||||
{ code: "upc.rev.cfi", i18nKey: "deadlines.upc.rev.cfi", name: "Nichtigkeitsklage" },
|
||||
{ code: "upc.ccr.cfi", i18nKey: "deadlines.upc.ccr.cfi", name: "Widerklage auf Nichtigkeit" },
|
||||
{ code: "upc.pi.cfi", i18nKey: "deadlines.upc.pi.cfi", name: "Einstw. Ma\u00dfnahmen" },
|
||||
{ code: "upc.apl.merits", i18nKey: "deadlines.upc.apl.merits", name: "Berufung" },
|
||||
{ code: "upc.dmgs.cfi", i18nKey: "deadlines.upc.dmgs.cfi", name: "Schadensbemessung" },
|
||||
{ code: "upc.disc.cfi", i18nKey: "deadlines.upc.disc.cfi", name: "Bucheinsicht" },
|
||||
{ code: "upc.apl.cost", i18nKey: "deadlines.upc.apl.cost", name: "Berufung Kosten" },
|
||||
{ code: "upc.apl.order", i18nKey: "deadlines.upc.apl.order", name: "Berufung Anordnungen" },
|
||||
];
|
||||
|
||||
// DE proceedings split by type (Verletzung / Nichtigkeit) per m's
|
||||
// 2026-05-18 ask. Labels are parallel: <court> (<procedural role>),
|
||||
// so a user scanning the picker sees the instance-and-role at a glance
|
||||
// without one tile reading "Berufung OLG" and another "Nichtigkeits-
|
||||
// verfahren". Sub-group headers convey the type grouping. Combined-
|
||||
// timeline behaviour (LG→OLG→BGH as one calc) is filed as m/paliad#41.
|
||||
const DE_INF_TYPES: ProceedingDef[] = [
|
||||
{ code: "de.inf.lg", i18nKey: "deadlines.de.inf.lg", name: "LG (1. Instanz)" },
|
||||
{ code: "de.inf.olg", i18nKey: "deadlines.de.inf.olg", name: "OLG (Berufung)" },
|
||||
{ code: "de.inf.bgh", i18nKey: "deadlines.de.inf.bgh", name: "BGH (Revision / NZB)" },
|
||||
];
|
||||
|
||||
const DE_NULL_TYPES: ProceedingDef[] = [
|
||||
{ code: "de.null.bpatg", i18nKey: "deadlines.de.null.bpatg", name: "BPatG (1. Instanz)" },
|
||||
{ code: "de.null.bgh", i18nKey: "deadlines.de.null.bgh", name: "BGH (Berufung)" },
|
||||
];
|
||||
|
||||
const EPA_TYPES: ProceedingDef[] = [
|
||||
{ code: "epa.opp.opd", i18nKey: "deadlines.epa.opp.opd", name: "Einspruchsverfahren" },
|
||||
{ code: "epa.opp.boa", i18nKey: "deadlines.epa.opp.boa", name: "Beschwerdeverfahren" },
|
||||
{ code: "epa.grant.exa", i18nKey: "deadlines.epa.grant.exa", name: "EP-Erteilungsverfahren" },
|
||||
];
|
||||
|
||||
const DPMA_TYPES: ProceedingDef[] = [
|
||||
{ code: "dpma.opp.dpma", i18nKey: "deadlines.dpma.opp.dpma", name: "Einspruch DPMA" },
|
||||
{ code: "dpma.appeal.bpatg", i18nKey: "deadlines.dpma.appeal.bpatg", name: "Beschwerde BPatG (DPMA)" },
|
||||
{ code: "dpma.appeal.bgh", i18nKey: "deadlines.dpma.appeal.bgh", name: "Rechtsbeschwerde BGH" },
|
||||
];
|
||||
|
||||
export function renderFristenrechner(): string {
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
|
||||
return "<!DOCTYPE html>" + (
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<meta name="theme-color" content="#BFF355" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<PWAHead />
|
||||
<title data-i18n="deadlines.title">Fristenrechner — Paliad</title>
|
||||
<link rel="stylesheet" href="/assets/global.css" />
|
||||
</head>
|
||||
<body className="has-sidebar">
|
||||
<Sidebar currentPath="/tools/fristenrechner" />
|
||||
<BottomNav currentPath="/tools/fristenrechner" />
|
||||
|
||||
<main>
|
||||
<section className="tool-page">
|
||||
<div className="container">
|
||||
<div className="tool-header">
|
||||
<h1 data-i18n="deadlines.heading">Fristenrechner</h1>
|
||||
<p className="tool-subtitle" data-i18n="deadlines.subtitle">
|
||||
Berechnung von Verfahrensfristen für UPC-, deutsche und EPA-Verfahren.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* t-paliad-323 Slice S2 — overhaul result view mount root.
|
||||
Hidden by default; the client module shows this and hides
|
||||
the legacy panels when `?overhaul=1` is present in the
|
||||
URL. Deep-linkable on its own via
|
||||
`?overhaul=1&event=<code>&trigger_date=…`. Mode A (S3)
|
||||
and Mode B wizard (S4) will land users on this surface
|
||||
once they identify a trigger procedural_event. */}
|
||||
<div className="fristen-overhaul-root" id="fristen-overhaul-root" hidden></div>
|
||||
|
||||
{/* m's 2026-05-08 18:08 Determinator redesign — Step 1: pick the
|
||||
Akte (project) that scopes the rest of the flow. Filtered
|
||||
list of visible projects + "Neue Akte anlegen" link +
|
||||
four ad-hoc explore-mode chips for users who just want to
|
||||
look up a rule without saving anywhere. */}
|
||||
<div className="fristen-step1" id="fristen-step1" role="group" aria-label="Akte picker">
|
||||
<h2 className="fristen-step-heading" data-i18n="deadlines.step1.heading">
|
||||
Schritt 1 — Welche Akte?
|
||||
</h2>
|
||||
<div className="fristen-step1-search-row">
|
||||
<svg className="fristen-search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<circle cx="11" cy="11" r="7"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
<input type="search" id="fristen-akte-search"
|
||||
className="fristen-akte-search" autocomplete="off"
|
||||
data-i18n-placeholder="deadlines.step1.search.placeholder"
|
||||
placeholder="Akte suchen…" />
|
||||
</div>
|
||||
<ul className="fristen-akte-list" id="fristen-akte-list" role="listbox" aria-label="Akten"></ul>
|
||||
|
||||
<div className="fristen-step1-divider">
|
||||
<span data-i18n="deadlines.step1.divider.new">oder eine neue Akte</span>
|
||||
</div>
|
||||
{/* return-bounce: projects-new.ts honours ?return= and
|
||||
redirects back to /tools/fristenrechner?project=<new_uuid>
|
||||
so the new Akte preselects itself in Step 1. */}
|
||||
<a href="/projects/new?return=/tools/fristenrechner" className="fristen-step1-new" id="fristen-step1-new"
|
||||
data-i18n="deadlines.step1.new.cta">
|
||||
+ Neue Akte anlegen
|
||||
</a>
|
||||
|
||||
<div className="fristen-step1-divider">
|
||||
<span data-i18n="deadlines.step1.divider.adhoc">oder ad-hoc, ohne Akte</span>
|
||||
</div>
|
||||
<div className="fristen-adhoc-chips" role="group" aria-label="Ad-hoc proceeding">
|
||||
<button type="button" className="fristen-adhoc-chip" data-ad-hoc="upc"
|
||||
data-i18n="deadlines.step1.adhoc.upc">
|
||||
UPC proceeding
|
||||
</button>
|
||||
<button type="button" className="fristen-adhoc-chip" data-ad-hoc="de"
|
||||
data-i18n="deadlines.step1.adhoc.de">
|
||||
DE proceeding
|
||||
</button>
|
||||
<button type="button" className="fristen-adhoc-chip" data-ad-hoc="epa"
|
||||
data-i18n="deadlines.step1.adhoc.epa">
|
||||
EPA proceeding
|
||||
</button>
|
||||
<button type="button" className="fristen-adhoc-chip" data-ad-hoc="dpma"
|
||||
data-i18n="deadlines.step1.adhoc.dpma">
|
||||
DPMA proceeding
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 1 collapsed summary, shown after a pick. Mirrors the
|
||||
proceeding-summary collapse pattern from 097e21c. */}
|
||||
<div className="fristen-step1-summary" id="fristen-step1-summary" style="display:none" role="group">
|
||||
<span className="fristen-step1-summary-label" data-i18n="deadlines.step1.selected">Akte:</span>
|
||||
<strong className="fristen-step1-summary-name" id="fristen-step1-summary-name">—</strong>
|
||||
<span className="fristen-step1-summary-meta" id="fristen-step1-summary-meta"></span>
|
||||
<button type="button" className="fristen-step1-summary-reselect" id="fristen-step1-summary-reselect"
|
||||
data-i18n="deadlines.step1.reselect">
|
||||
Andere Akte
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Step 2 — Do / Happened bifurcation. Hidden until Step 1 is
|
||||
satisfied. Click on a card routes to the existing Pathway A
|
||||
(Verfahrensablauf wizard) or Pathway B (cascade) shells —
|
||||
we keep the routing primitive in showPathway()/showBMode(). */}
|
||||
<div className="fristen-step2" id="fristen-step2" hidden>
|
||||
<h2 className="fristen-step-heading" data-i18n="deadlines.step2.heading">
|
||||
Schritt 2 — Was möchten Sie tun?
|
||||
</h2>
|
||||
<div className="fristen-step2-cards">
|
||||
<button type="button" className="fristen-step2-card" data-action="file" id="fristen-step2-file">
|
||||
<span className="fristen-step2-card-icon" aria-hidden="true">✏️</span>
|
||||
<span className="fristen-step2-card-title" data-i18n="deadlines.step2.file.title">
|
||||
Etwas einreichen
|
||||
</span>
|
||||
<span className="fristen-step2-card-desc" data-i18n="deadlines.step2.file.desc">
|
||||
Outgoing — eine Frist tritt aus eigener Handlung ein.
|
||||
</span>
|
||||
</button>
|
||||
<button type="button" className="fristen-step2-card" data-action="happened" id="fristen-step2-happened">
|
||||
<span className="fristen-step2-card-icon" aria-hidden="true">📥</span>
|
||||
<span className="fristen-step2-card-title" data-i18n="deadlines.step2.happened.title">
|
||||
Etwas ist passiert
|
||||
</span>
|
||||
<span className="fristen-step2-card-desc" data-i18n="deadlines.step2.happened.desc">
|
||||
Incoming — ein Ereignis hat eine Frist ausgelöst.
|
||||
</span>
|
||||
</button>
|
||||
{/* t-paliad-179 Slice 1: the third "Verfahrensablauf
|
||||
einsehen" card retired — abstract-browse intent now
|
||||
owns its own route at /tools/verfahrensablauf. */}
|
||||
</div>
|
||||
<div className="fristen-step2-shortcut">
|
||||
<div className="fristen-pathway-fork-shortcut-label" data-i18n="deadlines.pathway.shortcut.label">
|
||||
oder direkt zu einer Frist springen:
|
||||
</div>
|
||||
<div className="fristen-search-chips" id="fristen-fork-chips" role="group" aria-label="Schnellzugriff">
|
||||
{QUICK_CHIPS.map((c) => quickChip(c))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pathway B container — search bar relocates here from the page top.
|
||||
Mode toggle (B1 tree / B2 filter) sits above the panels.
|
||||
Hidden until ?path=b. */}
|
||||
<div className="fristen-pathway-shell" id="fristen-pathway-b" data-path="b" hidden>
|
||||
<button type="button" className="fristen-pathway-back" id="fristen-pathway-b-back">
|
||||
<span aria-hidden="true">←</span>{" "}
|
||||
<span data-i18n="deadlines.pathway.back">zurück zur Auswahl</span>
|
||||
</button>
|
||||
<h2 className="fristen-pathway-heading">
|
||||
<span aria-hidden="true">📅</span>{" "}
|
||||
<span data-i18n="deadlines.pathway.b.title">Frist eintragen aufgrund Ereignis</span>
|
||||
</h2>
|
||||
|
||||
{/* B1 panel — row-stack cascade.
|
||||
`#fristen-row-stack` hosts the perspective / inbox /
|
||||
cascade rows (t-paliad-180 Slice 1; t-paliad-197 Slice 2
|
||||
added project-driven prefills + auto-walk). The
|
||||
stack-header above carries the inline-search trigger
|
||||
(t-paliad-198 Slice 3 — clicking expands
|
||||
`#fristen-row-search-panel` over the row stack instead
|
||||
of routing to the legacy B2 surface) and the reset link.
|
||||
`#fristen-b1-results` is unchanged — it renders concept
|
||||
cards for both cascade-narrowing AND inline-search
|
||||
results, so users see the same card layout regardless
|
||||
of how they reached a deadline rule. */}
|
||||
<div className="fristen-b1-panel" id="fristen-b1-panel" data-mode="tree" hidden>
|
||||
<div className="fristen-row-stack-header" id="fristen-row-stack-header">
|
||||
<button type="button" className="fristen-row-search-link" id="fristen-row-search-link"
|
||||
data-i18n-title="deadlines.row.search.link.title"
|
||||
aria-expanded="false"
|
||||
aria-controls="fristen-row-search-panel"
|
||||
title="Direkt nach einer Frist suchen">
|
||||
<span aria-hidden="true">🔍</span>{" "}
|
||||
<span data-i18n="deadlines.row.search.link">Direkt suchen</span>
|
||||
</button>
|
||||
<button type="button" className="fristen-row-reset-link" id="fristen-row-reset"
|
||||
data-i18n-title="deadlines.row.reset.title"
|
||||
title="Pfad zurücksetzen — alle Cascade-Antworten verwerfen">
|
||||
<span aria-hidden="true">↺</span>{" "}
|
||||
<span data-i18n="deadlines.row.reset">Pfad zurücksetzen</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Inline search overlay (t-paliad-198 Slice 3). Hidden by
|
||||
default; the search icon-button in the stack header
|
||||
toggles it open / closed. While open, the row stack is
|
||||
hidden and the search input drives `#fristen-b1-results`
|
||||
directly — same surface the cascade leaf populates so
|
||||
the user sees one consistent concept-card list. */}
|
||||
<div className="fristen-row-search-panel" id="fristen-row-search-panel" hidden role="search">
|
||||
<button type="button" className="fristen-row-search-panel-back" id="fristen-row-search-panel-back"
|
||||
data-i18n-title="deadlines.row.search.panel.back.title"
|
||||
title="Zurück zum Entscheidungsbaum">
|
||||
<span aria-hidden="true">←</span>{" "}
|
||||
<span data-i18n="deadlines.row.search.panel.back">Zurück zum Entscheidungsbaum</span>
|
||||
</button>
|
||||
<div className="fristen-row-search-panel-input-wrap">
|
||||
<svg className="fristen-row-search-panel-icon" width="18" height="18" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" aria-hidden="true">
|
||||
<circle cx="11" cy="11" r="7"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
<input
|
||||
type="search"
|
||||
id="fristen-row-search-panel-input"
|
||||
className="fristen-row-search-panel-input"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
data-i18n-placeholder="deadlines.row.search.panel.placeholder"
|
||||
placeholder="Frist suchen…"
|
||||
aria-label="Frist suchen"
|
||||
/>
|
||||
<button type="button" className="fristen-row-search-panel-clear" id="fristen-row-search-panel-clear"
|
||||
data-i18n-title="deadlines.row.search.panel.clear" title="Eingabe leeren" hidden>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="fristen-row-stack" id="fristen-row-stack" aria-live="polite"></div>
|
||||
<div className="fristen-b1-results" id="fristen-b1-results" aria-live="polite"></div>
|
||||
</div>
|
||||
|
||||
{/* B2 panel — search bar + chips + concept-card results.
|
||||
The search input + chips + results host live here so
|
||||
fristenrechner.ts can drive both Phase D (today) and the
|
||||
B1↔B2 state-share in Phase D (forum filter). */}
|
||||
<div className="fristen-b2-panel" id="fristen-b2-panel" data-mode="filter">
|
||||
<div className="fristen-search">
|
||||
<label htmlFor="fristen-search-input" className="visually-hidden" data-i18n="deadlines.search.label">Frist suchen</label>
|
||||
<div className="fristen-search-row">
|
||||
<svg className="fristen-search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<circle cx="11" cy="11" r="7"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
<input
|
||||
type="search"
|
||||
id="fristen-search-input"
|
||||
className="fristen-search-input"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
data-i18n-placeholder="deadlines.search.placeholder"
|
||||
placeholder="Klageerwiderung, RoP 23, § 82, Wiedereinsetzung…"
|
||||
/>
|
||||
<button type="button" id="fristen-search-clear" className="fristen-search-clear" aria-label="Suche leeren" data-i18n-aria-label="deadlines.search.clear" hidden>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div className="fristen-search-chips" id="fristen-search-chips" role="group" aria-label="Schnellzugriff">
|
||||
<span className="fristen-search-chips-label" data-i18n="deadlines.search.chips.label">Schnellzugriff:</span>
|
||||
{QUICK_CHIPS.map((c) => quickChip(c))}
|
||||
</div>
|
||||
{/* Forum filter row — populated by Phase D. */}
|
||||
<div className="fristen-forum-filter" id="fristen-forum-filter" hidden>
|
||||
<span className="fristen-forum-filter-label" data-i18n="deadlines.filter.forum.label">Gericht / System:</span>
|
||||
<div className="fristen-forum-chips" id="fristen-forum-chips"></div>
|
||||
</div>
|
||||
<div id="fristen-search-results" className="fristen-search-results" aria-live="polite"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 3a — outgoing-intent chooser. Reached when the user
|
||||
picks "Etwas einreichen" on Step 2. Three options per
|
||||
m's 2026-05-08 18:09 spec: File (drives the Pathway A
|
||||
wizard), Draft (future drafting surface; v1
|
||||
placeholder), Enter (routes to the existing manual-
|
||||
create form). */}
|
||||
<div className="fristen-pathway-shell" id="fristen-step3a" data-path="outgoing" hidden>
|
||||
<button type="button" className="fristen-pathway-back" id="fristen-step3a-back">
|
||||
<span aria-hidden="true">←</span>{" "}
|
||||
<span data-i18n="deadlines.step3a.back">zurück zur Auswahl</span>
|
||||
</button>
|
||||
<h2 className="fristen-pathway-heading">
|
||||
<span aria-hidden="true">✏️</span>{" "}
|
||||
<span data-i18n="deadlines.step3a.heading">Was möchten Sie einreichen?</span>
|
||||
</h2>
|
||||
<div className="fristen-step2-cards">
|
||||
<button type="button" className="fristen-step2-card" id="fristen-step3a-file" data-action="file">
|
||||
<span className="fristen-step2-card-icon" aria-hidden="true">📝</span>
|
||||
<span className="fristen-step2-card-title" data-i18n="deadlines.step3a.file.title">
|
||||
Schriftsatz einreichen
|
||||
</span>
|
||||
<span className="fristen-step2-card-desc" data-i18n="deadlines.step3a.file.desc">
|
||||
Verfahrensablauf laden — Frist berechnen und zur Akte hinzufügen.
|
||||
</span>
|
||||
</button>
|
||||
<button type="button" className="fristen-step2-card fristen-step2-card--soon" id="fristen-step3a-draft" data-action="draft" disabled
|
||||
data-i18n-title="deadlines.step3a.soon">
|
||||
<span className="fristen-step2-card-icon" aria-hidden="true">🖉</span>
|
||||
<span className="fristen-step2-card-title" data-i18n="deadlines.step3a.draft.title">
|
||||
Schriftsatz entwerfen
|
||||
</span>
|
||||
<span className="fristen-step2-card-desc" data-i18n="deadlines.step3a.draft.desc">
|
||||
Vorbereitung — später mit Drafting-Surface verknüpft.
|
||||
</span>
|
||||
<span className="fristen-step2-card-soon" data-i18n="deadlines.step3a.soon">kommt bald</span>
|
||||
</button>
|
||||
<button type="button" className="fristen-step2-card" id="fristen-step3a-enter" data-action="enter">
|
||||
<span className="fristen-step2-card-icon" aria-hidden="true">💾</span>
|
||||
<span className="fristen-step2-card-title" data-i18n="deadlines.step3a.enter.title">
|
||||
Frist manuell erfassen
|
||||
</span>
|
||||
<span className="fristen-step2-card-desc" data-i18n="deadlines.step3a.enter.desc">
|
||||
Direkt eintragen — bereits bekanntes Datum / bekannter Typ.
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pathway A container — wraps the existing wizard.
|
||||
Hidden until ?path=a. */}
|
||||
<div className="fristen-pathway-shell" id="fristen-pathway-a" data-path="a" hidden>
|
||||
<button type="button" className="fristen-pathway-back" id="fristen-pathway-a-back">
|
||||
<span aria-hidden="true">←</span>{" "}
|
||||
<span data-i18n="deadlines.pathway.back">zurück zur Auswahl</span>
|
||||
</button>
|
||||
<h2 className="fristen-pathway-heading">
|
||||
<span aria-hidden="true">📖</span>{" "}
|
||||
<span data-i18n="deadlines.pathway.a.title">Verfahrensablauf informieren</span>
|
||||
</h2>
|
||||
|
||||
{/* v3: legacy mode tabs retired (m's spec lock §10 Q1, 2026-05-05).
|
||||
Pathway A is Verfahrensablauf-only; trigger-event drill-in
|
||||
surfaces via concept-card pills with ?path=a&trigger=N URL,
|
||||
which resurfaces mode-event-panel programmatically below. */}
|
||||
<div className="fristen-wizard mode-panel" id="mode-procedure-panel" data-mode="procedure" role="tabpanel">
|
||||
<div className="wizard-step" id="step-1">
|
||||
<h3 className="wizard-step-label">
|
||||
<span className="step-number">1</span>
|
||||
<span data-i18n="deadlines.step1">Verfahrensart wählen</span>
|
||||
</h3>
|
||||
|
||||
<div className="proceeding-group" data-forum="upc">
|
||||
<h4 data-i18n="deadlines.upc">UPC</h4>
|
||||
<div className="proceeding-btns">
|
||||
{UPC_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="proceeding-group" data-forum="de">
|
||||
<h4 data-i18n="deadlines.de">Deutsche Gerichte</h4>
|
||||
<div className="proceeding-subgroup">
|
||||
<h5 className="proceeding-subgroup-heading" data-i18n="deadlines.de.group.inf">Verletzungsverfahren</h5>
|
||||
<div className="proceeding-btns">
|
||||
{DE_INF_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="proceeding-subgroup">
|
||||
<h5 className="proceeding-subgroup-heading" data-i18n="deadlines.de.group.null">Nichtigkeitsverfahren</h5>
|
||||
<div className="proceeding-btns">
|
||||
{DE_NULL_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="proceeding-group" data-forum="epa">
|
||||
<h4 data-i18n="deadlines.epa">EPA</h4>
|
||||
<div className="proceeding-btns">
|
||||
{EPA_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="proceeding-group" data-forum="dpma">
|
||||
<h4 data-i18n="deadlines.dpma">DPMA</h4>
|
||||
<div className="proceeding-btns">
|
||||
{DPMA_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* m's 2026-05-08 18:26: collapse the proceeding picker once
|
||||
a choice is made; this summary line replaces the four
|
||||
group blocks with a one-line "Selected: X [Reselect]"
|
||||
affordance. JS toggles `.proceeding-summary` visibility
|
||||
in lockstep with `.proceeding-group` blocks. */}
|
||||
<div className="proceeding-summary" id="proceeding-summary" style="display:none" role="group">
|
||||
<span className="proceeding-summary-label" data-i18n="deadlines.proceeding.selected">Verfahren:</span>
|
||||
<strong className="proceeding-summary-name" id="proceeding-summary-name">—</strong>
|
||||
<button type="button" className="proceeding-summary-reselect" id="proceeding-summary-reselect"
|
||||
data-i18n="deadlines.proceeding.reselect">
|
||||
Anderes Verfahren wählen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="wizard-step" id="step-2" style="display:none">
|
||||
<h3 className="wizard-step-label">
|
||||
<span className="step-number">2</span>
|
||||
<span data-i18n="deadlines.step2">Ausgangsdatum eingeben</span>
|
||||
</h3>
|
||||
|
||||
<div className="date-input-group">
|
||||
<div className="date-field-row">
|
||||
{/* Read-only caption labelling the value <span>. Not a
|
||||
<label htmlFor> — m/paliad#60: <label for=…> must
|
||||
point at a labelable form control, never a span. */}
|
||||
<span className="date-label" data-i18n="deadlines.trigger.event">Auslösendes Ereignis:</span>
|
||||
<span id="trigger-event" className="trigger-event-name">—</span>
|
||||
</div>
|
||||
<div className="date-field-row">
|
||||
<label htmlFor="trigger-date" className="date-label" data-i18n="deadlines.trigger.date">Datum:</label>
|
||||
<input type="date" id="trigger-date" className="date-input" value={today} />
|
||||
</div>
|
||||
<div className="date-field-row" id="court-picker-row" style="display:none">
|
||||
<label htmlFor="court-picker" className="date-label" data-i18n="deadlines.court.label">Gericht:</label>
|
||||
<select id="court-picker" className="date-input"></select>
|
||||
</div>
|
||||
<div className="date-field-row" id="priority-date-row" style="display:none">
|
||||
<label htmlFor="priority-date" className="date-label" data-i18n="deadlines.priority.date">Prioritätstag (optional):</label>
|
||||
<input type="date" id="priority-date" className="date-input" />
|
||||
</div>
|
||||
<div className="date-field-row" id="ccr-flag-row" style="display:none">
|
||||
<label className="date-label">
|
||||
<input type="checkbox" id="ccr-flag" />
|
||||
<span data-i18n="deadlines.flag.ccr">Mit Widerklage auf Nichtigkeit</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="date-field-row date-field-row--nested" id="inf-amend-flag-row" style="display:none">
|
||||
<label className="date-label">
|
||||
<input type="checkbox" id="inf-amend-flag" />
|
||||
<span data-i18n="deadlines.flag.inf_amend">Mit Antrag auf Patentänderung (R.30)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="date-field-row" id="rev-amend-flag-row" style="display:none">
|
||||
<label className="date-label">
|
||||
<input type="checkbox" id="rev-amend-flag" />
|
||||
<span data-i18n="deadlines.flag.rev_amend">Mit Antrag auf Patentänderung (R.49.2.a)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="date-field-row" id="rev-cci-flag-row" style="display:none">
|
||||
<label className="date-label">
|
||||
<input type="checkbox" id="rev-cci-flag" />
|
||||
<span data-i18n="deadlines.flag.rev_cci">Mit Verletzungswiderklage (R.49.2.b)</span>
|
||||
</label>
|
||||
</div>
|
||||
<button type="button" id="calculate-btn" className="calculate-btn" data-i18n="deadlines.calculate">
|
||||
Fristen berechnen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="wizard-step" id="step-3" style="display:none">
|
||||
<h3 className="wizard-step-label">
|
||||
<span className="step-number">3</span>
|
||||
<span data-i18n="deadlines.step3">Ergebnis</span>
|
||||
</h3>
|
||||
|
||||
<div className="fristen-view-toggle" id="fristen-view-toggle" role="radiogroup" aria-label="Ansicht">
|
||||
<span className="fristen-view-label" data-i18n="deadlines.view.label">Ansicht:</span>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="fristen-view" value="columns" checked />
|
||||
<span data-i18n="deadlines.view.columns">Spalten</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="fristen-view" value="timeline" />
|
||||
<span data-i18n="deadlines.view.timeline">Zeitstrahl</span>
|
||||
</label>
|
||||
<label className="fristen-notes-option">
|
||||
<input type="checkbox" id="fristen-notes-show" />
|
||||
<span data-i18n="deadlines.notes.show">Hinweise anzeigen</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="timeline-container">
|
||||
</div>
|
||||
|
||||
<div className="fristen-result-actions">
|
||||
<button type="button" id="fristen-save-cta" className="btn-primary btn-cta-lime" style="display:none" data-i18n="deadlines.save.cta">
|
||||
Als Frist(en) speichern
|
||||
</button>
|
||||
<button type="button" id="fristen-print-btn" className="print-btn" style="display:none">
|
||||
<svg className="print-btn-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<polyline points="6 9 6 2 18 2 18 9"></polyline>
|
||||
<path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path>
|
||||
<rect x="6" y="14" width="12" height="8"></rect>
|
||||
</svg>
|
||||
<span data-i18n="deadlines.print">Drucken</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" id="reset-btn" className="reset-btn" style="display:none" data-i18n="deadlines.reset">
|
||||
← Neu berechnen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="fristen-wizard mode-panel" id="mode-event-panel" data-mode="event" role="tabpanel" hidden>
|
||||
<div className="wizard-step" id="event-step-1">
|
||||
<h3 className="wizard-step-label">
|
||||
<span className="step-number">1</span>
|
||||
<span data-i18n="deadlines.event.step1">Trigger-Ereignis wählen</span>
|
||||
</h3>
|
||||
<p className="wizard-step-hint" data-i18n="deadlines.event.step1.hint">
|
||||
Welches Ereignis ist eingetreten? (z.B. Klageerhebung, Entscheidung des EPA, Zustellung einer Verfügung)
|
||||
</p>
|
||||
<div className="event-picker-row">
|
||||
<label htmlFor="event-search" className="visually-hidden" data-i18n="deadlines.event.search.label">Trigger-Ereignis suchen</label>
|
||||
<input
|
||||
type="search"
|
||||
id="event-search"
|
||||
className="event-search-input"
|
||||
autocomplete="off"
|
||||
data-i18n-placeholder="deadlines.event.search.placeholder"
|
||||
placeholder="Tippe, um zu suchen…"
|
||||
/>
|
||||
<ul id="event-list" className="event-list" role="listbox" aria-label="Trigger-Ereignisse"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="wizard-step" id="event-step-2" style="display:none">
|
||||
<h3 className="wizard-step-label">
|
||||
<span className="step-number">2</span>
|
||||
<span data-i18n="deadlines.event.step2">Datum des Ereignisses</span>
|
||||
</h3>
|
||||
<div className="date-input-group">
|
||||
<div className="date-field-row">
|
||||
<label className="date-label" data-i18n="deadlines.event.selected">Gewähltes Ereignis:</label>
|
||||
<span id="event-selected-name" className="trigger-event-name">—</span>
|
||||
</div>
|
||||
<div className="date-field-row">
|
||||
<label htmlFor="event-date" className="date-label" data-i18n="deadlines.event.date">Eintrittsdatum:</label>
|
||||
<input type="date" id="event-date" className="date-input" value={today} />
|
||||
</div>
|
||||
<button type="button" id="event-calculate-btn" className="calculate-btn" data-i18n="deadlines.event.calculate">
|
||||
Folgefristen berechnen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="wizard-step" id="event-step-3" style="display:none">
|
||||
<h3 className="wizard-step-label">
|
||||
<span className="step-number">3</span>
|
||||
<span data-i18n="deadlines.event.step3">Folgefristen</span>
|
||||
</h3>
|
||||
<div id="event-results-container"></div>
|
||||
<div className="fristen-result-actions">
|
||||
<button type="button" id="event-print-btn" className="print-btn" style="display:none">
|
||||
<svg className="print-btn-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<polyline points="6 9 6 2 18 2 18 9"></polyline>
|
||||
<path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path>
|
||||
<rect x="6" y="14" width="12" height="8"></rect>
|
||||
</svg>
|
||||
<span data-i18n="deadlines.print">Drucken</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" id="event-reset-btn" className="reset-btn" style="display:none" data-i18n="deadlines.reset">
|
||||
← Neu berechnen
|
||||
</button>
|
||||
</div>
|
||||
</div>{/* /pathway-a */}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
<PaliadinWidget />
|
||||
<script src="/assets/fristenrechner.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -2071,7 +2071,6 @@ export type I18nKey =
|
||||
| "nav.downloads"
|
||||
| "nav.einstellungen"
|
||||
| "nav.fristen"
|
||||
| "nav.fristenrechner"
|
||||
| "nav.gebuehrentabellen"
|
||||
| "nav.gerichte"
|
||||
| "nav.glossar"
|
||||
@@ -2088,13 +2087,13 @@ export type I18nKey =
|
||||
| "nav.logout"
|
||||
| "nav.neuigkeiten"
|
||||
| "nav.paliadin"
|
||||
| "nav.procedures"
|
||||
| "nav.projekte"
|
||||
| "nav.soon.tooltip"
|
||||
| "nav.submissions"
|
||||
| "nav.team"
|
||||
| "nav.termine"
|
||||
| "nav.user_views.new"
|
||||
| "nav.verfahrensablauf"
|
||||
| "notes.cancel"
|
||||
| "notes.delete"
|
||||
| "notes.delete.confirm"
|
||||
@@ -2204,6 +2203,19 @@ export type I18nKey =
|
||||
| "partner_unit.members_label"
|
||||
| "partner_unit.none"
|
||||
| "partner_unit.subtitle"
|
||||
| "procedures.filter.axis.forum"
|
||||
| "procedures.filter.axis.kind"
|
||||
| "procedures.filter.axis.party"
|
||||
| "procedures.filter.axis.proc"
|
||||
| "procedures.filter.search.placeholder"
|
||||
| "procedures.heading"
|
||||
| "procedures.panel.akte.placeholder"
|
||||
| "procedures.subtitle"
|
||||
| "procedures.tab.akte"
|
||||
| "procedures.tab.proceeding"
|
||||
| "procedures.tab.search"
|
||||
| "procedures.tab.wizard"
|
||||
| "procedures.title"
|
||||
| "project.instance_level.appeal"
|
||||
| "project.instance_level.cassation"
|
||||
| "project.instance_level.first"
|
||||
@@ -2803,9 +2815,6 @@ export type I18nKey =
|
||||
| "theme.toggle.cycle.light"
|
||||
| "theme.toggle.dark"
|
||||
| "theme.toggle.light"
|
||||
| "tools.verfahrensablauf.heading"
|
||||
| "tools.verfahrensablauf.subtitle"
|
||||
| "tools.verfahrensablauf.title"
|
||||
| "unit_role.attorney"
|
||||
| "unit_role.lead"
|
||||
| "unit_role.pa"
|
||||
|
||||
@@ -74,7 +74,7 @@ export function renderIndex(): string {
|
||||
<p data-i18n="index.cost.desc">Schätzung der Verfahrenskosten für DE-Gerichte, UPC und EPA-Verfahren. Gerichts- und Anwaltskosten auf einen Blick.</p>
|
||||
</a>
|
||||
|
||||
<a href="/tools/fristenrechner" className="card card-link">
|
||||
<a href="/tools/procedures" className="card card-link">
|
||||
<div className="card-icon" dangerouslySetInnerHTML={{ __html: ICON_CLOCK }} />
|
||||
<h2 data-i18n="index.deadline.title">Fristenrechner</h2>
|
||||
<p data-i18n="index.deadline.desc">Berechnung von Verfahrensfristen für UPC-, deutsche und EPA-Verfahren mit Feiertags-Anpassung.</p>
|
||||
|
||||
194
frontend/src/procedures.tsx
Normal file
194
frontend/src/procedures.tsx
Normal file
@@ -0,0 +1,194 @@
|
||||
import { h } from "./jsx";
|
||||
import { Sidebar } from "./components/Sidebar";
|
||||
import { PaliadinWidget } from "./components/PaliadinWidget";
|
||||
import { BottomNav } from "./components/BottomNav";
|
||||
import { Footer } from "./components/Footer";
|
||||
import { PWAHead } from "./components/PWAHead";
|
||||
import { VerfahrensablaufBody } from "./components/VerfahrensablaufBody";
|
||||
|
||||
// U0 — Skeleton for the unified procedural-events tool
|
||||
// (m/paliad#151, design docs/design-unified-procedural-events-tool-2026-05-27.md).
|
||||
//
|
||||
// Folds /tools/fristenrechner (Mode A + Mode B + result) and
|
||||
// /tools/verfahrensablauf into a single page at /tools/procedures. Each
|
||||
// later slice fills one of the four entry tabs:
|
||||
//
|
||||
// U1 — Direkt suchen (Mode A search)
|
||||
// U2 — Geführt (Mode B wizard)
|
||||
// U3 — Verfahren (Verfahrensablauf tree + 3-way detail filter)
|
||||
// U4 — Hard-cut 301 (drop legacy pages, redirect URLs)
|
||||
//
|
||||
// This file ships only the page chrome — sidebar, header, filter strip
|
||||
// with search box, four entry-mode tabs, and the host containers the
|
||||
// later slices mount their UI into. No data wiring.
|
||||
|
||||
export function renderProcedures(): string {
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
return "<!DOCTYPE html>" + (
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<meta name="theme-color" content="#BFF355" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<PWAHead />
|
||||
<title data-i18n="procedures.title">Verfahren & Fristen — Paliad</title>
|
||||
<link rel="stylesheet" href="/assets/global.css" />
|
||||
</head>
|
||||
<body className="has-sidebar page-procedures">
|
||||
<Sidebar currentPath="/tools/procedures" />
|
||||
<BottomNav currentPath="/tools/procedures" />
|
||||
|
||||
<main>
|
||||
<section className="tool-page">
|
||||
<div className="container">
|
||||
<div className="tool-header">
|
||||
<h1 data-i18n="procedures.heading">Verfahren & Fristen</h1>
|
||||
<p className="tool-subtitle" data-i18n="procedures.subtitle">
|
||||
Verfahrensablauf, Fristenrechner und gerührte Suche in einem Tool.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Shared filter strip — search box + four chip groups
|
||||
(forum / proceeding / event_kind / party). Lives at the
|
||||
top of the page so every entry tab and output mode reads
|
||||
the same active filter set (design §4 + m's Q3
|
||||
divergence: search composes with chip filters). U0
|
||||
ships the markup only; chip hydration + search wiring
|
||||
arrive with U1-U3. */}
|
||||
<section className="procedures-filter-strip" aria-label="Filter">
|
||||
<div className="procedures-filter-search">
|
||||
<svg className="procedures-filter-search-icon" width="18" height="18" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" aria-hidden="true">
|
||||
<circle cx="11" cy="11" r="7"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
<input
|
||||
type="search"
|
||||
id="procedures-search-input"
|
||||
className="procedures-filter-search-input"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
data-i18n-placeholder="procedures.filter.search.placeholder"
|
||||
placeholder="Klageerhebung, Hinweisbeschluss, oral hearing…"
|
||||
/>
|
||||
</div>
|
||||
<div className="procedures-filter-chips" id="procedures-filter-chips">
|
||||
<div className="procedures-filter-chip-row" data-axis="forum">
|
||||
<span className="procedures-filter-axis-label" data-i18n="procedures.filter.axis.forum">Forum:</span>
|
||||
<div className="procedures-filter-chip-host" id="procedures-filter-chips-forum"></div>
|
||||
</div>
|
||||
<div className="procedures-filter-chip-row" data-axis="proc">
|
||||
<span className="procedures-filter-axis-label" data-i18n="procedures.filter.axis.proc">Verfahren:</span>
|
||||
<div className="procedures-filter-chip-host" id="procedures-filter-chips-proc"></div>
|
||||
</div>
|
||||
<div className="procedures-filter-chip-row" data-axis="kind">
|
||||
<span className="procedures-filter-axis-label" data-i18n="procedures.filter.axis.kind">Ereignisart:</span>
|
||||
<div className="procedures-filter-chip-host" id="procedures-filter-chips-kind"></div>
|
||||
</div>
|
||||
<div className="procedures-filter-chip-row" data-axis="party">
|
||||
<span className="procedures-filter-axis-label" data-i18n="procedures.filter.axis.party">Partei:</span>
|
||||
<div className="procedures-filter-chip-host" id="procedures-filter-chips-party"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Entry-mode tab strip — all four tabs visible from boot
|
||||
(m's Q3 divergence). The active tab is URL-driven
|
||||
(?mode=proceeding|search|wizard|akte); cold open lands
|
||||
on "proceeding" per design §11.5.Q3. */}
|
||||
<nav className="procedures-tabs" role="tablist" aria-label="Einstieg">
|
||||
<button type="button"
|
||||
className="procedures-tab is-active"
|
||||
role="tab"
|
||||
aria-selected="true"
|
||||
data-tab="proceeding"
|
||||
id="procedures-tab-proceeding">
|
||||
<span className="procedures-tab-icon" aria-hidden="true">📚</span>
|
||||
<span className="procedures-tab-label" data-i18n="procedures.tab.proceeding">Verfahren wählen</span>
|
||||
</button>
|
||||
<button type="button"
|
||||
className="procedures-tab"
|
||||
role="tab"
|
||||
aria-selected="false"
|
||||
data-tab="search"
|
||||
id="procedures-tab-search">
|
||||
<span className="procedures-tab-icon" aria-hidden="true">⚡</span>
|
||||
<span className="procedures-tab-label" data-i18n="procedures.tab.search">Direkt suchen</span>
|
||||
</button>
|
||||
<button type="button"
|
||||
className="procedures-tab"
|
||||
role="tab"
|
||||
aria-selected="false"
|
||||
data-tab="wizard"
|
||||
id="procedures-tab-wizard">
|
||||
<span className="procedures-tab-icon" aria-hidden="true">🧭</span>
|
||||
<span className="procedures-tab-label" data-i18n="procedures.tab.wizard">Geführt</span>
|
||||
</button>
|
||||
<button type="button"
|
||||
className="procedures-tab"
|
||||
role="tab"
|
||||
aria-selected="false"
|
||||
data-tab="akte"
|
||||
id="procedures-tab-akte">
|
||||
<span className="procedures-tab-icon" aria-hidden="true">📁</span>
|
||||
<span className="procedures-tab-label" data-i18n="procedures.tab.akte">Aus Akte</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
{/* Per-tab content hosts. Only one is visible at a time —
|
||||
procedures.ts toggles `hidden` on the inactive ones.
|
||||
Each later slice fills the corresponding host. */}
|
||||
<section className="procedures-panel" id="procedures-panel-proceeding" role="tabpanel"
|
||||
aria-labelledby="procedures-tab-proceeding">
|
||||
{/* Verfahrensablauf wizard body — shared TSX component
|
||||
used by /tools/verfahrensablauf (legacy) and the
|
||||
unified /tools/procedures page. procedures.ts calls
|
||||
initVerfahrensablauf() on the first activation of
|
||||
this tab, which wires the .proceeding-btn clicks,
|
||||
timeline-container, detail-mode toggle, etc. against
|
||||
the markup. The legacy page's auto-boot is guarded
|
||||
against the procedures-only #procedures-panel-proceeding
|
||||
element so it doesn't fire twice. */}
|
||||
<VerfahrensablaufBody todayIso={today} />
|
||||
</section>
|
||||
|
||||
<section className="procedures-panel" id="procedures-panel-search" role="tabpanel"
|
||||
aria-labelledby="procedures-tab-search" hidden></section>
|
||||
|
||||
<section className="procedures-panel" id="procedures-panel-wizard" role="tabpanel"
|
||||
aria-labelledby="procedures-tab-wizard" hidden></section>
|
||||
|
||||
<section className="procedures-panel" id="procedures-panel-akte" role="tabpanel"
|
||||
aria-labelledby="procedures-tab-akte" hidden>
|
||||
<div className="procedures-panel-placeholder" data-i18n="procedures.panel.akte.placeholder">
|
||||
Akten-Einstieg folgt in einem späteren Slice.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Tree output host. Slice U3 mounts the Verfahrensablauf
|
||||
tree here; U0 leaves it empty + hidden so the
|
||||
tab placeholders are the only thing visible. */}
|
||||
<section className="procedures-output procedures-output-tree" id="procedures-output-tree"
|
||||
aria-label="Tree output" hidden></section>
|
||||
|
||||
{/* Linear-drawer host. Inline drawer expanding beneath a
|
||||
tree card (design §8 — desktop) AND the standalone
|
||||
linear follow-up view that Mode A / Mode B land on
|
||||
after locking a trigger event (design §3.2). U1
|
||||
switches it on. */}
|
||||
<section className="procedures-output procedures-output-linear" id="procedures-output-linear"
|
||||
aria-label="Linear output" hidden></section>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
<PaliadinWidget />
|
||||
<script src="/assets/procedures.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -1,403 +0,0 @@
|
||||
import { h } from "./jsx";
|
||||
import { Sidebar } from "./components/Sidebar";
|
||||
import { PaliadinWidget } from "./components/PaliadinWidget";
|
||||
import { BottomNav } from "./components/BottomNav";
|
||||
import { Footer } from "./components/Footer";
|
||||
import { PWAHead } from "./components/PWAHead";
|
||||
|
||||
// Slice 1 (t-paliad-179) — the dedicated abstract-browse surface for
|
||||
// procedural shape. Same backend (POST /api/tools/fristenrechner) +
|
||||
// same renderer module (./client/views/verfahrensablauf-core) as
|
||||
// /tools/fristenrechner; this page strips the Step 1 Akte picker /
|
||||
// Step 2 cards / Pathway A wizard / Pathway B cascade / save modal,
|
||||
// leaving just: proceeding-type tile picker + trigger date + court
|
||||
// picker + result panel. Variant chips, lane view and compare arrive in
|
||||
// Slices 2-4.
|
||||
|
||||
interface ProceedingDef {
|
||||
code: string;
|
||||
i18nKey: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
function proceedingBtn(p: ProceedingDef): string {
|
||||
return (
|
||||
<button type="button" className="proceeding-btn" data-code={p.code}>
|
||||
<strong data-i18n={p.i18nKey}>{p.name}</strong>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// Slice B1 (m/paliad#124 §18.1): the 3 separate Berufung tiles
|
||||
// (upc.apl.merits / upc.apl.cost / upc.apl.order) collapse into ONE
|
||||
// unified "Berufung" tile (upc.apl). After picking it, the user
|
||||
// selects which decision the appeal is directed AT via the
|
||||
// .appeal-target-row chip group below — the engine then filters
|
||||
// rules whose applies_to_target contains the picked slug.
|
||||
const UPC_TYPES: ProceedingDef[] = [
|
||||
{ code: "upc.inf.cfi", i18nKey: "deadlines.upc.inf.cfi", name: "Verletzungsverfahren" },
|
||||
{ code: "upc.rev.cfi", i18nKey: "deadlines.upc.rev.cfi", name: "Nichtigkeitsklage" },
|
||||
{ code: "upc.ccr.cfi", i18nKey: "deadlines.upc.ccr.cfi", name: "Widerklage auf Nichtigkeit" },
|
||||
{ code: "upc.pi.cfi", i18nKey: "deadlines.upc.pi.cfi", name: "Einstw. Maßnahmen" },
|
||||
{ code: "upc.apl.unified", i18nKey: "deadlines.upc.apl.unified", name: "Berufung" },
|
||||
{ code: "upc.dmgs.cfi", i18nKey: "deadlines.upc.dmgs.cfi", name: "Schadensbemessung" },
|
||||
{ code: "upc.disc.cfi", i18nKey: "deadlines.upc.disc.cfi", name: "Bucheinsicht" },
|
||||
];
|
||||
|
||||
// DE proceedings split by type (Verletzung / Nichtigkeit) per m's
|
||||
// 2026-05-18 ask. Labels are parallel: <court> (<procedural role>),
|
||||
// so a user scanning the picker sees the instance-and-role at a glance
|
||||
// without one tile reading "Berufung OLG" and another "Nichtigkeits-
|
||||
// verfahren". Sub-group headers convey the type grouping. Combined-
|
||||
// timeline behaviour (LG→OLG→BGH as one calc) is filed as m/paliad#41.
|
||||
const DE_INF_TYPES: ProceedingDef[] = [
|
||||
{ code: "de.inf.lg", i18nKey: "deadlines.de.inf.lg", name: "LG (1. Instanz)" },
|
||||
{ code: "de.inf.olg", i18nKey: "deadlines.de.inf.olg", name: "OLG (Berufung)" },
|
||||
{ code: "de.inf.bgh", i18nKey: "deadlines.de.inf.bgh", name: "BGH (Revision / NZB)" },
|
||||
];
|
||||
|
||||
const DE_NULL_TYPES: ProceedingDef[] = [
|
||||
{ code: "de.null.bpatg", i18nKey: "deadlines.de.null.bpatg", name: "BPatG (1. Instanz)" },
|
||||
{ code: "de.null.bgh", i18nKey: "deadlines.de.null.bgh", name: "BGH (Berufung)" },
|
||||
];
|
||||
|
||||
const EPA_TYPES: ProceedingDef[] = [
|
||||
{ code: "epa.opp.opd", i18nKey: "deadlines.epa.opp.opd", name: "Einspruchsverfahren" },
|
||||
{ code: "epa.opp.boa", i18nKey: "deadlines.epa.opp.boa", name: "Beschwerdeverfahren" },
|
||||
{ code: "epa.grant.exa", i18nKey: "deadlines.epa.grant.exa", name: "EP-Erteilungsverfahren" },
|
||||
];
|
||||
|
||||
const DPMA_TYPES: ProceedingDef[] = [
|
||||
{ code: "dpma.opp.dpma", i18nKey: "deadlines.dpma.opp.dpma", name: "Einspruch DPMA" },
|
||||
{ code: "dpma.appeal.bpatg", i18nKey: "deadlines.dpma.appeal.bpatg", name: "Beschwerde BPatG (DPMA)" },
|
||||
{ code: "dpma.appeal.bgh", i18nKey: "deadlines.dpma.appeal.bgh", name: "Rechtsbeschwerde BGH" },
|
||||
];
|
||||
|
||||
export function renderVerfahrensablauf(): string {
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
|
||||
return "<!DOCTYPE html>" + (
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<meta name="theme-color" content="#BFF355" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<PWAHead />
|
||||
<title data-i18n="tools.verfahrensablauf.title">Verfahrensablauf — Paliad</title>
|
||||
<link rel="stylesheet" href="/assets/global.css" />
|
||||
</head>
|
||||
<body className="has-sidebar page-verfahrensablauf">
|
||||
<Sidebar currentPath="/tools/verfahrensablauf" />
|
||||
<BottomNav currentPath="/tools/verfahrensablauf" />
|
||||
|
||||
<main>
|
||||
<section className="tool-page">
|
||||
<div className="container">
|
||||
<div className="tool-header">
|
||||
<h1 data-i18n="tools.verfahrensablauf.heading">Verfahrensablauf</h1>
|
||||
<p className="tool-subtitle" data-i18n="tools.verfahrensablauf.subtitle">
|
||||
Typischen Verfahrensablauf einsehen — Verfahrensart wählen, Datum optional setzen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Verfahrensart picker (single-tile mode — same DOM ids as
|
||||
/tools/fristenrechner so the shared renderer module and
|
||||
court-picker primitives bind without parameterisation). */}
|
||||
<div className="fristen-wizard" id="verfahrensablauf-wizard" data-mode="procedure">
|
||||
<div className="wizard-step" id="step-1">
|
||||
<h3 className="wizard-step-label">
|
||||
<span className="step-number">1</span>
|
||||
<span data-i18n="deadlines.step1">Verfahrensart wählen</span>
|
||||
</h3>
|
||||
|
||||
<div className="proceeding-group" data-forum="upc">
|
||||
<h4 data-i18n="deadlines.upc">UPC</h4>
|
||||
<div className="proceeding-btns">
|
||||
{UPC_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="proceeding-group" data-forum="de">
|
||||
<h4 data-i18n="deadlines.de">Deutsche Gerichte</h4>
|
||||
<div className="proceeding-subgroup">
|
||||
<h5 className="proceeding-subgroup-heading" data-i18n="deadlines.de.group.inf">Verletzungsverfahren</h5>
|
||||
<div className="proceeding-btns">
|
||||
{DE_INF_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="proceeding-subgroup">
|
||||
<h5 className="proceeding-subgroup-heading" data-i18n="deadlines.de.group.null">Nichtigkeitsverfahren</h5>
|
||||
<div className="proceeding-btns">
|
||||
{DE_NULL_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="proceeding-group" data-forum="epa">
|
||||
<h4 data-i18n="deadlines.epa">EPA</h4>
|
||||
<div className="proceeding-btns">
|
||||
{EPA_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="proceeding-group" data-forum="dpma">
|
||||
<h4 data-i18n="deadlines.dpma">DPMA</h4>
|
||||
<div className="proceeding-btns">
|
||||
{DPMA_TYPES.map((p) => proceedingBtn(p))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="proceeding-summary" id="proceeding-summary" style="display:none" role="group">
|
||||
<span className="proceeding-summary-label" data-i18n="deadlines.proceeding.selected">Verfahren:</span>
|
||||
<strong className="proceeding-summary-name" id="proceeding-summary-name">—</strong>
|
||||
<button type="button" className="proceeding-summary-reselect" id="proceeding-summary-reselect"
|
||||
data-i18n="deadlines.proceeding.reselect">
|
||||
Anderes Verfahren wählen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="wizard-step" id="step-2" style="display:none">
|
||||
<h3 className="wizard-step-label">
|
||||
<span className="step-number">2</span>
|
||||
<span data-i18n="deadlines.step2.perspective">Perspektive und Datum</span>
|
||||
</h3>
|
||||
|
||||
{/* Perspective strip (t-paliad-250 / m/paliad#81, reordered
|
||||
in t-paliad-279 / m/paliad#111). Side defines whose
|
||||
perspective the columns project; appellant collapses
|
||||
party=both rows for role-swap proceedings (Appeal etc.).
|
||||
Moved above .date-input-group because party-side is the
|
||||
most-defining input after proceeding-type — without
|
||||
side, the column labels can't pick "your filings". Both
|
||||
selectors are URL-driven (?side= + ?appellant=) so the
|
||||
perspective survives reload and is shareable.
|
||||
|
||||
When the page is opened with ?project=<id> and that
|
||||
project's our_side is set, side-row renders as a
|
||||
read-only chip with an "Andere Seite wählen" override
|
||||
link — see client/verfahrensablauf.ts. */}
|
||||
<div className="verfahrensablauf-perspective" id="verfahrensablauf-perspective">
|
||||
<div className="verfahrensablauf-perspective-row" id="side-row">
|
||||
<span className="date-label" data-i18n="deadlines.side.label">Seite:</span>
|
||||
<div className="side-radio-cluster" id="side-radio-cluster">
|
||||
<div className="fristen-view-toggle" role="radiogroup" aria-label="Side">
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="side" value="claimant" />
|
||||
<span data-i18n="deadlines.side.claimant">Klägerseite</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="side" value="defendant" />
|
||||
<span data-i18n="deadlines.side.defendant">Beklagtenseite</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="side" value="" checked />
|
||||
<span data-i18n="deadlines.side.undefined">Nicht festgelegt</span>
|
||||
</label>
|
||||
</div>
|
||||
{/* Prompt shown while the user hasn't picked a side
|
||||
(m/paliad#120). Hidden by client when side is
|
||||
claimant or defendant. Both columns still
|
||||
render every rule in this state — picking a
|
||||
side just focuses the user's column. */}
|
||||
<span className="side-hint" id="side-hint"
|
||||
data-i18n="deadlines.side.hint">
|
||||
Wählen Sie eine Seite, um die Spalten zu fokussieren.
|
||||
</span>
|
||||
</div>
|
||||
{/* Auto-fill chip — populated by the client when a
|
||||
?project=<id> URL resolves a project with our_side
|
||||
set. Hidden by default; the radio cluster above is
|
||||
hidden whenever this chip is shown. */}
|
||||
<div className="side-chip" id="side-chip" style="display:none">
|
||||
<span className="side-chip-tag" data-i18n="deadlines.side.from_project">Aus Akte:</span>
|
||||
<strong className="side-chip-value" id="side-chip-value">—</strong>
|
||||
<button type="button" className="side-chip-override" id="side-chip-override"
|
||||
data-i18n="deadlines.side.override">
|
||||
Andere Seite wählen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Appeal-target chip row (Slice B1 / m/paliad#124 §18.1).
|
||||
Shown only when the unified upc.apl Berufung tile is
|
||||
selected; lets the user narrow the timeline to the
|
||||
rules whose applies_to_target contains the picked
|
||||
decision kind. URL state ?target=<slug>. */}
|
||||
<div className="verfahrensablauf-perspective-row" id="appeal-target-row" style="display:none">
|
||||
<span className="date-label" data-i18n="deadlines.appeal_target.label">Worauf richtet sich die Berufung?</span>
|
||||
<div className="fristen-view-toggle" role="radiogroup" aria-label="Appeal target">
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="appeal-target" value="endentscheidung" checked />
|
||||
<span data-i18n="deadlines.appeal_target.endentscheidung">Endentscheidung</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="appeal-target" value="kostenentscheidung" />
|
||||
<span data-i18n="deadlines.appeal_target.kostenentscheidung">Kostenentscheidung</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="appeal-target" value="anordnung" />
|
||||
<span data-i18n="deadlines.appeal_target.anordnung">Anordnung</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="appeal-target" value="schadensbemessung" />
|
||||
<span data-i18n="deadlines.appeal_target.schadensbemessung">Schadensbemessung</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="appeal-target" value="bucheinsicht" />
|
||||
<span data-i18n="deadlines.appeal_target.bucheinsicht">Bucheinsicht</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{/* Show-hidden toggle (t-paliad-290 / m/paliad#122).
|
||||
Re-surfaces optional cards the user has previously
|
||||
marked "Überspringen" via the per-card popover.
|
||||
The row hides itself when the projection has no
|
||||
hidden cards (handled in client/verfahrensablauf.ts).
|
||||
Default OFF; URL state ?show_hidden=1. */}
|
||||
<div className="verfahrensablauf-perspective-row" id="show-hidden-row" style="display:none">
|
||||
<label className="fristen-view-option">
|
||||
<input type="checkbox" id="show-hidden-toggle" />
|
||||
<span data-i18n="choices.show_hidden.label">Ausgeblendete anzeigen</span>
|
||||
</label>
|
||||
<span className="show-hidden-count" id="show-hidden-count" aria-live="polite"> </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual divider — keeps the perspective block (most-
|
||||
defining inputs after proceeding-type) optically
|
||||
separate from the date / court / flag knobs below. */}
|
||||
<div className="verfahrensablauf-step2-divider" aria-hidden="true"></div>
|
||||
|
||||
<div className="date-input-group">
|
||||
<div className="date-field-row">
|
||||
{/* Read-only caption labelling the value <span>. Not a
|
||||
<label htmlFor> — m/paliad#60: <label for=…> must
|
||||
point at a labelable form control, never a span. */}
|
||||
<span className="date-label" data-i18n="deadlines.trigger.event">Auslösendes Ereignis:</span>
|
||||
<span id="trigger-event" className="trigger-event-name">—</span>
|
||||
</div>
|
||||
<div className="date-field-row">
|
||||
<label htmlFor="trigger-date" className="date-label" data-i18n="deadlines.trigger.date">Datum:</label>
|
||||
<input type="date" id="trigger-date" className="date-input" value={today} />
|
||||
</div>
|
||||
<div className="date-field-row" id="court-picker-row" style="display:none">
|
||||
<label htmlFor="court-picker" className="date-label" data-i18n="deadlines.court.label">Gericht:</label>
|
||||
<select id="court-picker" className="date-input"></select>
|
||||
</div>
|
||||
{/* Proceeding-specific flag rows — mirror /tools/fristenrechner
|
||||
so an abstract-browse user can model the same variants
|
||||
(CCR, Patentänderung, Verletzungswiderklage,
|
||||
Vorab-Einrede). Show/hide driven by selectedType in
|
||||
the client. */}
|
||||
<div className="date-field-row" id="ccr-flag-row" style="display:none">
|
||||
<label className="date-label">
|
||||
<input type="checkbox" id="ccr-flag" />
|
||||
<span data-i18n="deadlines.flag.ccr">Mit Widerklage auf Nichtigkeit</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="date-field-row date-field-row--nested" id="inf-amend-flag-row" style="display:none">
|
||||
<label className="date-label">
|
||||
<input type="checkbox" id="inf-amend-flag" />
|
||||
<span data-i18n="deadlines.flag.inf_amend">Mit Antrag auf Patentänderung (R.30)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="date-field-row" id="rev-amend-flag-row" style="display:none">
|
||||
<label className="date-label">
|
||||
<input type="checkbox" id="rev-amend-flag" />
|
||||
<span data-i18n="deadlines.flag.rev_amend">Mit Antrag auf Patentänderung (R.49.2.a)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="date-field-row" id="rev-cci-flag-row" style="display:none">
|
||||
<label className="date-label">
|
||||
<input type="checkbox" id="rev-cci-flag" />
|
||||
<span data-i18n="deadlines.flag.rev_cci">Mit Verletzungswiderklage (R.49.2.b)</span>
|
||||
</label>
|
||||
</div>
|
||||
<button type="button" id="calculate-btn" className="calculate-btn" data-i18n="deadlines.calculate">
|
||||
Fristen berechnen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="wizard-step" id="step-3" style="display:none">
|
||||
<h3 className="wizard-step-label">
|
||||
<span className="step-number">3</span>
|
||||
<span data-i18n="deadlines.step3">Ergebnis</span>
|
||||
</h3>
|
||||
|
||||
{/* m/paliad#149 Phase 2 P3 — three-way detail filter.
|
||||
Controls how much of the procedural shape renders:
|
||||
just mandatory; mandatory + selected (default); or
|
||||
every option including unselected ones rendered
|
||||
muted. State persists in localStorage under
|
||||
verfahrensablauf:view_mode. The toggle drives a
|
||||
client-side filter pre-render; the calc payload
|
||||
stays the same, so flipping is instant. */}
|
||||
<div className="verfahrensablauf-detail-toggle" id="verfahrensablauf-detail-toggle"
|
||||
role="radiogroup" aria-label="Detail">
|
||||
<span className="fristen-view-label" data-i18n="deadlines.detail.label">Anzeige:</span>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="detail-mode" value="mandatory_only" />
|
||||
<span data-i18n="deadlines.detail.mandatory_only">Nur Pflicht</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="detail-mode" value="selected" checked />
|
||||
<span data-i18n="deadlines.detail.selected">Gewählt</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="detail-mode" value="all_options" />
|
||||
<span data-i18n="deadlines.detail.all_options">Alle Optionen</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="fristen-view-toggle" id="fristen-view-toggle" role="radiogroup" aria-label="Ansicht">
|
||||
<span className="fristen-view-label" data-i18n="deadlines.view.label">Ansicht:</span>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="fristen-view" value="columns" checked />
|
||||
<span data-i18n="deadlines.view.columns">Spalten</span>
|
||||
</label>
|
||||
<label className="fristen-view-option">
|
||||
<input type="radio" name="fristen-view" value="timeline" />
|
||||
<span data-i18n="deadlines.view.timeline">Zeitstrahl</span>
|
||||
</label>
|
||||
<label className="fristen-notes-option">
|
||||
<input type="checkbox" id="fristen-notes-show" />
|
||||
<span data-i18n="deadlines.notes.show">Hinweise anzeigen</span>
|
||||
</label>
|
||||
{/* Durations toggle (m/paliad#133, t-paliad-302).
|
||||
Default off — hover-tooltips on date spans are
|
||||
the always-on path. */}
|
||||
<label className="fristen-notes-option">
|
||||
<input type="checkbox" id="verfahrensablauf-durations-show" />
|
||||
<span data-i18n="deadlines.durations.show">Dauern anzeigen</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="timeline-container">
|
||||
</div>
|
||||
|
||||
<div className="fristen-result-actions">
|
||||
<button type="button" id="fristen-print-btn" className="print-btn" style="display:none">
|
||||
<svg className="print-btn-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<polyline points="6 9 6 2 18 2 18 9"></polyline>
|
||||
<path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path>
|
||||
<rect x="6" y="14" width="12" height="8"></rect>
|
||||
</svg>
|
||||
<span data-i18n="deadlines.print">Drucken</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
<PaliadinWidget />
|
||||
<script src="/assets/verfahrensablauf.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -12,28 +12,41 @@ import (
|
||||
"mgit.msbls.de/m/paliad/internal/services"
|
||||
)
|
||||
|
||||
// Fristenrechner page handler: serves the static HTML. No DB dependency.
|
||||
//
|
||||
// Back-compat: the pre-split sidebar entry for "Verfahrensablauf" pointed at
|
||||
// /tools/fristenrechner?path=a. After the t-paliad-179 split, that landing is
|
||||
// owned by /tools/verfahrensablauf. A naked ?path=a (no Akte context — i.e.
|
||||
// no ?project=) is the bookmarked-legacy-entry case → 302 to the new route.
|
||||
// ?project=<uuid>&path=a is the Akte-mode internal wizard pathway and stays
|
||||
// on /tools/fristenrechner so the wizard state survives a refresh.
|
||||
func handleFristenrechnerPage(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
if q.Get("path") == "a" && q.Get("project") == "" {
|
||||
http.Redirect(w, r, "/tools/verfahrensablauf", http.StatusFound)
|
||||
return
|
||||
// U4 (m/paliad#151) — legacy /tools/fristenrechner and
|
||||
// /tools/verfahrensablauf folded into /tools/procedures via hard 301
|
||||
// redirects. Per m's Q11 divergence in the design (no 2-week dual-ship
|
||||
// window), bookmarks resolve via Location preservation of query params;
|
||||
// no `?legacy=1` escape, no in-product affordance points back at the
|
||||
// retired URLs after the merge.
|
||||
|
||||
func redirectToProcedures(w http.ResponseWriter, r *http.Request) {
|
||||
loc := "/tools/procedures"
|
||||
if raw := r.URL.RawQuery; raw != "" {
|
||||
loc += "?" + raw
|
||||
}
|
||||
http.ServeFile(w, r, "dist/fristenrechner.html")
|
||||
http.Redirect(w, r, loc, http.StatusMovedPermanently)
|
||||
}
|
||||
|
||||
// Verfahrensablauf page handler (t-paliad-179 Slice 1): the dedicated
|
||||
// abstract-browse surface for procedural shape. No DB dependency — the page
|
||||
// shell is static HTML; the calculator API still drives the timeline render.
|
||||
// handleFristenrechnerPage — kept as a registration name for the legacy
|
||||
// URL so bookmarks (and the existing Sidebar history a former user may
|
||||
// have cached) keep resolving. 301s to /tools/procedures.
|
||||
func handleFristenrechnerPage(w http.ResponseWriter, r *http.Request) {
|
||||
redirectToProcedures(w, r)
|
||||
}
|
||||
|
||||
// handleVerfahrensablaufPage — symmetrical 301 to /tools/procedures.
|
||||
func handleVerfahrensablaufPage(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "dist/verfahrensablauf.html")
|
||||
redirectToProcedures(w, r)
|
||||
}
|
||||
|
||||
// Unified procedural-events tool page (m/paliad#151, design
|
||||
// docs/design-unified-procedural-events-tool-2026-05-27.md). Consolidates
|
||||
// Fristenrechner Mode A + Mode B + result + Verfahrensablauf into a
|
||||
// single surface at /tools/procedures. No DB dependency — the page
|
||||
// itself is static HTML; per-tab data flows over the existing
|
||||
// /api/tools/fristenrechner/* endpoints.
|
||||
func handleProceduresPage(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "dist/procedures.html")
|
||||
}
|
||||
|
||||
// POST /api/tools/fristenrechner — calculate the UI timeline for a proceeding.
|
||||
|
||||
@@ -305,6 +305,7 @@ func Register(mux *http.ServeMux, client *auth.Client, giteaAPIToken string, svc
|
||||
protected.HandleFunc("POST /api/tools/kostenrechner", handleKostenrechnerAPI)
|
||||
protected.HandleFunc("GET /tools/fristenrechner", handleFristenrechnerPage)
|
||||
protected.HandleFunc("GET /tools/verfahrensablauf", handleVerfahrensablaufPage)
|
||||
protected.HandleFunc("GET /tools/procedures", handleProceduresPage)
|
||||
protected.HandleFunc("POST /api/tools/fristenrechner", handleFristenrechnerAPI)
|
||||
protected.HandleFunc("POST /api/tools/fristenrechner/calculate-rule", handleFristenrechnerCalculateRule)
|
||||
protected.HandleFunc("GET /api/tools/proceeding-types", handleProceedingTypes)
|
||||
|
||||
@@ -6,78 +6,54 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// /tools/fristenrechner?path=a was the pre-split sidebar entry for the
|
||||
// "Verfahrensablauf" surface. After t-paliad-179 Slice 1 that intent
|
||||
// owns its own /tools/verfahrensablauf route — so a naked ?path=a hit
|
||||
// must 302 to the new URL to preserve bookmarked legacy links.
|
||||
//
|
||||
// The Akte-mode internal wizard pathway (?project=<uuid>&path=a) is
|
||||
// NOT a top-level entry — it's wizard state set by client-side
|
||||
// history.replaceState. That URL must keep serving the fristenrechner
|
||||
// shell so a mid-wizard refresh doesn't bounce away.
|
||||
func TestHandleFristenrechnerPage_LegacyPathARedirect(t *testing.T) {
|
||||
// U4 (m/paliad#151) — both legacy URLs hard-cut to /tools/procedures
|
||||
// with HTTP 301. Query strings carry through so bookmarks like
|
||||
// /tools/fristenrechner?event=upc.inf.cfi.soc&trigger_date=2026-04-01
|
||||
// resolve to /tools/procedures?event=…&trigger_date=… without losing
|
||||
// the user's intent.
|
||||
func TestLegacyToolsPagesRedirect(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
path string
|
||||
wantStatus int
|
||||
wantLoc string
|
||||
name string
|
||||
path string
|
||||
handler func(http.ResponseWriter, *http.Request)
|
||||
wantLoc string
|
||||
}{
|
||||
{
|
||||
name: "naked path=a → redirect",
|
||||
path: "/tools/fristenrechner?path=a",
|
||||
wantStatus: http.StatusFound,
|
||||
wantLoc: "/tools/verfahrensablauf",
|
||||
name: "fristenrechner naked",
|
||||
path: "/tools/fristenrechner",
|
||||
handler: handleFristenrechnerPage,
|
||||
wantLoc: "/tools/procedures",
|
||||
},
|
||||
{
|
||||
name: "path=a with project= → no redirect (Akte-mode wizard)",
|
||||
path: "/tools/fristenrechner?project=abc-123&path=a",
|
||||
wantStatus: http.StatusOK,
|
||||
name: "fristenrechner with query",
|
||||
path: "/tools/fristenrechner?event=upc.inf.cfi.soc&trigger_date=2026-04-01",
|
||||
handler: handleFristenrechnerPage,
|
||||
wantLoc: "/tools/procedures?event=upc.inf.cfi.soc&trigger_date=2026-04-01",
|
||||
},
|
||||
{
|
||||
name: "no path param → no redirect",
|
||||
path: "/tools/fristenrechner",
|
||||
wantStatus: http.StatusOK,
|
||||
name: "verfahrensablauf naked",
|
||||
path: "/tools/verfahrensablauf",
|
||||
handler: handleVerfahrensablaufPage,
|
||||
wantLoc: "/tools/procedures",
|
||||
},
|
||||
{
|
||||
name: "path=b → no redirect (Pathway B stays)",
|
||||
path: "/tools/fristenrechner?path=b",
|
||||
wantStatus: http.StatusOK,
|
||||
name: "verfahrensablauf with proceeding",
|
||||
path: "/tools/verfahrensablauf?proceeding=upc.inf.cfi&side=claimant",
|
||||
handler: handleVerfahrensablaufPage,
|
||||
wantLoc: "/tools/procedures?proceeding=upc.inf.cfi&side=claimant",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, tc.path, nil)
|
||||
w := httptest.NewRecorder()
|
||||
handleFristenrechnerPage(w, req)
|
||||
if w.Code != tc.wantStatus {
|
||||
// http.ServeFile may write 404 if dist/fristenrechner.html
|
||||
// is missing under `go test` (CI runs without a frontend
|
||||
// build). We only care that we did NOT redirect in those
|
||||
// cases — collapse 200 and 404 into "not a redirect".
|
||||
if tc.wantStatus == http.StatusOK && w.Code != http.StatusFound {
|
||||
return
|
||||
}
|
||||
t.Fatalf("status = %d, want %d", w.Code, tc.wantStatus)
|
||||
tc.handler(w, req)
|
||||
if w.Code != http.StatusMovedPermanently {
|
||||
t.Fatalf("status = %d, want %d", w.Code, http.StatusMovedPermanently)
|
||||
}
|
||||
if tc.wantLoc != "" {
|
||||
if got := w.Header().Get("Location"); got != tc.wantLoc {
|
||||
t.Fatalf("Location = %q, want %q", got, tc.wantLoc)
|
||||
}
|
||||
if got := w.Header().Get("Location"); got != tc.wantLoc {
|
||||
t.Fatalf("Location = %q, want %q", got, tc.wantLoc)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The new /tools/verfahrensablauf route registers as a 1-liner page
|
||||
// handler that ServeFiles dist/verfahrensablauf.html. We assert the
|
||||
// handler does NOT redirect — if the dist artefact is missing under
|
||||
// `go test`, ServeFile may return 404, but it must never return a 3xx.
|
||||
func TestHandleVerfahrensablaufPage_NoRedirect(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/tools/verfahrensablauf", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handleVerfahrensablaufPage(w, req)
|
||||
if w.Code >= 300 && w.Code < 400 {
|
||||
t.Fatalf("verfahrensablauf must not redirect; got %d → %s",
|
||||
w.Code, w.Header().Get("Location"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -853,19 +853,24 @@ func buildPill(p pillRow) Pill {
|
||||
}
|
||||
|
||||
func pillDrillURL(p pillRow) string {
|
||||
// m/paliad#151 U4 — drill-in URLs target /tools/procedures, the
|
||||
// unified successor to /tools/fristenrechner and
|
||||
// /tools/verfahrensablauf. The legacy URLs still 301-redirect, so
|
||||
// any cached snapshot keeps working, but new searches land on the
|
||||
// new page directly.
|
||||
switch p.Kind {
|
||||
case "rule":
|
||||
if p.ProceedingCode.Valid && p.RuleLocalCode != "" {
|
||||
return "/tools/fristenrechner?proc=" + p.ProceedingCode.String + "&focus=" + p.RuleLocalCode
|
||||
return "/tools/procedures?proc=" + p.ProceedingCode.String + "&focus=" + p.RuleLocalCode
|
||||
}
|
||||
return "/tools/fristenrechner"
|
||||
return "/tools/procedures"
|
||||
case "trigger":
|
||||
if p.TriggerEventID.Valid {
|
||||
return fmt.Sprintf("/tools/fristenrechner?mode=event&triggerId=%d", p.TriggerEventID.Int64)
|
||||
return fmt.Sprintf("/tools/procedures?mode=event&triggerId=%d", p.TriggerEventID.Int64)
|
||||
}
|
||||
return "/tools/fristenrechner?mode=event"
|
||||
return "/tools/procedures?mode=event"
|
||||
}
|
||||
return "/tools/fristenrechner"
|
||||
return "/tools/procedures"
|
||||
}
|
||||
|
||||
// pillSortKey orders pills inside a card. Rule pills before triggers;
|
||||
|
||||
Reference in New Issue
Block a user