Skip to content

feat: tournament events, shifts, and season events - #61

Merged
ethnjs merged 71 commits into
mainfrom
feat/tournament-event-shift-season
Aug 17, 2026
Merged

feat: tournament events, shifts, and season events#61
ethnjs merged 71 commits into
mainfrom
feat/tournament-event-shift-season

Conversation

@ethnjs

@ethnjs ethnjs commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Summary

Replaces the old JSON-blob TournamentEvent.blocks/.category model with real scheduling data — start/end datetimes, a reusable per-tournament TournamentShift catalog, and a link to the canonical Event/EventCategory catalog via a new SeasonEvent bulk-load table. Ships the first UI for any of this: an Events tab (read-only table + edit panel) and a Shifts tab (inline-editable table + attach/detach panel) on the tournament events page, which previously had no frontend at all.

Also folded in, along the way: a rebuild of the members roster's panel/filter infrastructure (reused directly by the events UI), and tournament-timezone-aware datetime validation.

What changed

Backend — events, shifts, season catalog

  • TournamentEvent rewritten: dropped blocks (JSON) and category (string); division now nullable; added event_id FK to the canonical Event catalog (null = custom event) and required-by-convention start_time/end_time — left nullable on the column itself since TDs often create an event before its time is scheduled, but the frontend warns before allowing shift-attach without them.
  • TournamentShift — new per-tournament shift catalog (label, start, end), attached to events via tournament_event_shifts. Named TournamentShift rather than EventShift to match the existing TournamentEvent/TournamentMembership naming convention.
  • Attach validation: a shift must fit entirely within its event's start/end, and shifts on the same event can't overlap (adjacent, end == start, is fine). Delete has no server-side guard — cascades silently, confirmation is frontend-only.
  • Partial unique index on (tournament_id, event_id, division) — a canonical event can only be added once per division per tournament; custom events (event_id IS NULL) are unconstrained.
  • SeasonEvent — admin-curated (event_id, year, division, is_active) suggestion list. New POST /admin/season-events/ CRUD and a bulk-load action that creates one TournamentEvent per active SeasonEvent matching the tournament's own division list, with name snapshotted (not live-linked).
  • Seeded 2027 Division B/C season events (seed_season_events_2027.py).
  • manage_events gates every events/shifts/bulk-load route, read and write alike — no separate read-only permission.
  • TournamentMembership.assigned_event_id/.schedule dropped outright — dead weight from the old single-assignment model, no migration path kept.
  • Tournament now has a timezone column, captured silently from the browser on create; event/shift bounds validation compares against tournament-local time instead of raw UTC, and per-field 409s (start/end outside tournament bounds) surface on the offending input instead of a generic save-bar error.

Backend — TournamentSummary

  • New TournamentSummary schema on GET /tournaments/me/ — drops volunteer_schema, time_blocks, categories; adds event_count/volunteer_count computed from .events/.memberships. (There's no separate GET /tournaments/ list route in this codebase — only /me/ and /{id}/ — so only /me/ needed the change.)
  • GET /tournaments/{id}/ unchanged, still returns full TournamentRead.
  • Extracted TournamentPublic as a shared base between the summary and the join-code preview schema.

Frontend — events & shifts page

  • New tournament events page: Events / Shifts tabs behind a newly-extracted shared TabStrip (pulled out of the roles-settings tab UI).
  • Events tab: read-only table with search/filter/sort matching the members page pattern (FilterModal, division/type/category filters); row click docks an edit panel (EventPanel) instead of opening a modal.
  • EventPanel: name/division/type/building/room/floor/volunteers all editable; category shown as fixed text only when event_id is set (no category at all for custom events); Combobox for canonical-vs-custom event selection; shift picker filtered to shifts that fit inside the event's start/end, selected shifts render as chips.
  • Empty state (no events yet) offers a bulk-load-from-season action, mirroring the roles-settings "load default roles" pattern exactly (LoadDefaultEventsModal).
  • Shifts tab: inline-editable label/start/end with a FloatingSaveBar batching changes (per-row edit toggle, not per-field autosave); expand panel (ShiftEventsCard) shows/edits which events a shift is attached to — the reverse of the event panel's shift picker.
  • Deleting a shift attached to N events prompts a confirm modal warning it'll be removed from all of them.
  • Archived tournaments: every input and button in both tabs renders locked (not hidden), reusing the same locked convention as the rest of settings. Users without manage_events don't see the page at all — no read-only mode.
  • Mass-edit: multi-row select mode on both events and members tables, unified into one selection-driven panel for single vs. bulk edit (division/type/start/end, shift attach/detach with deferred save).

Frontend — also included

  • Members roster panel rebuild reused by the events work: docked side panels (DockedPanel, useLayoutPanel) replacing the old modal SidePanel, usePanelSelection unifying select/edit flow, SelectionBar for multi-row select, usePersistedFilter for per-user/per-tournament filter persistence.
  • Members: MassRoleEditor (add/remove roles with diff preview), MembersFilterModal, locked-role display instead of hiding, checklist column animation.
  • UI primitives: FilterModal, FormPopover, Badge division/warning variants, Button xs size, Popover flip-above-trigger fix, datetime-local input year clamp.
  • Misc fixes: hard nav on topbar wordmark logo (stale-cache issue), not-found screen for missing tournaments instead of infinite spinner, stale tournament-list refresh race, guard against removing a tournament's owner membership.

Out of scope

  • Volunteer/member assignment to events or shifts — deferred to a future assignment epic.
  • SeasonEvent curation admin UI — bulk-load consumes it, managing the season list itself is not built.
  • Timeline/card views, CSV import for events.

ethnjs added 30 commits August 13, 2026 16:33
…nk to canonical Event catalog, add start/end times
…inks, add TournamentShift and SeasonEvent schemas
…onfirm-preview modal, rename from 'catalog' to 'default events'
… section, switch division and type filters to ButtonGroup, bump filter icon sizes
ethnjs added 19 commits August 14, 2026 19:54
@ethnjs ethnjs added the backend label Aug 17, 2026
@ethnjs ethnjs linked an issue Aug 17, 2026 that may be closed by this pull request
@ethnjs ethnjs added frontend database Updates database schemas or models events labels Aug 17, 2026
@vercel

vercel Bot commented Aug 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nexus Ready Ready Preview Aug 17, 2026 6:48am

@railway-app

railway-app Bot commented Aug 17, 2026

Copy link
Copy Markdown

🚅 Deployed to the nexus-pr-61 environment in nexus

Service Status Web Updated (UTC)
nexus ✅ Success (View Logs) Aug 17, 2026 at 6:49 am

@railway-app
railway-app Bot temporarily deployed to nexus / nexus-pr-61 August 17, 2026 06:48 Destroyed
@ethnjs
ethnjs merged commit 7bdabe9 into main Aug 17, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend database Updates database schemas or models events frontend

Projects

None yet

1 participant