feat: event assignments board, archive freeze, and overview widgets - #74
Open
ethnjs wants to merge 94 commits into
Open
feat: event assignments board, archive freeze, and overview widgets#74ethnjs wants to merge 94 commits into
ethnjs wants to merge 94 commits into
Conversation
…instead of flattening
…r nested assignments
…ot the generic filter list
…d chip the track filter
…ds them as the schedule
…nents/ui into components/tournament/sheets
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
🚅 Deployed to the nexus-pr-74 environment in nexus
|
…rchiving, and lock instead of hide write controls
… and member panel field
…us, availability, lunch, and event preferences
…he setup checklist spanning columns
…s, availability-option, and onboarding counts
…tiles and stacked availability bars
…dget with per-segment bar tooltips
…gets and move the setup checklist into overview/
…rd showing a tile per primary track
… timeline, stacked to fit the card
ethnjs
added this pull request to stack #75
September 11, 2026 06:02
ethnjs
marked this pull request as ready for review
September 11, 2026 06:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat: event assignments board, archive freeze, and overview widgets
Summary
fieldsgroup registry on the event reads, events table view state moved into display config, bulk duplicate/delete on both tables, a mass shift editor, open panels kept in the URL, and refetch on tab focus for pages that several people edit together.What changed
Assignments backend (#70)
TournamentEventAssignmentlinks an event, a membership, atournament_membership_rolesrow, an optional shift, and a track. Both the role and the shift go through composite FKs:(membership_role_id, membership_id)and(shift_id, track_id). Denormalized columns can't drift from their source this way, whatever the write path. A member in a role they don't hold can't be stored at all./tournaments/{id}/assignments/, because an assignment joins an event to a member and nesting it under either one would imply an ownership that doesn't exist.GETtakes optionalevent_id/membership_idfilters, so the board, member panel, and event drawer share one route. Reads needmanage_eventsormanage_members. Writes needmanage_members.default_role: the role the board grants when a member is dropped on that track's column.fieldsgains anassignmentsgroup, anassignedroster filter, and a card surface. Event reads get their ownfieldsregistry (event_fields.py) with the same opt-down rules as the memberships one. An event's shifts come back in schedule order, not attach order.Assignments board (frontend)
app/dashboard/tournaments/[id]/assignments, with a sidebar entry andIconAssignments.lib/assignments/flags.ts):unavailable,track_unconfirmed,double_booked(compared by overlapping time, not shift id), andover_staffedagainstvolunteers_needed. Computed client-side, because every input already arrives on responses the board fetches. A field group that wasn't requested produces no flag, since "we didn't ask" must never render as "they said no".Intl.DateTimeFormatinstances are cached.Events, shifts, and shared UI
MassShiftEditor(BulkDeleteModal,MassResultsCard).usePanelUrlkeeps the open event/shift/member panel in the URL.useRefetchOnFocuscovers events, shifts, and the member panel.Inputgets a clear button (used in every search bar). Also: aChipInputdisabled state, a compactEmptyState,DockedPanelwithout a close button, andPopoverstaying put on scroll.Archive freeze
core/tournament/archive.py: onearchive_tournamentshared by the manual route and the auto-archive job, so their side effects can't drift apart. Before this, the job didn't cleararchive_override_at.require_form_not_archivedis called in every form write route: create, update, delete, bulk field edits, invalidate, submit/patch response, and prerequisites. It isn't built into the access dependencies because those also gate reads, and an archived tournament's forms stay readable. Sheets, admin, display config, onboarding, leaving a tournament, and ownership transfer are also rejected while archived.useArchiveLocklocks write controls across events, shifts, assignments, members, forms, sheets, and roles instead of hiding them. The form builder reads a newForm.tournament_is_archivedproperty.Onboarding progress
recompute_onboardingrecalculates every active member'sonboarded_atwhenever the live sequence changes (a step published, unpublished, added, or removed), instead of waiting for each member's next progress call. It keeps an existing completion time.onboardingmembership field group (completed / total live steps, counted from responses in two queries per roster), shown as aProgressRingcolumn on the members table and a field on the member panel.Overview
GET /members/summary/(manage_members): counts only, never names. It's its own route rather than a roster param because it returns counts, not memberships. It returns per-track status counts, availability-option coverage (options grouping the same shifts across forms collapse into one), and onboarding counts.TournamentHeaderCard(one tile per primary track),MemberSummaryCard(status tiles and stacked availability bars with per-segment tooltips), andMySignupCards(the viewer's own status, availability on the member panel's timeline, lunch, and event preferences, per track). The setup checklist moved intooverview/behind a sharedOverviewCardheader.MasonryGridnow packs cards into a gapless mosaic without changing their order, and the setup checklist can span columns.Unlisted
db/seed_members.py: a CLI that seeds members by replaying onboarding form submissions.components/uitocomponents/tournament/sheets.Migrations
3 new revisions, so
alembic upgrade headis required.b570994db077: thetournament_event_assignmentstable, plus a unique(id, membership_id)ontournament_membership_rolesfor the composite FK to target.c018f2ae8e71: nullabletournament_tracks.default_role_id(ON DELETE SET NULL).d3b71a4c9e02:tournament_track_idbecomes NOT NULL on assignments. Backfill: a pinned row takes its shift's track. An unpinned row takes its track by matching its role against the cosmetic tracks' default roles, which is ambiguous when two tracks share a default, soORDER BYkeeps it deterministic. After that the fallbacks are the event's first cosmetic track, then any of its tracks, then the tournament's first primary track. Rows that still have no track are deleted (the track invariant says there shouldn't be any). The unpinned uniqueness index now includes the track.Test plan
backend/tests/api/tournament/test_assignments.py(CRUD, 409 on duplicates, rank-bound auto-grant, shift-on-event validation, track resolution, unpin on shift detach, permission split).backend/tests/api/tournament/test_member_summary.py(status, availability-option, and onboarding counts).test_display_config.py,test_events.py,test_memberships.py,test_setup_checklist.py,test_shifts.py,test_join_codes.py,test_onboarding.py(skipped unpublished steps, recompute on sequence change),test_core.py/test_admin.py/test_forms.py/test_sheets.py(archive freeze and form cascade),core/test_field_groups.py.