t-paliad-258. m's verdict on t-paliad-251's rule UI: "too many options"
(4 'Oral hearings' across courts, etc.). Replace the full deadline_rules
catalog dropdown + sort selector with a binary model and unify the rule
display contract across every surface that prints a rule label.
Binary Rule field on the deadline form
- Auto (default): rule_id is derived from the chosen Type. The resolved
rule renders read-only as 'Auto | <Name · Citation>' next to the
field. No catalog picker, no sort options.
- Custom: free-text input. Stored as deadlines.custom_rule_text (new
nullable column, migration 122). Mutually exclusive with rule_id at
the persistence boundary.
- Toggle link flips between modes. Re-toggling to Auto re-resolves from
the current Type — no stale state.
Schema + service (additive)
- migration 122 adds paliad.deadlines.custom_rule_text (nullable).
Existing rows: empty custom_rule_text + non-null rule_id = Auto-
equivalent. Both NULL = "keine Regel" (consistent with today).
- models.Deadline.CustomRuleText + service SELECTs include the column.
- CreateDeadlineInput accepts custom_rule_text; the service drops it
when rule_id is set (catalog wins; simple invariant at the boundary).
- UpdateDeadlineInput grows a {RuleSet, RuleID, CustomRuleText} triple.
RuleSet=true is the discriminator so absent fields don't overwrite
the row (PATCH semantics). RuleID and CustomRuleText are mutually
exclusive in one request; service rejects "both set".
- EventListItem (the /api/events union) carries CustomRuleText so list
surfaces can render it.
Frontend: deadlines-new
- Drop the rule <select>, the by_proceeding/by_court/alpha sort
dropdown, the override-warning slot, and the collapsed-by-Regel Typ
view. Strip the (Rule→Type) auto-fill machinery — direction is now
one-way (Type → Auto-resolved Rule).
- Keep Type→Rule resolution: resolveAutoRuleForType picks the canonical
rule by project's proceeding, then jurisdiction match, then first
candidate. Same logic, just re-aimed at the read-only display.
- Standardtitel preserves the chain (event type → Auto rule label →
Custom text → proceeding → fallback) so the recipe still produces a
sensible title even when Custom is used.
Frontend: deadlines-detail
- Read-only display: catalog rule → Name · Citation, else
custom_rule_text + Custom badge, else legacy rule_code, else "—".
- Edit mode: mirror the create form with the Auto/Custom toggle.
enterEdit initialises the mode from the persisted deadline; Save
PATCHes with rule_set:true + the chosen rule pointer.
Rule-label addendum (m's 14:31 follow-up)
- Canonical contract everywhere: Name primary, Citation muted secondary
("Notice of Appeal · UPC.RoP.220.1"). Custom rules render the text
with a "Custom" pill.
- New frontend/src/client/rule-label.ts exports formatRuleLabel /
formatRuleLabelHTML / formatCustomRuleLabelHTML — one helper per
shape (plain text vs muted-citation HTML).
- Wired into: deadlines-new Auto display, deadlines-detail read +
Standardtitel, events.ts ruleDisplay (REGEL column on /events),
projects-detail.ts Fristen table, views/shape-list.ts generic
rule column.
- Verfahrensablauf (views/verfahrensablauf-core.ts) already renders
name + citation chip separately and matches the canonical pattern;
no change needed. Schriftsätze table is column-shaped (name + code
in distinct columns) and out of scope per the addendum.
CSS
- New .rule-mode-auto / .rule-mode-custom / .rule-label-* family.
- Drop the dead .rule-sort-select rule and the .event-type-collapsed*
family (retired with the catalog dropdown).
i18n
- DE+EN. Remove 10 stale keys (rule.none, autofill, autofill_inline,
mismatch, override, override_warn, sort.*). Add 6 (auto_no_match,
auto_pick_type, custom_badge, custom_placeholder,
mode.toggle_to_auto, mode.toggle_to_custom).
Build hygiene
- go build + go test ./internal/... clean.
- frontend bun build clean (2803 keys, scan clean).
Out of scope (per issue)
- Promoting Custom entries back to the catalog ("save as new rule").
- Filtering/searching custom_rule_text in deadline lists.
- Touching the event-type browse modal (Part 1 of #82 — that stays).
Files
- internal/db/migrations/122_deadlines_custom_rule_text.{up,down}.sql
- internal/models/models.go
- internal/services/deadline_service.go (Create+Update+SELECT)
- internal/services/event_service.go (union projection)
- frontend/src/client/rule-label.ts (new helper)
- frontend/src/client/deadlines-new.ts (rewrite)
- frontend/src/client/deadlines-detail.ts (Auto/Custom editor + display)
- frontend/src/client/events.ts (REGEL column)
- frontend/src/client/projects-detail.ts (Fristen table cell)
- frontend/src/client/views/shape-list.ts (generic rule column)
- frontend/src/client/i18n.ts + i18n-keys.ts (DE+EN delta)
- frontend/src/deadlines-new.tsx (strip dropdown+sort, add toggle)
- frontend/src/deadlines-detail.tsx (Auto/Custom edit slots)
- frontend/src/styles/global.css (rule-mode + rule-label families)
199 lines
11 KiB
TypeScript
199 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";
|
|
|
|
export function renderDeadlinesDetail(): 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="deadlines.detail.title">Frist — Paliad</title>
|
|
<link rel="stylesheet" href="/assets/global.css" />
|
|
</head>
|
|
<body className="has-sidebar">
|
|
<Sidebar currentPath="/events?type=deadline" />
|
|
<BottomNav currentPath="/events?type=deadline" />
|
|
|
|
<main>
|
|
<section className="tool-page">
|
|
<div className="container">
|
|
<a href="/events?type=deadline" className="back-link" data-i18n="deadlines.detail.back">← Zurück zur Fristenübersicht</a>
|
|
|
|
<div id="deadline-loading" className="entity-loading">
|
|
<p data-i18n="deadlines.detail.loading">Lädt…</p>
|
|
</div>
|
|
|
|
<div id="deadline-notfound" className="entity-empty" style="display:none">
|
|
<p data-i18n="deadlines.detail.notfound">Frist nicht gefunden oder keine Berechtigung.</p>
|
|
</div>
|
|
|
|
<div id="deadline-body" style="display:none">
|
|
<header className="entity-detail-header">
|
|
<div className="entity-detail-title-row">
|
|
<div className="entity-detail-title-col">
|
|
<h1 id="deadline-title-display" />
|
|
<input type="text" id="deadline-title-edit" className="entity-title-input" style="display:none" />
|
|
{/* t-paliad-251 Part 4 — Standardtitel button only
|
|
visible in edit mode; clicking replaces the
|
|
title with a default derived from the project
|
|
and the deadline's event types / rule. */}
|
|
<button
|
|
type="button"
|
|
id="deadline-title-default-btn"
|
|
className="btn-link-action"
|
|
style="display:none"
|
|
data-i18n="deadlines.field.title.default_btn"
|
|
>
|
|
Standardtitel
|
|
</button>
|
|
<div className="entity-detail-meta">
|
|
<span id="deadline-due-chip" className="frist-due-chip" />
|
|
<span id="deadline-status-chip" className="entity-status-chip" />
|
|
<span id="deadline-pending-approval-badge" className="approval-pending-badge" style="display:none" data-i18n="approvals.pending.badge" title="">
|
|
Wartet auf Genehmigung
|
|
</span>
|
|
<a id="deadline-project-link" className="entity-ref" href="#" />
|
|
<select id="deadline-project-edit" className="entity-ref-select" style="display:none" />
|
|
</div>
|
|
</div>
|
|
<div className="entity-detail-actions">
|
|
<button id="deadline-complete-btn" type="button" className="btn-primary btn-cta-lime btn-small" data-i18n="deadlines.detail.complete">
|
|
Als erledigt markieren
|
|
</button>
|
|
<button id="deadline-reopen-btn" type="button" className="btn-primary btn-cta-lime btn-small" style="display:none" data-i18n="deadlines.detail.reopen">
|
|
Wieder öffnen
|
|
</button>
|
|
<button id="deadline-withdraw-btn" type="button" className="btn-secondary btn-small" style="display:none" data-i18n="approvals.withdraw.cta">
|
|
Genehmigungsanfrage zurückziehen
|
|
</button>
|
|
<button id="deadline-edit-btn" className="btn-icon" type="button" aria-label="Bearbeiten" data-i18n-title="deadlines.detail.edit" title="Bearbeiten">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
|
</svg>
|
|
</button>
|
|
<button id="deadline-save-btn" className="btn-primary btn-cta-lime" type="button" style="display:none" data-i18n="deadlines.detail.save">
|
|
Speichern
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<section className="entity-tab-panel frist-detail-panel">
|
|
<dl className="frist-detail-list">
|
|
<dt data-i18n="deadlines.detail.due">Fälligkeitsdatum</dt>
|
|
<dd>
|
|
<span id="deadline-due-display" />
|
|
<input type="date" id="deadline-due-edit" style="display:none" />
|
|
</dd>
|
|
|
|
{/* m/paliad#56 — Verfahrenshandlung block.
|
|
Event type (parent concept) renders first; rule
|
|
sits beneath as the citation under that event
|
|
type. Editor splits them back into separate
|
|
pickers but the read-only stack reads as one
|
|
compound "Typ — Regel" surface. */}
|
|
<dt data-i18n="deadlines.field.event_type">Typ (optional)</dt>
|
|
<dd>
|
|
<span id="deadline-event-types-display">—</span>
|
|
<div id="deadline-event-types-edit" className="event-type-picker-host" style="display:none" />
|
|
</dd>
|
|
|
|
<dt data-i18n="deadlines.detail.rule">Regel</dt>
|
|
<dd>
|
|
<span id="deadline-rule-display">—</span>
|
|
{/* t-paliad-258 — Auto / Custom rule editor.
|
|
Mirrors /deadlines/new: read-only Auto display
|
|
(resolved from Type) or free-text Custom input,
|
|
with a toggle link. Hidden outside edit mode. */}
|
|
<div className="rule-edit-block" id="deadline-rule-edit" style="display:none">
|
|
<button
|
|
type="button"
|
|
id="deadline-rule-mode-toggle"
|
|
className="btn-link-action"
|
|
data-i18n="deadlines.field.rule.mode.toggle_to_custom"
|
|
>
|
|
Eigene Regel eingeben
|
|
</button>
|
|
<div className="rule-mode-auto" id="deadline-rule-auto-display">
|
|
<span className="form-hint-badge" data-i18n="deadlines.field.rule.auto_badge">Auto</span>
|
|
<span id="deadline-rule-auto-text" className="rule-auto-text">—</span>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
id="deadline-rule-custom-input"
|
|
className="rule-mode-custom"
|
|
style="display:none"
|
|
placeholder="z.B. interner Review-Termin"
|
|
data-i18n-placeholder="deadlines.field.rule.custom_placeholder"
|
|
maxLength={200}
|
|
/>
|
|
</div>
|
|
</dd>
|
|
|
|
<dt data-i18n="deadlines.detail.source">Quelle</dt>
|
|
<dd id="deadline-source-display" />
|
|
|
|
<dt data-i18n="deadlines.detail.notes">Notizen</dt>
|
|
<dd>
|
|
<span id="deadline-notes-display" />
|
|
<textarea id="deadline-notes-edit" rows={3} style="display:none" />
|
|
</dd>
|
|
|
|
<dt data-i18n="deadlines.detail.created">Angelegt</dt>
|
|
<dd id="deadline-created-display" />
|
|
|
|
<dt id="deadline-completed-row-label" data-i18n="deadlines.detail.completed" style="display:none">
|
|
Erledigt am
|
|
</dt>
|
|
<dd id="deadline-completed-display" style="display:none" />
|
|
</dl>
|
|
</section>
|
|
|
|
<section className="frist-notes-section">
|
|
<h2 className="frist-section-heading" data-i18n="notes.section.title">Notizen</h2>
|
|
<div id="notes-container" className="notiz-container" data-parent-type="deadline" />
|
|
</section>
|
|
|
|
<div className="entity-detail-footer" id="deadline-delete-wrap" style="display:none">
|
|
<button id="deadline-delete-btn" className="btn-danger" type="button" data-i18n="deadlines.detail.delete">
|
|
Frist löschen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="modal-overlay" id="deadline-delete-modal" style="display:none">
|
|
<div className="modal-card">
|
|
<div className="modal-header">
|
|
<h2 data-i18n="deadlines.detail.delete.confirm.title">Frist wirklich löschen?</h2>
|
|
<button className="modal-close" id="deadline-delete-modal-close" type="button">×</button>
|
|
</div>
|
|
<p data-i18n="deadlines.detail.delete.confirm.body">
|
|
Die Frist wird endgültig entfernt. Der Eintrag im Verlauf der Akte bleibt erhalten.
|
|
</p>
|
|
<div className="form-actions">
|
|
<button type="button" className="btn-cancel" id="deadline-delete-modal-cancel" data-i18n="deadlines.detail.delete.confirm.cancel">Abbrechen</button>
|
|
<button type="button" className="btn-danger" id="deadline-delete-modal-confirm" data-i18n="deadlines.detail.delete.confirm.ok">Löschen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<Footer />
|
|
<PaliadinWidget />
|
|
<script src="/assets/deadlines-detail.js"></script>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|