Closes the procedural-events rename loop opened by m/paliad#93. The admin surface now lives under its canonical URL; the legacy paths remain reachable for one deprecation cycle via 301 redirects so bookmarks, audit-log entries, and curl scripts keep working. * internal/handlers/handlers.go — - Registers the 12 canonical routes under /admin/procedural-events* (page paths and JSON API). Same handlers — just the new URL slot. - Registers the 12 legacy /admin/rules* routes as 301 redirects. * internal/handlers/admin_rules.go — - redirectToProceduralEvents(dst) — fixed-destination redirect for paths without an {id}. - redirectToProceduralEventEdit — page redirect carrying the {id}. - redirectToProceduralEventAPI(suffix) — JSON API redirect carrying {id} + optional suffix (/clone-as-draft, /publish, /archive, /restore, /audit, /preview). Query string is preserved on every redirect. - All three helpers add the IETF Deprecation header + a Link header pointing at the successor-version path. * frontend internal nav + URL strings — Sidebar.tsx, admin.tsx, admin-rules-list.tsx, admin-rules-edit.tsx, client/admin-rules-list.ts, client/admin-rules-edit.ts: every `/admin/rules*` reference flipped to `/admin/procedural-events*`. In-app navigation now hits the canonical paths directly without a redirect round-trip; external callers keep working via the 301s. * frontend .tsx i18n rebind — 9 admin .tsx i18n bindings rebound to the canonical `admin.procedural_events.*` keys that already exist as aliases in i18n.ts (per Slice A from t-paliad-262). Specifically: admin.rules.list.title → admin.procedural_events.list.title admin.rules.list.heading → admin.procedural_events.list.heading admin.rules.list.new → admin.procedural_events.list.new admin.rules.col.submission_code → admin.procedural_events.col.code admin.rules.edit.title → admin.procedural_events.edit.title admin.rules.edit.breadcrumb → admin.procedural_events.edit.breadcrumb admin.rules.edit.field.submission_code → admin.procedural_events.edit.field.code admin.rules.edit.field.event_type → admin.procedural_events.edit.field.event_kind admin.rules.edit.field.parent → admin.procedural_events.edit.field.parent The remaining ~142 admin.rules.* keys do NOT yet have procedural_events aliases. Migrating them is a follow-up slice — each needs a new alias entry in i18n.ts (DE + EN) before the .tsx reference can be flipped. The 9 keys touched here are the most visible (page titles + edit-page field labels) so the admin UI immediately reads as "Verfahrensschritte" everywhere. * frontend/src/client/i18n.ts header comment updated to reflect that the URL rename has shipped (Slice B.6 done) and to flag the remaining i18n-key migration as the next step. Scope (documented, paliadin authorised): - "go everything" applied: backend routes + frontend nav + .tsx rebind of the 9 keys whose canonical aliases exist. - Full migration of all 142 admin.rules.* keys deferred — would require seeding ~142 new alias entries in i18n.ts (DE + EN) plus another 142 .tsx rebinds. Out of scope for tonight; flag as follow-up `feat(i18n): finish admin.rules.* → admin.procedural_events.* alias migration`. - 12 legacy /admin/rules routes still hit a handler (the redirect helper) — they don't 404 yet. Once a deprecation window passes with no traffic on the old paths, a future slice can drop them outright. Build + vet clean. TestMigrations_NoDuplicateSlot passes. This concludes the m/paliad#93 procedural-events rename slice train (Slices A through B.6). curie stays parked persistently for any follow-up the deploy / monitor cycle surfaces.
353 lines
21 KiB
TypeScript
353 lines
21 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";
|
|
|
|
// /admin/procedural-events/{id}/edit — Slice 11b (t-paliad-192). Form for the full
|
|
// 37-column rule row plus a side panel with the preview widget and the
|
|
// audit-log timeline. Lifecycle action bar at the bottom adapts to the
|
|
// rule's current state (draft/published/archived). Every write goes
|
|
// through a reason modal that enforces the ≥10-char rule from Slice 11a
|
|
// edge case #4.
|
|
//
|
|
// The id of the rule is parsed from the URL path on hydration —
|
|
// frontend never reads it from a server-injected blob, so the static
|
|
// HTML shell is reusable for every rule. condition_expr ships with a
|
|
// raw JSON textarea + a simple AND/OR/NOT tree-builder (toggle).
|
|
export function renderAdminRulesEdit(): string {
|
|
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="admin.procedural_events.edit.title">Regel bearbeiten — Paliad</title>
|
|
<link rel="stylesheet" href="/assets/global.css" />
|
|
</head>
|
|
<body className="has-sidebar">
|
|
<Sidebar currentPath="/admin/procedural-events" />
|
|
<BottomNav currentPath="/admin/procedural-events" />
|
|
|
|
<main>
|
|
<section className="tool-page">
|
|
<div className="container">
|
|
<div className="tool-header admin-rules-edit-header">
|
|
<div>
|
|
<p className="admin-rules-breadcrumb">
|
|
<a href="/admin/procedural-events" data-i18n="admin.procedural_events.edit.breadcrumb">← Regeln verwalten</a>
|
|
</p>
|
|
<h1 id="rules-edit-heading" data-i18n="admin.rules.edit.heading.loading">Regel laden...</h1>
|
|
<div className="admin-rules-edit-meta">
|
|
<span id="rules-edit-lifecycle" className="admin-rules-pill admin-rules-pill-draft" />
|
|
<span id="rules-edit-id" className="admin-rules-edit-uuid" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="rules-edit-feedback" className="form-msg" style="display:none" />
|
|
|
|
<div className="admin-rules-edit-grid">
|
|
<form id="rules-edit-form" className="entity-form admin-rules-edit-form" autocomplete="off">
|
|
<fieldset className="admin-rules-fieldset">
|
|
<legend data-i18n="admin.rules.edit.section.identity">Identität</legend>
|
|
<div className="admin-rules-edit-row">
|
|
<div className="form-field">
|
|
<label htmlFor="f-name" data-i18n="admin.rules.edit.field.name">Name (DE)</label>
|
|
<input type="text" id="f-name" className="admin-rules-input" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-name-en" data-i18n="admin.rules.edit.field.name_en">Name (EN)</label>
|
|
<input type="text" id="f-name-en" className="admin-rules-input" />
|
|
</div>
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-description" data-i18n="admin.rules.edit.field.description">Beschreibung</label>
|
|
<textarea id="f-description" className="admin-rules-input" rows={2} />
|
|
</div>
|
|
<div className="admin-rules-edit-row">
|
|
<div className="form-field">
|
|
<label htmlFor="f-submission-code" data-i18n="admin.procedural_events.edit.field.code">Submission Code / Einreichung-Kennung</label>
|
|
<input type="text" id="f-submission-code" className="admin-rules-input" readonly placeholder="z. B. upc.inf.cfi.soc" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-rule-code" data-i18n="admin.rules.edit.field.rule_code">Rechtsgrundlage (Kurzform)</label>
|
|
<input type="text" id="f-rule-code" className="admin-rules-input" placeholder="z. B. RoP.151" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-legal-source" data-i18n="admin.rules.edit.field.legal_source">Rechtsgrundlage (Langform)</label>
|
|
<input type="text" id="f-legal-source" className="admin-rules-input" placeholder="z. B. UPC.RoP.151" />
|
|
</div>
|
|
</div>
|
|
</fieldset>
|
|
|
|
<fieldset className="admin-rules-fieldset">
|
|
<legend data-i18n="admin.rules.edit.section.proceeding">Verfahren & Trigger</legend>
|
|
<div className="admin-rules-edit-row">
|
|
<div className="form-field">
|
|
<label htmlFor="f-proceeding" data-i18n="admin.rules.edit.field.proceeding">Verfahrenstyp</label>
|
|
<select id="f-proceeding" className="admin-rules-select">
|
|
<option value="" data-i18n="admin.rules.edit.field.proceeding.none">—</option>
|
|
</select>
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-trigger" data-i18n="admin.rules.edit.field.trigger">Trigger-Ereignis</label>
|
|
<select id="f-trigger" className="admin-rules-select">
|
|
<option value="" data-i18n="admin.rules.edit.field.trigger.none">—</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div className="admin-rules-edit-row">
|
|
<div className="form-field">
|
|
<label htmlFor="f-parent" data-i18n="admin.procedural_events.edit.field.parent">Parent-Regel (UUID)</label>
|
|
<input type="text" id="f-parent" className="admin-rules-input" placeholder="UUID oder leer" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-concept" data-i18n="admin.rules.edit.field.concept">Konzept (UUID)</label>
|
|
<input type="text" id="f-concept" className="admin-rules-input" placeholder="UUID oder leer" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-sequence" data-i18n="admin.rules.edit.field.sequence_order">Reihenfolge</label>
|
|
<input type="number" id="f-sequence" className="admin-rules-input" min="0" />
|
|
</div>
|
|
</div>
|
|
</fieldset>
|
|
|
|
<fieldset className="admin-rules-fieldset">
|
|
<legend data-i18n="admin.rules.edit.section.timing">Berechnung</legend>
|
|
<div className="admin-rules-edit-row">
|
|
<div className="form-field">
|
|
<label htmlFor="f-duration" data-i18n="admin.rules.edit.field.duration_value">Dauer</label>
|
|
<input type="number" id="f-duration" className="admin-rules-input" min="0" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-duration-unit" data-i18n="admin.rules.edit.field.duration_unit">Einheit</label>
|
|
<select id="f-duration-unit" className="admin-rules-select">
|
|
<option value="days">days</option>
|
|
<option value="weeks">weeks</option>
|
|
<option value="months">months</option>
|
|
<option value="working_days">working_days</option>
|
|
</select>
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-timing" data-i18n="admin.rules.edit.field.timing">Timing</label>
|
|
<select id="f-timing" className="admin-rules-select">
|
|
<option value="">—</option>
|
|
<option value="after">after</option>
|
|
<option value="before">before</option>
|
|
</select>
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-combine-op" data-i18n="admin.rules.edit.field.combine_op">Combine-Op</label>
|
|
<select id="f-combine-op" className="admin-rules-select">
|
|
<option value="">—</option>
|
|
<option value="max">max</option>
|
|
<option value="min">min</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div className="admin-rules-edit-row">
|
|
<div className="form-field">
|
|
<label htmlFor="f-alt-duration" data-i18n="admin.rules.edit.field.alt_duration_value">Alt-Dauer</label>
|
|
<input type="number" id="f-alt-duration" className="admin-rules-input" min="0" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-alt-duration-unit" data-i18n="admin.rules.edit.field.alt_duration_unit">Alt-Einheit</label>
|
|
<select id="f-alt-duration-unit" className="admin-rules-select">
|
|
<option value="">—</option>
|
|
<option value="days">days</option>
|
|
<option value="weeks">weeks</option>
|
|
<option value="months">months</option>
|
|
<option value="working_days">working_days</option>
|
|
</select>
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-alt-rule-code" data-i18n="admin.rules.edit.field.alt_rule_code">Alt-Rule-Code</label>
|
|
<input type="text" id="f-alt-rule-code" className="admin-rules-input" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-anchor-alt" data-i18n="admin.rules.edit.field.anchor_alt">Alt-Anchor</label>
|
|
<input type="text" id="f-anchor-alt" className="admin-rules-input" />
|
|
</div>
|
|
</div>
|
|
</fieldset>
|
|
|
|
<fieldset className="admin-rules-fieldset">
|
|
<legend data-i18n="admin.rules.edit.section.party">Partei & Ereignis</legend>
|
|
<div className="admin-rules-edit-row">
|
|
<div className="form-field">
|
|
<label htmlFor="f-primary-party" data-i18n="admin.rules.edit.field.primary_party">Primäre Partei</label>
|
|
<input type="text" id="f-primary-party" className="admin-rules-input" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-event-type" data-i18n="admin.procedural_events.edit.field.event_kind">Event-Typ (frei)</label>
|
|
<input type="text" id="f-event-type" className="admin-rules-input" />
|
|
</div>
|
|
</div>
|
|
</fieldset>
|
|
|
|
<fieldset className="admin-rules-fieldset">
|
|
<legend data-i18n="admin.rules.edit.section.display">Anzeige & Notizen</legend>
|
|
<div className="form-field">
|
|
<label htmlFor="f-notes" data-i18n="admin.rules.edit.field.deadline_notes">Hinweise (DE)</label>
|
|
<textarea id="f-notes" className="admin-rules-input" rows={2} />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-notes-en" data-i18n="admin.rules.edit.field.deadline_notes_en">Hinweise (EN)</label>
|
|
<textarea id="f-notes-en" className="admin-rules-input" rows={2} />
|
|
</div>
|
|
</fieldset>
|
|
|
|
<fieldset className="admin-rules-fieldset">
|
|
<legend data-i18n="admin.rules.edit.section.lifecycle">Priorität & Flags</legend>
|
|
<div className="admin-rules-edit-row">
|
|
<div className="form-field">
|
|
<label htmlFor="f-priority" data-i18n="admin.rules.edit.field.priority">Priorität</label>
|
|
<select id="f-priority" className="admin-rules-select">
|
|
<option value="mandatory">mandatory</option>
|
|
<option value="recommended">recommended</option>
|
|
<option value="optional">optional</option>
|
|
<option value="informational">informational</option>
|
|
</select>
|
|
</div>
|
|
<div className="form-field admin-rules-checkbox-field">
|
|
<label>
|
|
<input type="checkbox" id="f-is-court-set" />
|
|
<span data-i18n="admin.rules.edit.field.is_court_set">Gerichtlich gesetzt</span>
|
|
</label>
|
|
</div>
|
|
<div className="form-field admin-rules-checkbox-field">
|
|
<label>
|
|
<input type="checkbox" id="f-is-spawn" />
|
|
<span data-i18n="admin.rules.edit.field.is_spawn">Spawn</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div className="admin-rules-edit-row" id="f-spawn-row" style="display:none">
|
|
<div className="form-field">
|
|
<label htmlFor="f-spawn-label" data-i18n="admin.rules.edit.field.spawn_label">Spawn-Label</label>
|
|
<input type="text" id="f-spawn-label" className="admin-rules-input" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="f-spawn-proceeding" data-i18n="admin.rules.edit.field.spawn_proceeding">Spawn-Verfahren</label>
|
|
<select id="f-spawn-proceeding" className="admin-rules-select">
|
|
<option value="" data-i18n="admin.rules.edit.field.spawn_proceeding.none">—</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</fieldset>
|
|
|
|
<fieldset className="admin-rules-fieldset">
|
|
<legend data-i18n="admin.rules.edit.section.condition">Bedingung (condition_expr)</legend>
|
|
<p className="admin-rules-hint" data-i18n="admin.rules.edit.field.condition_hint">
|
|
JSON-Grammatik: <code>{"flag":"name"}</code> · <code>{"op":"and|or","args":[...]}</code> · <code>{"op":"not","args":[...]}</code>
|
|
</p>
|
|
<div className="form-field">
|
|
<textarea id="f-condition-expr" className="admin-rules-input admin-rules-code-input" rows={5} placeholder='z. B. {"flag":"with_ccr"}' />
|
|
<p className="admin-rules-hint" id="f-condition-msg" />
|
|
</div>
|
|
</fieldset>
|
|
</form>
|
|
|
|
<aside className="admin-rules-edit-side">
|
|
{/* Preview widget */}
|
|
<div className="admin-rules-edit-card">
|
|
<h3 data-i18n="admin.rules.edit.preview.heading">Preview</h3>
|
|
<p className="admin-rules-hint" data-i18n="admin.rules.edit.preview.hint">
|
|
Nur für Drafts. Berechnet die Fristenkette mit dieser Draft-Regel anstelle der publizierten Variante.
|
|
</p>
|
|
<div className="form-field">
|
|
<label htmlFor="preview-trigger-date" data-i18n="admin.rules.edit.preview.trigger_date">Trigger-Datum</label>
|
|
<input type="date" lang="de" id="preview-trigger-date" className="admin-rules-input" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="preview-flags" data-i18n="admin.rules.edit.preview.flags">Flags (komma-separiert)</label>
|
|
<input type="text" id="preview-flags" className="admin-rules-input" placeholder="z. B. with_ccr,is_appeal" />
|
|
</div>
|
|
<button type="button" id="preview-run" className="btn-secondary" data-i18n="admin.rules.edit.preview.run">
|
|
Preview berechnen
|
|
</button>
|
|
<div id="preview-result" className="admin-rules-preview-result" style="display:none" />
|
|
</div>
|
|
|
|
{/* Audit-log timeline */}
|
|
<div className="admin-rules-edit-card">
|
|
<h3 data-i18n="admin.rules.edit.audit.heading">Audit-Log</h3>
|
|
<ol id="rules-edit-audit" className="admin-rules-audit-list">
|
|
<li className="admin-rules-loading" data-i18n="admin.rules.edit.audit.loading">Lade...</li>
|
|
</ol>
|
|
<button type="button" id="audit-loadmore" className="btn-secondary" style="display:none" data-i18n="admin.rules.edit.audit.loadmore">
|
|
Weitere laden
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
|
|
{/* Action bar */}
|
|
<div className="admin-rules-actionbar">
|
|
<button type="button" id="action-save-draft" className="btn-primary" style="display:none" data-i18n="admin.rules.edit.action.save_draft">
|
|
Draft speichern
|
|
</button>
|
|
<button type="button" id="action-publish" className="btn-primary" style="display:none" data-i18n="admin.rules.edit.action.publish">
|
|
Publish
|
|
</button>
|
|
<button type="button" id="action-clone" className="btn-secondary" style="display:none" data-i18n="admin.rules.edit.action.clone">
|
|
Als Draft klonen
|
|
</button>
|
|
<button type="button" id="action-archive" className="btn-secondary" style="display:none" data-i18n="admin.rules.edit.action.archive">
|
|
Archivieren
|
|
</button>
|
|
<button type="button" id="action-restore" className="btn-secondary" style="display:none" data-i18n="admin.rules.edit.action.restore">
|
|
Wiederherstellen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
{/* Reason modal — shared for every lifecycle action. Action-specific
|
|
body text is set by the client at open time. */}
|
|
<div className="modal-overlay" id="rules-action-modal" style="display:none">
|
|
<div className="modal-card">
|
|
<div className="modal-header">
|
|
<h2 id="rules-action-modal-title">Aktion bestätigen</h2>
|
|
<button className="modal-close" id="rules-action-modal-close" type="button" aria-label="Close">×</button>
|
|
</div>
|
|
<p id="rules-action-modal-body" className="invite-modal-body" />
|
|
<form id="rules-action-modal-form" className="entity-form" autocomplete="off">
|
|
<div className="form-field">
|
|
<label htmlFor="rules-action-modal-reason" data-i18n="admin.rules.modal.reason">Grund</label>
|
|
<textarea
|
|
id="rules-action-modal-reason"
|
|
className="admin-rules-input"
|
|
rows={3}
|
|
required
|
|
minlength={10}
|
|
/>
|
|
<p className="admin-rules-hint" data-i18n="admin.rules.modal.reason.hint">
|
|
Mindestens 10 Zeichen.
|
|
</p>
|
|
</div>
|
|
<p className="form-msg" id="rules-action-modal-msg" style="display:none" />
|
|
<div className="form-actions">
|
|
<button type="button" className="btn-cancel" id="rules-action-modal-cancel" data-i18n="common.cancel">Abbrechen</button>
|
|
<button type="submit" className="btn-primary" id="rules-action-modal-submit" data-i18n="admin.rules.modal.confirm">
|
|
Bestätigen
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<Footer />
|
|
<PaliadinWidget />
|
|
<script src="/assets/admin-rules-edit.js"></script>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|