diff --git a/frontend/src/client/deadlines-new.ts b/frontend/src/client/deadlines-new.ts index 78b4e8b..9859228 100644 --- a/frontend/src/client/deadlines-new.ts +++ b/frontend/src/client/deadlines-new.ts @@ -1,10 +1,23 @@ import { initI18n, t, tDyn } from "./i18n"; import { initSidebar } from "./sidebar"; -import { attachEventTypePicker, type PickerHandle } from "./event-types"; +import { + attachEventTypePicker, + eventTypeLabel, + fetchEventTypes, + type EventType, + type PickerHandle, +} from "./event-types"; import { projectIndent } from "./project-indent"; let eventTypePicker: PickerHandle | null = null; let currentUserAdmin = false; +let eventTypesByID = new Map(); +// expandedOverride flips to true when the user clicks "Anderen Typ +// wählen" on the collapsed inline summary. Sticky for the rest of the +// form session — cleared only when the user reverts the rule to "Keine +// Regel". When true, the picker stays visible regardless of whether +// the chip matches the rule's canonical default. +let expandedOverride = false; interface Project { id: string; @@ -100,33 +113,55 @@ async function loadRules() { } } -// t-paliad-165 — refresh the autofill hint + mismatch warning state. The -// hint shows when the picker exactly matches the current rule's -// suggestion (so the chip is "vorgegeben durch Regel"). The warning -// shows when the user has picked event_types that don't include the -// rule's canonical default (override is fine, just call it out). -function refreshRuleHints(): void { - const hint = document.getElementById("deadline-event-type-rule-hint"); +// t-paliad-165 follow-up — drive the collapsed/expanded view of the Typ +// picker. The two modes are mutually exclusive: +// +// collapsed: rule selected + canonical event_type known + picker +// contains exactly [default] + user hasn't clicked "Anderen Typ +// wählen". Hides the chip cluster, surfaces a single inline +// summary "Klageerwiderung (vorgegeben durch Regel)" + an +// override link. +// +// expanded: every other case — no rule, no default for the rule, +// picker has been edited, or expandedOverride is sticky after the +// user clicked the override link. Picker visible; mismatch warning +// surfaces yellow when the rule expected a different event_type. +function refreshRuleView(): void { + const collapsed = document.getElementById("deadline-event-type-collapsed"); + const collapsedLabel = document.getElementById("deadline-event-type-collapsed-label"); + const pickerHost = document.getElementById("deadline-event-types"); const warn = document.getElementById("deadline-event-type-rule-mismatch"); - if (!hint || !warn) return; + if (!collapsed || !collapsedLabel || !pickerHost || !warn) return; const ruleID = (document.getElementById("deadline-rule") as HTMLSelectElement | null)?.value || ""; const rule = ruleID ? rulesByID.get(ruleID) : undefined; const expected = rule?.concept_default_event_type_id ?? null; const picked = eventTypePicker?.getIDs() ?? []; - if (!ruleID || !expected) { - hint.style.display = "none"; + const pickerMatchesDefault = + expected !== null && picked.length === 1 && picked[0] === expected; + const wantsCollapsed = + !expandedOverride && ruleID !== "" && expected !== null && pickerMatchesDefault; + + if (wantsCollapsed) { + const et = eventTypesByID.get(expected!); + collapsedLabel.textContent = et ? eventTypeLabel(et) : ""; + collapsed.style.display = ""; + pickerHost.style.display = "none"; warn.style.display = "none"; return; } - if (picked.length === 1 && picked[0] === expected) { - hint.style.display = ""; + + collapsed.style.display = "none"; + pickerHost.style.display = ""; + // Mismatch warning: rule expected an event_type AND the picker + // doesn't contain it. (When the picker is empty + no override, no + // warning — user is free to leave it blank.) + if (expected && picked.length > 0 && !picked.includes(expected)) { + warn.style.display = ""; + } else { warn.style.display = "none"; - return; } - hint.style.display = "none"; - warn.style.display = picked.includes(expected) ? "none" : ""; } // applyRuleAutoFill replaces the picker silently when it still reflects @@ -139,6 +174,12 @@ function applyRuleAutoFill(): void { const expected = rule?.concept_default_event_type_id ?? null; const current = eventTypePicker.getIDs(); + // Reset the override on transition to "Keine Regel" — fresh form + // session. Otherwise expandedOverride stays sticky. + if (ruleID === "") { + expandedOverride = false; + } + const pickerStillReflectsLastSuggestion = lastAutoFilledEventTypeID !== null && current.length === 1 && @@ -152,11 +193,11 @@ function applyRuleAutoFill(): void { } } else if (pickerStillReflectsLastSuggestion) { // New rule has no canonical event_type — clear the stale auto-fill - // so the picker doesn't carry a hint from the old rule. + // so the picker doesn't carry a chip from the old rule. eventTypePicker.setIDs([]); lastAutoFilledEventTypeID = null; } - refreshRuleHints(); + refreshRuleView(); } function initBackLinks() { @@ -307,15 +348,36 @@ document.addEventListener("DOMContentLoaded", async () => { if (pickerHost) { eventTypePicker = attachEventTypePicker(pickerHost, { currentUserAdmin, - onChange: () => refreshRuleHints(), + onChange: () => refreshRuleView(), }); } + // t-paliad-165 follow-up — preload event_types so the collapsed + // summary can render the type's label inline without an extra round + // trip when the user picks a Regel. + fetchEventTypes() + .then((types) => { + eventTypesByID = new Map(types.map((et) => [et.id, et])); + refreshRuleView(); + }) + .catch(() => {/* non-fatal — collapsed view falls back to empty label */}); // t-paliad-165 — Regel change auto-fills the Typ chip from the rule's // concept's canonical event_type, when the picker hasn't been // manually edited away from the previous rule's suggestion. document.getElementById("deadline-rule")?.addEventListener("change", () => { applyRuleAutoFill(); }); + // "Anderen Typ wählen" — sticky expanded mode so the picker stays + // visible even when the chip still matches the rule's default. + document.getElementById("deadline-event-type-override-btn")?.addEventListener("click", () => { + expandedOverride = true; + refreshRuleView(); + // Move focus into the picker's search box so the user can type + // immediately without an extra click. + const search = document.querySelector( + "#deadline-event-types .event-type-search", + ); + search?.focus(); + }); // Wire approval-hint refresh: on first render + on project change. void refreshApprovalHint(); document.getElementById("deadline-project")?.addEventListener("change", () => { diff --git a/frontend/src/client/i18n.ts b/frontend/src/client/i18n.ts index 338409a..eea566a 100644 --- a/frontend/src/client/i18n.ts +++ b/frontend/src/client/i18n.ts @@ -742,7 +742,9 @@ const translations: Record> = { "deadlines.field.rule": "Regel (optional)", "deadlines.field.rule.none": "Keine Regel", "deadlines.field.rule.autofill": "Typ vorgegeben durch Regel — entfernen, um zu überschreiben.", + "deadlines.field.rule.autofill_inline": " (vorgegeben durch Regel)", "deadlines.field.rule.mismatch": "Hinweis: Typ widerspricht Regel — Sie haben den Typ überschrieben.", + "deadlines.field.rule.override": "Anderen Typ wählen", "deadlines.field.notes": "Notizen (optional)", "deadlines.field.notes.placeholder": "Hinweise, Verweise, n\u00e4chste Schritte\u2026", "deadlines.error.required": "Akte, Titel und F\u00e4lligkeitsdatum sind Pflichtfelder.", @@ -2949,7 +2951,9 @@ const translations: Record> = { "deadlines.field.rule": "Rule (optional)", "deadlines.field.rule.none": "No rule", "deadlines.field.rule.autofill": "Type set by rule — remove to override.", + "deadlines.field.rule.autofill_inline": " (set by rule)", "deadlines.field.rule.mismatch": "Note: type contradicts rule — you have overridden the type.", + "deadlines.field.rule.override": "Choose another type", "deadlines.field.notes": "Notes (optional)", "deadlines.field.notes.placeholder": "References, hints, next steps\u2026", "deadlines.error.required": "Matter, title and due date are required.", diff --git a/frontend/src/deadlines-new.tsx b/frontend/src/deadlines-new.tsx index 3596662..eff6d7d 100644 --- a/frontend/src/deadlines-new.tsx +++ b/frontend/src/deadlines-new.tsx @@ -55,21 +55,42 @@ export function renderDeadlinesNew(): string { /> -
+
-
- {/* t-paliad-165 — soft hint when the Regel auto-populated - the chip, and a soft warning when the user picked a - Regel + Typ combination that contradicts the rule's - concept. Both rendered yellow, never blocking. */} - + + +  (vorgegeben durch Regel) + + +
+
+ {/* Soft warning when the user is in expanded mode AND + has picked an event_type that doesn't include the + rule's canonical default. Reuses the existing + yellow form-hint--warning style; never blocking. */}