Litigation Builder slice B5 (m/paliad#153 PRD §2.4 + §2.5 + §5.4 + §10). Backend (internal/services/scenario_builder_service.go): - ListSharedWithMe — scenarios shared read-only with the caller (the "Geteilt mit mir" bucket). - PromoteScenario — transactional promote-to-project (PRD §10, no partial promotions). One Postgres tx: INSERT paliad.projects ('case', origin_scenario_id, proceeding_type_id + scenario_flags from the primary triplet) → creator team lead + wizard-selected colleagues → parties → deadlines (filed→completed, planned→pending with computed/actual date, skipped→none) → flip scenario to 'promoted' + promoted_project_id. The primary top-level proceeding + its spawned descendants form the one case file; additional standalone proceedings are reported via ProceedingsSkipped and stay in the scenario. Planned dates come from the injected FristenrechnerService.Calculate; court-set/undated planned events are skipped + counted. - NewScenarioBuilderService gains a *FristenrechnerService dep (wired in cmd/server/main.go; nil in tests that don't promote). Handlers/routes: - GET /api/builder/scenarios/shared, POST /api/builder/scenarios/{id}/promote. Frontend: - builder-shares.ts — share modal (HLC user picker + current-shares list + revoke). - builder-promote.ts — 3-step wizard (Bestätigen → Parteien ergänzen → Akte-Metadaten) → POST /promote → navigate to /projects/{id}. - builder.ts — bucketed side panel (Aktiv / Geteilt mit mir / Als Projekt angelegt / Archiviert), read-only chrome (watermark + locked affordances) for shared/promoted scenarios, wired share + promote buttons, deep-link auto-load now covers shared scenarios. - procedures.tsx — enabled buttons, bucket containers, readonly watermark slot. - global.css — modal scaffold, share UI, promote wizard, buckets, readonly state. i18n.ts + i18n-keys.ts — DE+EN keys. Tests: TestScenarioBuilderPromote (live-DB) pins the transactional cascade + readonly-after-promote + re-promote rejection. go build/vet/test + bun build clean. Verified end-to-end via Playwright: Journey E (share → 2nd user read-only watermark + locked canvas, incl. deep-link) and Journey D (promote wizard 3 steps → project created with party → navigate → scenario flipped to promoted).
193 lines
11 KiB
TypeScript
193 lines
11 KiB
TypeScript
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";
|
|
|
|
// /tools/procedures — Litigation Builder (m/paliad#153 PRD §3).
|
|
//
|
|
// Replaces cronus's 4-tab catalog (U0-U4) with a persistence-backed
|
|
// builder shell. Server-rendered chrome is minimal — the page-header
|
|
// scenario picker, side panel, and canvas are all hydrated by
|
|
// `builder.ts` at boot. The builder loads scenarios from
|
|
// /api/builder/scenarios (B0 surface, t-paliad-340) and renders the
|
|
// per-proceeding triplets with the existing verfahrensablauf-core calc.
|
|
//
|
|
// B1 — Builder shell + cold-open mode + single triplet end-to-end.
|
|
// B2 — Multi-triplet stack + spawn nesting + per-event state machine.
|
|
// B3+ — event-triggered + Akte modes, sharing, promotion (head-gated).
|
|
|
|
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 page-builder">
|
|
<Sidebar currentPath="/tools/procedures" />
|
|
<BottomNav currentPath="/tools/procedures" />
|
|
|
|
<main>
|
|
<section className="tool-page builder-page">
|
|
<div className="container">
|
|
<div className="tool-header">
|
|
<h1 data-i18n="procedures.heading">Verfahren & Fristen</h1>
|
|
<p className="tool-subtitle" data-i18n="builder.subtitle">
|
|
Litigation Builder — Szenarien bauen, Verfahren stapeln, Fristen behalten.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Page header (PRD §3.1): scenario picker · save state · name · share · promote
|
|
· Akte picker · Stichtag input. B1 wires the scenario picker
|
|
+ name action + Stichtag + save indicator. Akte / share /
|
|
promote land at B4 / B5; the affordances render disabled in
|
|
B1 so the layout is stable across slices. */}
|
|
<section className="builder-pageheader" aria-label="Builder-Steuerung">
|
|
<div className="builder-pageheader-row">
|
|
<label className="builder-pageheader-field">
|
|
<span className="builder-pageheader-label" data-i18n="builder.header.scenario">Szenario:</span>
|
|
<select id="builder-scenario-picker" className="builder-scenario-picker" aria-label="Szenario wählen"></select>
|
|
</label>
|
|
<span id="builder-save-status" className="builder-save-status" aria-live="polite" data-state="idle">
|
|
<span data-i18n="builder.save.idle"> </span>
|
|
</span>
|
|
<span className="builder-pageheader-spacer"></span>
|
|
<button type="button" id="builder-rename-btn"
|
|
className="builder-action-btn builder-action-btn--secondary"
|
|
disabled
|
|
data-i18n="builder.action.rename">Benennen</button>
|
|
<button type="button" id="builder-share-btn"
|
|
className="builder-action-btn builder-action-btn--secondary"
|
|
disabled
|
|
data-i18n="builder.action.share">Teilen</button>
|
|
<button type="button" id="builder-promote-btn"
|
|
className="builder-action-btn builder-action-btn--primary"
|
|
disabled
|
|
data-i18n="builder.action.promote">Als Projekt anlegen</button>
|
|
</div>
|
|
<div className="builder-pageheader-row">
|
|
<label className="builder-pageheader-field">
|
|
<span className="builder-pageheader-label" data-i18n="builder.header.akte">Akte:</span>
|
|
<select id="builder-akte-picker" className="builder-akte-picker" disabled aria-label="Akte wählen">
|
|
<option value="" data-i18n="builder.akte.none">— ohne —</option>
|
|
</select>
|
|
</label>
|
|
<label className="builder-pageheader-field">
|
|
<span className="builder-pageheader-label" data-i18n="builder.header.stichtag">Stichtag:</span>
|
|
<input type="date" id="builder-stichtag-input" className="builder-stichtag-input"
|
|
defaultValue={today} aria-label="Stichtag" />
|
|
</label>
|
|
<label className="builder-pageheader-field builder-pageheader-field--grow">
|
|
<span className="builder-pageheader-label" data-i18n="builder.header.search">Suche:</span>
|
|
<input type="search" id="builder-search-input" className="builder-search-input"
|
|
data-i18n-placeholder="builder.search.placeholder"
|
|
placeholder="Ereignis, Szenario, Akte …"
|
|
autocomplete="off" spellcheck="false" />
|
|
</label>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Entry-mode radio (PRD §0.2, §2). B1 ships cold-open active;
|
|
event-triggered + akte ship at B3 / B4 and are disabled
|
|
here so the layout stays stable across slices. */}
|
|
<nav className="builder-modebar" role="tablist" aria-label="Einstieg">
|
|
<button type="button"
|
|
className="builder-mode is-active"
|
|
role="tab"
|
|
aria-selected="true"
|
|
data-mode="cold"
|
|
id="builder-mode-cold">
|
|
<span className="builder-mode-label" data-i18n="builder.mode.cold">Übersicht</span>
|
|
</button>
|
|
<button type="button"
|
|
className="builder-mode"
|
|
role="tab"
|
|
aria-selected="false"
|
|
data-mode="event"
|
|
id="builder-mode-event">
|
|
<span className="builder-mode-label" data-i18n="builder.mode.event">Ereignis</span>
|
|
</button>
|
|
<button type="button"
|
|
className="builder-mode"
|
|
role="tab"
|
|
aria-selected="false"
|
|
data-mode="akte"
|
|
id="builder-mode-akte">
|
|
<span className="builder-mode-label" data-i18n="builder.mode.akte">Aus Akte</span>
|
|
</button>
|
|
</nav>
|
|
|
|
{/* Two-column body: side panel (left, scenarios list) + canvas (right). */}
|
|
<div className="builder-body">
|
|
<aside className="builder-sidepanel" aria-label="Meine Szenarien">
|
|
<header className="builder-sidepanel-header">
|
|
<h2 className="builder-sidepanel-title" data-i18n="builder.panel.title">Meine Szenarien</h2>
|
|
<button type="button" id="builder-new-scenario-btn"
|
|
className="builder-sidepanel-newbtn"
|
|
data-i18n="builder.panel.new">+ Neues Szenario</button>
|
|
</header>
|
|
<div className="builder-sidepanel-bucket" data-bucket="active">
|
|
<h3 className="builder-bucket-label" data-i18n="builder.bucket.active">Aktiv</h3>
|
|
<ul className="builder-scenario-list" id="builder-scenario-list-active" aria-label="Aktive Szenarien"></ul>
|
|
</div>
|
|
{/* B5 — Geteilt mit mir / Als Projekt angelegt / Archiviert.
|
|
Each bucket hides itself when empty (builder.ts toggles
|
|
the hidden attribute). */}
|
|
<div className="builder-sidepanel-bucket" data-bucket="shared" id="builder-bucket-shared" hidden>
|
|
<h3 className="builder-bucket-label" data-i18n="builder.bucket.shared">Geteilt mit mir</h3>
|
|
<ul className="builder-scenario-list" id="builder-scenario-list-shared" aria-label="Mit mir geteilte Szenarien"></ul>
|
|
</div>
|
|
<div className="builder-sidepanel-bucket" data-bucket="promoted" id="builder-bucket-promoted" hidden>
|
|
<h3 className="builder-bucket-label" data-i18n="builder.bucket.promoted">Als Projekt angelegt</h3>
|
|
<ul className="builder-scenario-list" id="builder-scenario-list-promoted" aria-label="Promotete Szenarien"></ul>
|
|
</div>
|
|
<div className="builder-sidepanel-bucket" data-bucket="archived" id="builder-bucket-archived" hidden>
|
|
<h3 className="builder-bucket-label" data-i18n="builder.bucket.archived">Archiviert</h3>
|
|
<ul className="builder-scenario-list" id="builder-scenario-list-archived" aria-label="Archivierte Szenarien"></ul>
|
|
</div>
|
|
</aside>
|
|
|
|
<section className="builder-canvas-wrap" aria-label="Builder-Canvas">
|
|
{/* B5 — read-only watermark for shared / promoted scenarios.
|
|
builder.ts fills + unhides it when the active scenario
|
|
is not editable by the current user. */}
|
|
<div id="builder-readonly-watermark" className="builder-readonly-watermark" hidden></div>
|
|
<div id="builder-canvas" className="builder-canvas">
|
|
{/* Cold-open placeholder — replaced by triplet stack once a
|
|
scenario is loaded. */}
|
|
<div className="builder-empty" id="builder-empty">
|
|
<p className="builder-empty-headline" data-i18n="builder.empty.headline">
|
|
Noch kein Szenario geöffnet.
|
|
</p>
|
|
<p className="builder-empty-hint" data-i18n="builder.empty.hint">
|
|
Starte ein neues Szenario, wähle aus deiner Liste oder übernimm eine Akte (B4).
|
|
</p>
|
|
<button type="button" id="builder-cta-new" className="builder-cta-new"
|
|
data-i18n="builder.empty.cta">
|
|
Neues Szenario starten
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<Footer />
|
|
<PaliadinWidget />
|
|
<script src="/assets/procedures.js"></script>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|