feat(verfahrensablauf): re-surface hidden optional events — show-hidden toggle + un-hide chip (t-paliad-290)
m/paliad#122. atlas's #96 Slice A added per-card 'Überspringen' but no un-skip path — hidden cards just disappeared from the timeline. This adds the missing return path: - CalcOptions.IncludeHidden (default false) tells the calculator to re-surface skipRules entries as faded rows instead of dropping them. When true, the rule renders with UIDeadline.IsHidden=true and the descendant-suppression cascade is bypassed so children compute their dates off the un-suppressed parent. - UIResponse.HiddenCount always reflects the projection's hide count (gate-passed rules whose submission_code is in skipRules) so the "Ausgeblendete (N)" badge stays accurate regardless of toggle state. - /tools/verfahrensablauf gets a "Ausgeblendete anzeigen" checkbox next to the perspective + appellant selectors. URL-driven (?show_hidden=1) so the state is shareable and survives reload. The row hides itself on projections with zero hidden cards. - Hidden cards render via .timeline-item--hidden / .fr-col-item--hidden (opacity 0.55 + dotted border, mirroring the existing --skipped fade) and carry an inline "Wieder einblenden" chip. Clicking the chip removes the skip choice via the page's existing attachEventCardChoices remove callback (URL state + recalc included) and runs through a new delegated handler in event-card-choices.ts. - 3 new i18n keys (DE+EN): choices.show_hidden.label, choices.show_hidden.count, choices.unhide.chip. The skip-choice storage shape (paliad.project_event_choices, atlas's table) is unchanged — un-hide is just a delete of the skip row. Tests: 3 new bun-test cases pin the chip contract (emits on isHidden= true with submission_code, suppressed otherwise); go test ./internal/... + bun run build clean.
This commit is contained in:
@@ -325,6 +325,10 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
"choices.include_ccr.chip": "mit Nichtigkeitswiderklage",
|
||||
"choices.reset": "Auswahl zurücksetzen",
|
||||
"choices.commit.error": "Konnte Auswahl nicht speichern",
|
||||
// t-paliad-290 (m/paliad#122) — re-surface hidden optional cards.
|
||||
"choices.show_hidden.label": "Ausgeblendete anzeigen",
|
||||
"choices.show_hidden.count": "Ausgeblendete ({n})",
|
||||
"choices.unhide.chip": "Wieder einblenden",
|
||||
// Trigger-event mode (PR-2 \u2014 youpc-parity)
|
||||
"deadlines.mode.procedure": "Verfahrensablauf",
|
||||
"deadlines.mode.event": "Was kommt nach\u2026",
|
||||
@@ -3422,6 +3426,10 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
"choices.include_ccr.chip": "with nullity counterclaim",
|
||||
"choices.reset": "Reset choice",
|
||||
"choices.commit.error": "Could not save selection",
|
||||
// t-paliad-290 (m/paliad#122) — re-surface hidden optional cards.
|
||||
"choices.show_hidden.label": "Show hidden",
|
||||
"choices.show_hidden.count": "Hidden ({n})",
|
||||
"choices.unhide.chip": "Show again",
|
||||
"deadlines.adjusted": "Adjusted",
|
||||
"deadlines.adjusted.reason": "weekend/holiday",
|
||||
"deadlines.adjusted.weekend": "weekend",
|
||||
|
||||
@@ -143,6 +143,25 @@ function writeChoicesToURL(choices: EventChoice[]) {
|
||||
window.history.replaceState(null, "", url.pathname + (url.search ? url.search : "") + url.hash);
|
||||
}
|
||||
|
||||
// Show-hidden toggle state (t-paliad-290 / m/paliad#122). When ON, the
|
||||
// calculator re-surfaces cards whose submission_code is in the active
|
||||
// skipRules set; they render faded with a "Wieder einblenden" chip.
|
||||
// URL-driven via ?show_hidden=1 so a shared link or reload preserves
|
||||
// the visibility. Default OFF — m's not asking to see hidden by
|
||||
// default, just to be able to.
|
||||
function readShowHiddenFromURL(): boolean {
|
||||
return new URLSearchParams(window.location.search).get("show_hidden") === "1";
|
||||
}
|
||||
|
||||
function writeShowHiddenToURL(on: boolean) {
|
||||
const url = new URL(window.location.href);
|
||||
if (on) url.searchParams.set("show_hidden", "1");
|
||||
else url.searchParams.delete("show_hidden");
|
||||
window.history.replaceState(null, "", url.pathname + (url.search ? url.search : "") + url.hash);
|
||||
}
|
||||
|
||||
let showHidden = readShowHiddenFromURL();
|
||||
|
||||
type ProcedureView = "timeline" | "columns";
|
||||
let procedureView: ProcedureView = "columns";
|
||||
|
||||
@@ -256,14 +275,33 @@ async function doCalc() {
|
||||
anchorOverrides: overrides,
|
||||
courtId,
|
||||
perCardChoices,
|
||||
includeHidden: showHidden,
|
||||
});
|
||||
if (seq !== calcSeq) return;
|
||||
if (!data) return;
|
||||
lastResponse = data;
|
||||
renderResults(data);
|
||||
syncHiddenBadge(data.hiddenCount ?? 0);
|
||||
showStep(3);
|
||||
}
|
||||
|
||||
// syncHiddenBadge updates the "Ausgeblendete (N)" count next to the
|
||||
// toggle. Visible regardless of toggle state so the user knows whether
|
||||
// there's anything to re-surface even when the toggle is OFF. Hides the
|
||||
// whole row when the projection has zero hidden cards — no clutter on
|
||||
// a project that's never used the skip feature. (t-paliad-290)
|
||||
function syncHiddenBadge(count: number) {
|
||||
const row = document.getElementById("show-hidden-row");
|
||||
const badge = document.getElementById("show-hidden-count");
|
||||
if (!row || !badge) return;
|
||||
if (count <= 0) {
|
||||
row.style.display = "none";
|
||||
return;
|
||||
}
|
||||
row.style.display = "";
|
||||
badge.textContent = tDyn("choices.show_hidden.count").replace("{n}", String(count));
|
||||
}
|
||||
|
||||
// triggerEventLabelFor picks the user-facing "Auslösendes Ereignis"
|
||||
// label from the calc response. Precedence:
|
||||
//
|
||||
@@ -696,6 +734,20 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
}
|
||||
|
||||
// t-paliad-290 — show-hidden toggle. Hydrate from URL, wire change
|
||||
// to URL + recalc (the backend reshapes the response — we can't just
|
||||
// re-render lastResponse since the hidden rows aren't in it when the
|
||||
// toggle was OFF).
|
||||
const showHiddenCb = document.getElementById("show-hidden-toggle") as HTMLInputElement | null;
|
||||
if (showHiddenCb) {
|
||||
showHiddenCb.checked = showHidden;
|
||||
showHiddenCb.addEventListener("change", () => {
|
||||
showHidden = showHiddenCb.checked;
|
||||
writeShowHiddenToURL(showHidden);
|
||||
scheduleCalc(0);
|
||||
});
|
||||
}
|
||||
|
||||
initViewToggle();
|
||||
initPerspectiveControls();
|
||||
|
||||
|
||||
@@ -74,10 +74,22 @@ export function attachEventCardChoices(opts: EventCardChoicesOpts): void {
|
||||
states.set(opts.container, state);
|
||||
|
||||
opts.container.addEventListener("click", (e) => {
|
||||
const target = (e.target as HTMLElement | null)?.closest<HTMLElement>(".event-card-choices-caret");
|
||||
if (target) {
|
||||
const targetEl = e.target as HTMLElement | null;
|
||||
const caret = targetEl?.closest<HTMLElement>(".event-card-choices-caret");
|
||||
if (caret) {
|
||||
e.stopPropagation();
|
||||
openPopover(state, target);
|
||||
openPopover(state, caret);
|
||||
return;
|
||||
}
|
||||
// t-paliad-290: "Wieder einblenden" chip — direct un-hide path that
|
||||
// mirrors the popover's reset on the `skip` kind. The chip only
|
||||
// renders on hidden cards (server-flagged via UIDeadline.IsHidden),
|
||||
// so we always have a real skip entry to remove.
|
||||
const unhide = targetEl?.closest<HTMLElement>(".event-card-choices-unhide");
|
||||
if (unhide) {
|
||||
e.stopPropagation();
|
||||
const code = unhide.dataset.submissionCode || "";
|
||||
if (code) void unhideCard(state, code);
|
||||
return;
|
||||
}
|
||||
// Outside-click closes the popover.
|
||||
@@ -259,6 +271,23 @@ function renderToggleBlock(state: AttachedState, code: string, kind: "include_cc
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// unhideCard removes the `skip` choice on the given submission_code via
|
||||
// the page-supplied remove() callback, then repaints chips so the card
|
||||
// loses its fade. The page's remove() also triggers a recalc — the
|
||||
// re-surfaced card will then drop out of the result list naturally
|
||||
// (since IncludeHidden is still on but the skip entry is gone). Errors
|
||||
// surface in the console; the chip stays clickable for a retry.
|
||||
// (t-paliad-290)
|
||||
async function unhideCard(state: AttachedState, code: string): Promise<void> {
|
||||
try {
|
||||
await state.opts.remove(code, "skip");
|
||||
state.active.get(code)?.delete("skip");
|
||||
reseedChips(state.opts.container);
|
||||
} catch (err) {
|
||||
console.error("event card un-hide failed", err);
|
||||
}
|
||||
}
|
||||
|
||||
function closePopover(state: AttachedState): void {
|
||||
if (state.popover) {
|
||||
state.popover.remove();
|
||||
|
||||
@@ -67,6 +67,31 @@ describe("deadlineCardHtml — editable=true emits click-to-edit attrs", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// t-paliad-290 (m/paliad#122): the "Ausgeblendete anzeigen" toggle
|
||||
// surfaces hidden cards via UIDeadline.IsHidden=true. The renderer
|
||||
// must (a) emit an inline "Wieder einblenden" chip carrying the
|
||||
// submission_code (so the delegated handler in event-card-choices.ts
|
||||
// can resolve which skip to clear) and (b) NOT emit the chip when
|
||||
// either isHidden is false or the rule has no submission_code (no
|
||||
// hide target to undo).
|
||||
describe("deadlineCardHtml — isHidden inline 'Wieder einblenden' chip (t-paliad-290)", () => {
|
||||
test("isHidden=true with submission_code emits unhide chip with data-submission-code", () => {
|
||||
const html = deadlineCardHtml(dl({ isHidden: true }), { showParty: true });
|
||||
expect(html).toContain("event-card-choices-unhide");
|
||||
expect(html).toContain('data-submission-code="upc-rop-12"');
|
||||
});
|
||||
|
||||
test("isHidden=false (default) suppresses unhide chip", () => {
|
||||
const html = deadlineCardHtml(dl(), { showParty: true });
|
||||
expect(html).not.toContain("event-card-choices-unhide");
|
||||
});
|
||||
|
||||
test("isHidden=true on a rule with no submission_code suppresses unhide chip", () => {
|
||||
const html = deadlineCardHtml(dl({ code: "", isHidden: true }), { showParty: true });
|
||||
expect(html).not.toContain("event-card-choices-unhide");
|
||||
});
|
||||
});
|
||||
|
||||
// Pure column-routing behaviour. Originally pinned by m/paliad#81
|
||||
// (side + appellant axes), re-framed by m/paliad#88: the column
|
||||
// axis is now "Unsere Seite vs Gegnerseite" ("WE always on the
|
||||
|
||||
@@ -72,6 +72,11 @@ export interface CalculatedDeadline {
|
||||
// page-level appellant axis still applies in that case). The bucketer
|
||||
// reads this in preference to the page-level appellant.
|
||||
appellantContext?: string;
|
||||
// isHidden (t-paliad-290 / m/paliad#122): server-side flag set when
|
||||
// a previously-hidden card is re-surfaced via the "Ausgeblendete
|
||||
// anzeigen" toggle. The renderer fades the card and exposes an
|
||||
// inline "Wieder einblenden" chip that deletes the skip choice.
|
||||
isHidden?: boolean;
|
||||
}
|
||||
|
||||
// priorityRendering returns the per-priority UX hints the save-modal
|
||||
@@ -131,6 +136,13 @@ export interface DeadlineResponse {
|
||||
// (m/paliad#81)
|
||||
triggerEventLabel?: string;
|
||||
triggerEventLabelEN?: string;
|
||||
// hiddenCount (t-paliad-290 / m/paliad#122): number of rules that
|
||||
// would have been hidden in this projection (i.e. their
|
||||
// submission_code is in skipRules and they passed the condition_expr
|
||||
// gate). Surfaces the "Ausgeblendete (N)" badge on the toggle even
|
||||
// when the toggle is OFF — so users know there's something to
|
||||
// re-surface.
|
||||
hiddenCount?: number;
|
||||
}
|
||||
|
||||
export interface CourtRow {
|
||||
@@ -160,6 +172,11 @@ export interface CalcParams {
|
||||
choice_kind: string;
|
||||
choice_value: string;
|
||||
}>;
|
||||
// includeHidden (t-paliad-290): when true the calculator returns
|
||||
// previously-skipped rules as faded cards instead of dropping them.
|
||||
// Sent only when the page-level "Ausgeblendete anzeigen" toggle is
|
||||
// ON.
|
||||
includeHidden?: boolean;
|
||||
}
|
||||
|
||||
const PARTY_CLASS: Record<string, string> = {
|
||||
@@ -305,6 +322,22 @@ export function deadlineCardHtml(dl: CalculatedDeadline, opts: CardOpts): string
|
||||
title="${escAttr(t("choices.caret.title"))}">▾</button>`
|
||||
: "";
|
||||
|
||||
// t-paliad-290 — inline "Wieder einblenden" chip on re-surfaced
|
||||
// hidden cards. Click deletes the skip choice (mirroring the popover
|
||||
// reset path). The chip only renders when the card is hidden in the
|
||||
// current projection (IsHidden=true on the wire) so it's always
|
||||
// pointing at a real skip entry. The chip text is a static i18n
|
||||
// value (no user input), so we use escAttr-only for attribute safety
|
||||
// and inline the translated label directly — matches the renderer's
|
||||
// pattern for the deadline name (also a known-safe string).
|
||||
const unhideLabel = t("choices.unhide.chip");
|
||||
const unhideHtml = dl.isHidden && dl.code !== ""
|
||||
? `<button type="button" class="event-card-choices-unhide"
|
||||
data-submission-code="${escAttr(dl.code)}"
|
||||
aria-label="${escAttr(unhideLabel)}"
|
||||
title="${escAttr(unhideLabel)}">${unhideLabel}</button>`
|
||||
: "";
|
||||
|
||||
const dlName = getLang() === "en" ? dl.nameEN : dl.name;
|
||||
|
||||
const adjustedNote = dl.wasAdjusted
|
||||
@@ -359,6 +392,7 @@ export function deadlineCardHtml(dl: CalculatedDeadline, opts: CardOpts): string
|
||||
</span>
|
||||
${dateStr}
|
||||
${choicesHtml}
|
||||
${unhideHtml}
|
||||
</div>
|
||||
${meta}
|
||||
${adjustedNote}
|
||||
@@ -449,8 +483,12 @@ export function wireDateEditClicks(
|
||||
export function renderTimelineBody(data: DeadlineResponse, opts: CardOpts = { showParty: true }): string {
|
||||
let html = '<div class="timeline">';
|
||||
for (const dl of data.deadlines) {
|
||||
// t-paliad-290: re-surfaced hidden cards render faded via the
|
||||
// shared timeline-item--hidden modifier (same modifier the columns
|
||||
// view uses; see fr-col-item--hidden below).
|
||||
const hiddenCls = dl.isHidden ? " timeline-item--hidden" : "";
|
||||
html += `
|
||||
<div class="timeline-item ${dl.isRootEvent ? "timeline-root" : ""}">
|
||||
<div class="timeline-item ${dl.isRootEvent ? "timeline-root" : ""}${hiddenCls}">
|
||||
<div class="timeline-dot-col">
|
||||
<div class="timeline-dot ${dl.isRootEvent ? "dot-root" : ""}"></div>
|
||||
<div class="timeline-line"></div>
|
||||
@@ -629,7 +667,8 @@ export function renderColumnsBody(data: DeadlineResponse, opts: ColumnsBodyOpts
|
||||
const mirrorTag = showMirrorTag && dl.party === "both"
|
||||
? `<div class="fr-col-mirror">↔ ${escHtml(t("deadlines.party.both.label"))}</div>`
|
||||
: "";
|
||||
return `<div class="fr-col-item ${dl.isRootEvent ? "fr-col-root" : ""}">
|
||||
const hiddenCls = dl.isHidden ? " fr-col-item--hidden" : "";
|
||||
return `<div class="fr-col-item ${dl.isRootEvent ? "fr-col-root" : ""}${hiddenCls}">
|
||||
${deadlineCardHtml(dl, cardOpts)}
|
||||
${mirrorTag}
|
||||
</div>`;
|
||||
@@ -680,6 +719,7 @@ export async function calculateDeadlines(params: CalcParams): Promise<DeadlineRe
|
||||
perCardChoices: params.perCardChoices && params.perCardChoices.length > 0
|
||||
? params.perCardChoices
|
||||
: undefined,
|
||||
includeHidden: params.includeHidden ? true : undefined,
|
||||
}),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
|
||||
@@ -1021,10 +1021,13 @@ export type I18nKey =
|
||||
| "choices.include_ccr.title"
|
||||
| "choices.include_ccr.true"
|
||||
| "choices.reset"
|
||||
| "choices.show_hidden.count"
|
||||
| "choices.show_hidden.label"
|
||||
| "choices.skip.false"
|
||||
| "choices.skip.title"
|
||||
| "choices.skip.true"
|
||||
| "choices.skipped.chip"
|
||||
| "choices.unhide.chip"
|
||||
| "common.cancel"
|
||||
| "common.close"
|
||||
| "common.forbidden"
|
||||
|
||||
@@ -3531,6 +3531,46 @@ input[type="range"]::-moz-range-thumb {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
/* t-paliad-290 (m/paliad#122) — re-surfaced "hidden" cards. The user
|
||||
* has previously marked these optional events as "Überspringen"; the
|
||||
* "Ausgeblendete anzeigen" toggle on /tools/verfahrensablauf returns
|
||||
* them with a faded + dotted-border treatment so they're visually
|
||||
* distinct from the active timeline. The inline "Wieder einblenden"
|
||||
* chip cancels the skip on click. */
|
||||
.timeline-item--hidden .timeline-content,
|
||||
.fr-col-item--hidden {
|
||||
opacity: 0.55;
|
||||
border: 1px dotted var(--color-border, #d4d4d4);
|
||||
border-radius: 6px;
|
||||
padding: 0.3rem 0.5rem;
|
||||
}
|
||||
|
||||
.event-card-choices-unhide {
|
||||
margin-left: 0.4rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
padding: 0.1rem 0.5rem;
|
||||
border-radius: 99px;
|
||||
border: 1px solid var(--color-accent, #c6f41c);
|
||||
background: var(--color-accent, #c6f41c);
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
/* Cancel the wrapper fade so the action remains a clear, high-
|
||||
* contrast affordance even though the rest of the card is muted. */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.event-card-choices-unhide:hover,
|
||||
.event-card-choices-unhide:focus-visible {
|
||||
background: var(--color-bg, #fff);
|
||||
}
|
||||
|
||||
.show-hidden-count {
|
||||
font-size: 0.78rem;
|
||||
color: var(--color-text-muted);
|
||||
margin-left: 0.4rem;
|
||||
}
|
||||
|
||||
.event-card-choices-popover {
|
||||
background: var(--color-bg, #fff);
|
||||
border: 1px solid var(--color-border, #d4d4d4);
|
||||
|
||||
@@ -224,6 +224,19 @@ export function renderVerfahrensablauf(): string {
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{/* Show-hidden toggle (t-paliad-290 / m/paliad#122).
|
||||
Re-surfaces optional cards the user has previously
|
||||
marked "Überspringen" via the per-card popover.
|
||||
The row hides itself when the projection has no
|
||||
hidden cards (handled in client/verfahrensablauf.ts).
|
||||
Default OFF; URL state ?show_hidden=1. */}
|
||||
<div className="verfahrensablauf-perspective-row" id="show-hidden-row" style="display:none">
|
||||
<label className="fristen-view-option">
|
||||
<input type="checkbox" id="show-hidden-toggle" />
|
||||
<span data-i18n="choices.show_hidden.label">Ausgeblendete anzeigen</span>
|
||||
</label>
|
||||
<span className="show-hidden-count" id="show-hidden-count" aria-live="polite"> </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual divider — keeps the perspective block (most-
|
||||
|
||||
@@ -63,6 +63,12 @@ func handleFristenrechnerAPI(w http.ResponseWriter, r *http.Request) {
|
||||
// wins (what-if exploration overrides the saved state).
|
||||
ProjectID string `json:"projectId,omitempty"`
|
||||
PerCardChoices []services.UpsertEventChoiceInput `json:"perCardChoices,omitempty"`
|
||||
// t-paliad-290 (m/paliad#122): re-surface previously-hidden
|
||||
// optional cards. When true the calculator marks skipped rows
|
||||
// with UIDeadline.IsHidden instead of dropping them; descendants
|
||||
// stay in the result list. Default false preserves the legacy
|
||||
// suppression. HiddenCount on the response is independent.
|
||||
IncludeHidden bool `json:"includeHidden,omitempty"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Ungültige Anfrage"})
|
||||
@@ -109,6 +115,7 @@ func handleFristenrechnerAPI(w http.ResponseWriter, r *http.Request) {
|
||||
PerCardAppellant: addendum.PerCardAppellant,
|
||||
SkipRules: addendum.SkipRules,
|
||||
IncludeCCRFor: addendum.IncludeCCRFor,
|
||||
IncludeHidden: req.IncludeHidden,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, services.ErrUnknownProceedingType) {
|
||||
|
||||
@@ -102,6 +102,12 @@ type UIDeadline struct {
|
||||
// Frontend bucketer prefers this over the page-level appellant when
|
||||
// non-empty. (t-paliad-265)
|
||||
AppellantContext string `json:"appellantContext,omitempty"`
|
||||
// IsHidden marks a card the user has previously hidden via a
|
||||
// skip choice. Only ever true when CalcOptions.IncludeHidden is
|
||||
// set — the toggle re-surfaces these rows so the user can either
|
||||
// keep them faded for context or un-hide them via the inline
|
||||
// "Wieder einblenden" chip. (t-paliad-290 / m/paliad#122)
|
||||
IsHidden bool `json:"isHidden,omitempty"`
|
||||
}
|
||||
|
||||
// UIResponse matches the frontend's DeadlineResponse TypeScript interface.
|
||||
@@ -137,6 +143,14 @@ type UIResponse struct {
|
||||
// is the appealable first-instance decision (m/paliad#81).
|
||||
TriggerEventLabel string `json:"triggerEventLabel,omitempty"`
|
||||
TriggerEventLabelEN string `json:"triggerEventLabelEN,omitempty"`
|
||||
// HiddenCount is the number of rules whose submission_code is in
|
||||
// CalcOptions.SkipRules AND whose condition_expr gate passes —
|
||||
// i.e. how many rows the user has hidden in this projection
|
||||
// regardless of the IncludeHidden toggle state. The frontend uses
|
||||
// this to render the "Ausgeblendete (N)" badge on the toggle even
|
||||
// when the toggle is OFF (so users know there's something to
|
||||
// re-surface). (t-paliad-290 / m/paliad#122)
|
||||
HiddenCount int `json:"hiddenCount"`
|
||||
}
|
||||
|
||||
// ErrUnknownProceedingType is returned when the UI sends an unrecognised code.
|
||||
@@ -214,6 +228,19 @@ type CalcOptions struct {
|
||||
PerCardAppellant map[string]string
|
||||
SkipRules map[string]struct{}
|
||||
IncludeCCRFor map[string]struct{}
|
||||
|
||||
// IncludeHidden re-surfaces rules whose submission_code is in
|
||||
// SkipRules (t-paliad-290 / m/paliad#122). When true:
|
||||
// - Skipped rules are NOT dropped from the result; they render
|
||||
// with UIDeadline.IsHidden=true so the frontend can fade them.
|
||||
// - Descendant suppression is bypassed (the skipped parent is
|
||||
// present in the result, so children compute their dates off
|
||||
// it as if the user had never hidden it).
|
||||
// Default false preserves the original skip semantic (drop rule +
|
||||
// suppress descendants). HiddenCount on UIResponse is independent
|
||||
// of this flag — it always reflects the number of hide-eligible
|
||||
// rows so the toggle's count badge stays accurate.
|
||||
IncludeHidden bool
|
||||
}
|
||||
|
||||
// Calculate renders the full UI timeline for a proceeding type + trigger date.
|
||||
@@ -381,6 +408,13 @@ func (s *FristenrechnerService) Calculate(ctx context.Context, proceedingCode, t
|
||||
// child rule's parent has already been classified — so descendant
|
||||
// suppression is a one-pass parent_id lookup.
|
||||
skippedIDs := make(map[uuid.UUID]struct{}, len(skipRules))
|
||||
// hiddenCount counts rows whose submission_code is in skipRules
|
||||
// AND that pass the condition_expr gate — i.e. rows the user has
|
||||
// hidden in this projection. Surfaced on UIResponse.HiddenCount so
|
||||
// the frontend's "Ausgeblendete (N)" badge stays accurate even when
|
||||
// IncludeHidden is off and the rows aren't in the result list.
|
||||
// (t-paliad-290 / m/paliad#122)
|
||||
hiddenCount := 0
|
||||
// appellantContext maps a rule UUID to the appellant value that
|
||||
// applies to its descendants. A rule that has its own PerCardAppellant
|
||||
// pick stamps itself with that value; a rule whose parent has a
|
||||
@@ -403,10 +437,22 @@ func (s *FristenrechnerService) Calculate(ctx context.Context, proceedingCode, t
|
||||
// this rule (or one of its ancestors) as "don't consider for
|
||||
// this case". Drop the row entirely AND record the rule ID so
|
||||
// descendants suppress too.
|
||||
//
|
||||
// t-paliad-290 (m/paliad#122): when opts.IncludeHidden is set,
|
||||
// we re-surface the directly-skipped row (faded via IsHidden)
|
||||
// instead of dropping it. Descendants are NOT cascade-suppressed
|
||||
// in that mode either — the un-suppressed parent computes its
|
||||
// date normally, so children compute off it as usual. Either
|
||||
// way we count the hide for the toggle's badge.
|
||||
var isHidden bool
|
||||
if r.SubmissionCode != nil {
|
||||
if _, skipped := skipRules[*r.SubmissionCode]; skipped {
|
||||
skippedIDs[r.ID] = struct{}{}
|
||||
continue
|
||||
hiddenCount++
|
||||
if !opts.IncludeHidden {
|
||||
skippedIDs[r.ID] = struct{}{}
|
||||
continue
|
||||
}
|
||||
isHidden = true
|
||||
}
|
||||
}
|
||||
if r.ParentID != nil {
|
||||
@@ -442,6 +488,7 @@ func (s *FristenrechnerService) Calculate(ctx context.Context, proceedingCode, t
|
||||
ConditionExpr: json.RawMessage(r.ConditionExpr),
|
||||
AppellantContext: ctxVal,
|
||||
ChoicesOffered: json.RawMessage(r.ChoicesOffered),
|
||||
IsHidden: isHidden,
|
||||
}
|
||||
if r.SubmissionCode != nil {
|
||||
d.Code = *r.SubmissionCode
|
||||
@@ -712,6 +759,7 @@ func (s *FristenrechnerService) Calculate(ctx context.Context, proceedingCode, t
|
||||
ProceedingNameEN: pickedProceeding.NameEN,
|
||||
TriggerDate: triggerDateStr,
|
||||
Deadlines: deadlines,
|
||||
HiddenCount: hiddenCount,
|
||||
}
|
||||
// Sub-track routing keeps the user-picked proceeding's identity,
|
||||
// so the trigger-event label rides on `pickedProceeding` (e.g.
|
||||
|
||||
Reference in New Issue
Block a user