Workstream B frontend sweep — matches mig 098 + the Go sweep. The
/admin/rules surfaces now distinguish submission_code (the rule's
filing identifier within a proceeding, e.g. upc.inf.cfi.soc) from
rule_code (the legal citation, e.g. RoP.013.1).
Admin rules list (/admin/rules):
- Column header renamed "Code" → "Submission Code / Einreichung-Kennung"
- New "Rechtsgrundlage" column shows rule_code alongside the submission
code; the old single-column fallback (rule_code || code) is gone.
- Filter-search placeholder updated to "Name, Submission Code,
Rechtsgrundlage…"
- Rule interface: code → submission_code field.
Admin rules edit (/admin/rules/{id}/edit):
- f-code → f-submission-code; input is now read-only with a
upc.inf.cfi.soc-style placeholder (consistent with the backend
RulePatch which doesn't allow editing the submission code).
- Labels reframe rule_code as "Rechtsgrundlage (Kurzform)" and
legal_source as "Rechtsgrundlage (Langform)" so the legal-citation
pair is named consistently with the list column.
- Rule interface: code → submission_code field.
i18n: new keys admin.rules.col.submission_code,
admin.rules.col.legal_citation, admin.rules.edit.field.submission_code
in both DE + EN; old admin.rules.col.code + admin.rules.edit.field.code
removed.
bun run build clean.
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/rules/{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.rules.edit.title">Regel bearbeiten — Paliad</title>
|
|
<link rel="stylesheet" href="/assets/global.css" />
|
|
</head>
|
|
<body className="has-sidebar">
|
|
<Sidebar currentPath="/admin/rules" />
|
|
<BottomNav currentPath="/admin/rules" />
|
|
|
|
<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/rules" data-i18n="admin.rules.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.rules.edit.field.submission_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.rules.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.rules.edit.field.event_type">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>
|
|
);
|
|
}
|