Skip to content

feat(overrides): fold past date overrides into a collapsed section - #176

Merged
asm0dey merged 6 commits into
mainfrom
feat/past-date-overrides
Sep 5, 2026
Merged

feat(overrides): fold past date overrides into a collapsed section#176
asm0dey merged 6 commits into
mainfrom
feat/past-date-overrides

Conversation

@asm0dey

@asm0dey asm0dey commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Closes #168.

What

/me/date-overrides listed every override the owner had ever created in one flat list, ordered global-first-then-by-date, so a host with many of them could not find the ones that still matter. The page now leads with upcoming overrides (soonest first) and folds everything dated before today into a collapsed Past overrides (N) section (most recent first).

Nothing is deleted — that was the reporter's actual ask: "hide them, rather than delete them, as they may still be useful to someone."

  • "Today" is read in the owner's configured timezone. A null, blank or unparseable stored timezone falls back to UTC rather than 500ing the page.
  • An override dated today counts as upcoming — it still governs today's bookable slots.
  • The collapse is a native <details>, so it works with JavaScript off. This is the codebase's first <details>-form daisyUI collapse; the checkbox form used in meetingTypeDetail.html is the other valid form, not a correction.
  • The section is omitted entirely when there is no history, so a new host never sees an empty bar.

The request path

One owner-scoped query, split in memory. All three handlers (GET, create-POST, delete-POST) now render through the same method, so the owner_id predicate lives in one place instead of three.

flowchart TD
    GET["GET /me/date-overrides"] --> INST
    POST["POST /me/date-overrides<br/>(create)"] --> INST
    DEL["POST /me/date-overrides/{id}/delete"] --> INST

    INST["dateOverridesInstance()"] --> Q

    Q["ownerId = ?1<br/>order by overrideDate, meetingTypeId nulls first"] --> W
    W["withWindows(...)<br/>one extra query, no N+1"] --> T

    T{"overrideDate.isBefore(today)?<br/>today = LocalDate.now(ownerZoneId())"}
    T -- "no (incl. today)" --> UP["upcoming — ascending"]
    T -- "yes" --> PAST["past — re-sorted descending"]

    UP --> TPL["dateOverrides.html"]
    PAST --> TPL
Loading

The two predicates are exact complements over a NOT NULL column, so no override can be dropped or shown twice. The secondary sort on meetingTypeId matters: a global and a per-type override may share a date, and Postgres's sort is not stable, so without it those two cards could swap places between page loads.

The page structure

 dateOverrides.html
   <h1> Date-specific overrides
-  for o in overrides                    # every override, global-first then by date
-    <card> date · scope · windows/day-off · Delete
+  for o in upcoming                     # ascending, today included
+    include _dateOverrideCard o=o
+
+  if !past.isEmpty()                    # no history -> no bar at all
+    <details id="past-overrides">       # native, no JS
+      <summary> Past overrides (N)
+      for o in past                     # descending
+        include _dateOverrideCard o=o
   <h2> Add an override
   <form method="post">

The card body moved into _dateOverrideCard.html so it is written once rather than duplicated across the two loops — same _name.html + {#include AdminResource/_name p=v /} convention as _workplanGrid.html and _meetingtypecard.html. It carries its own CSRF token, which is what CsrfFormCoverageTest counts per file.

Out of scope

The per-meeting-type override list on the meeting-type detail page is left unsplit. It already sits inside a closed accordion and is scoped to a single type, so it cannot produce the flat wall the reporter described. Tracked as a follow-up — note that _dateOverrideCard.html is not directly reusable there (different card class, no scope label, different delete action, different message-key family).

Testing

mvn test green: 1047 tests, 0 failures, 0 errors.

Four tests in AdminDateOverridesTest assert placement relative to the id="past-overrides" marker, plus the rendered Past overrides (1) label — not substring presence. <details> keeps its content in the response body, so a naive containsString would pass even with the split completely broken. A mutation check confirmed the tests fail when the upcoming predicate is neutered.

Also verified by hand against a running dev server: ordering, the count, expansion, delete-from-inside-the-collapse, and the bar disappearing once no past overrides remain.

i18n

New adm_dateOverrides_past_summary(int count) ships with German and Hebrew values, identical {count} placeholder in all three.

Docs

Changelog ## Unreleased bullet and a usage/availability.md paragraph are on the docs-site branch. The embedded date-overrides.png screenshot now predates this layout; refreshing it is filed separately, to keep binary assets out of this diff.

🤖 Generated with Claude Code

asm0dey and others added 6 commits September 5, 2026 15:25
GH #168: the page listed every override ever created in one flat list, so a
host with many of them could not find the ones that still matter. Split on
today in the owner's timezone; past ones move into a folded section instead
of being deleted. Card markup extracted to a partial so it exists once.
Final-review wave. Restores meetingTypeId as the ORDER BY tiebreaker (a
global and a per-type override can share a date, and Postgres's sort is
unstable, so their cards could swap between page loads); records why
ownerZoneId() does not reuse OwnerSettings.coerceZone; restores the
pre-branch gap below the upcoming list when no past section renders; and
pins the "Past overrides (N)" summary label in a test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Sep 5, 2026

Copy link
Copy Markdown

@asm0dey
asm0dey merged commit 6f7f469 into main Sep 5, 2026
11 checks passed
@asm0dey
asm0dey deleted the feat/past-date-overrides branch September 5, 2026 16:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Date overrides

1 participant