feat(procedures): U3 fold Verfahrensablauf tree + 3-way detail filter (m/paliad#151)

Mounts the full Verfahrensablauf wizard — proceeding picker, perspective
chooser, date inputs, scenario flag rows, detail-mode toggle, view
toggle, timeline-container — under the /tools/procedures "Verfahren
wählen" tab. Per-rule scenario_flags chips (P0 SSoT) and the
Aufnehmen/Entfernen affordances reach the unified page unchanged since
they're delegated handlers on the timeline-container.

Refactor steps:
- Extracted the wizard body markup into a shared TSX component
  (components/VerfahrensablaufBody) used by both verfahrensablauf.tsx
  (legacy) and procedures.tsx (unified). U4 will retire the legacy
  page; the shared component lets U3 ship without code duplication.
- Lifted the verfahrensablauf.ts DOMContentLoaded body into
  initVerfahrensablauf() and re-exported it. The legacy auto-boot
  stays in place but skips itself when #procedures-panel-proceeding
  is present, so the unified page imports the module without
  double-init. procedures.ts calls initVerfahrensablauf() the first
  time the proceeding tab activates, gated by a one-shot flag to
  preserve module-local selectedType / lastResponse across tab
  toggles.
This commit is contained in:
mAi
2026-05-27 20:29:05 +02:00
parent c8261da492
commit 48a07ef4ef
7 changed files with 348 additions and 362 deletions

View File

@@ -218,7 +218,6 @@ const translations: Record<Lang, Record<string, string>> = {
"procedures.tab.search": "Direkt suchen",
"procedures.tab.wizard": "Gef\u00fchrt",
"procedures.tab.akte": "Aus Akte",
"procedures.panel.proceeding.placeholder": "Verfahrenswahl folgt in U3 \u2014 das \u00dcbersichts-Baumdiagramm wird hier eingebettet.",
"procedures.panel.akte.placeholder": "Akten-Einstieg folgt in einem sp\u00e4teren Slice.",
"nav.procedures": "Verfahren & Fristen",
@@ -3429,7 +3428,6 @@ const translations: Record<Lang, Record<string, string>> = {
"procedures.tab.search": "Direct search",
"procedures.tab.wizard": "Guided",
"procedures.tab.akte": "From matter",
"procedures.panel.proceeding.placeholder": "Pick-proceeding view ships in U3 \u2014 the overview tree mounts here.",
"procedures.panel.akte.placeholder": "Matter entry ships in a later slice.",
"nav.procedures": "Procedures & Deadlines",

View File

@@ -8,8 +8,8 @@
//
// U0 — Skeleton + tab toggling.
// U1 — Direkt suchen mounts Mode A.
// U2 — Geführt mounts Mode B wizard (this slice).
// U3 — Verfahren wählen mounts Verfahrensablauf tree + 3-way detail filter.
// 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
@@ -24,6 +24,7 @@ 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";
@@ -80,6 +81,13 @@ function setActiveTabUI(tab: ProceduresTab): void {
}
}
// 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") {
@@ -92,7 +100,12 @@ async function activateTab(tab: ProceduresTab): Promise<void> {
await mountWizard();
return;
}
// U3 will mount Verfahrensablauf into proceeding-panel.
if (tab === "proceeding") {
if (!verfahrensablaufWired) {
initVerfahrensablauf();
verfahrensablaufWired = true;
}
}
}
function wireTabs(): void {

View File

@@ -1011,10 +1011,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 +1261,16 @@ document.addEventListener("DOMContentLoaded", () => {
const writeURL = urlProceeding !== "" && !urlHit;
selectProceeding(initialBtn, { writeURL });
}
}
// Legacy /tools/verfahrensablauf entrypoint auto-boot. The unified
// /tools/procedures page imports this module too but owns its own boot —
// the guard checks for the procedures-only #procedures-panel-proceeding
// element so the auto-boot doesn't fire twice. U4 drops the legacy page
// + this auto-boot together.
document.addEventListener("DOMContentLoaded", () => {
if (document.getElementById("procedures-panel-proceeding")) return;
initI18n();
initSidebar();
initVerfahrensablauf();
});

View 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&auml;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">&mdash;</strong>
<button type="button" className="proceeding-summary-reselect" id="proceeding-summary-reselect"
data-i18n="deadlines.proceeding.reselect">
Anderes Verfahren w&auml;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&auml;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">&mdash;</strong>
<button type="button" className="side-chip-override" id="side-chip-override"
data-i18n="deadlines.side.override">
Andere Seite w&auml;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">&nbsp;</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&ouml;sendes Ereignis:</span>
<span id="trigger-event" className="trigger-event-name">&mdash;</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&auml;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&auml;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>
);
}

View File

@@ -2212,7 +2212,6 @@ export type I18nKey =
| "procedures.filter.search.placeholder"
| "procedures.heading"
| "procedures.panel.akte.placeholder"
| "procedures.panel.proceeding.placeholder"
| "procedures.subtitle"
| "procedures.tab.akte"
| "procedures.tab.proceeding"

View File

@@ -4,6 +4,7 @@ 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).
@@ -22,6 +23,7 @@ import { PWAHead } from "./components/PWAHead";
// 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>
@@ -141,9 +143,16 @@ export function renderProcedures(): string {
Each later slice fills the corresponding host. */}
<section className="procedures-panel" id="procedures-panel-proceeding" role="tabpanel"
aria-labelledby="procedures-tab-proceeding">
<div className="procedures-panel-placeholder" data-i18n="procedures.panel.proceeding.placeholder">
Verfahrenswahl folgt in U3 &mdash; das &Uuml;bersichts-Baumdiagramm wird hier eingebettet.
</div>
{/* 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"

View File

@@ -4,6 +4,7 @@ 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";
// Slice 1 (t-paliad-179) — the dedicated abstract-browse surface for
// procedural shape. Same backend (POST /api/tools/fristenrechner) +
@@ -11,67 +12,11 @@ import { PWAHead } from "./components/PWAHead";
// /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" },
];
// picker + result panel.
//
// U3 (m/paliad#151) extracted the wizard body markup into
// components/VerfahrensablaufBody so /tools/procedures can mount the
// same DOM under its "Verfahren wählen" tab. U4 will retire this page.
export function renderVerfahrensablauf(): string {
const today = new Date().toISOString().split("T")[0];
@@ -102,294 +47,7 @@ export function renderVerfahrensablauf(): string {
</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&auml;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">&mdash;</strong>
<button type="button" className="proceeding-summary-reselect" id="proceeding-summary-reselect"
data-i18n="deadlines.proceeding.reselect">
Anderes Verfahren w&auml;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&auml;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">&mdash;</strong>
<button type="button" className="side-chip-override" id="side-chip-override"
data-i18n="deadlines.side.override">
Andere Seite w&auml;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">&nbsp;</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&ouml;sendes Ereignis:</span>
<span id="trigger-event" className="trigger-event-name">&mdash;</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&auml;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&auml;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>
<VerfahrensablaufBody todayIso={today} />
</div>
</section>
</main>