Commit Graph

62 Commits

Author SHA1 Message Date
cf1a2f0f5a Add a season workload chart to the referee management dashboard
Games refereed per referee this season, as a chart.js bar chart plus a
small table with fees paid per referee, alongside season-at-a-glance KPIs
(active referees, total games refereed, average per referee) -- separate
from the existing "games in view" KPIs, which track the upcoming range
picked by the filter strip rather than season-to-date workload.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-22 17:25:09 +02:00
747514f1ff Let eligible referees self-serve sign-up for games needing one
Creating (or re-teaming) a home game for a club-managed team now auto-
invites every eligible referee (teams.RefereeProfile) via a new
RefereeSignup model, notifying them the same way news/events already do.
They see it as its own distinct row on the mobile Calendar (own accent
colour) and can accept or decline right there -- accepting routes through
the existing capacity-checked assign_referee (assigned_by=None marks it
self-service), so it lands as a real EventReferee row with no separate sync
step. The desktop referee-management screen and event detail page both
surface pending invites and flag self-signed-up referees distinctly from
admin assignments.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-22 17:19:48 +02:00
135580d83e Simplify the line-up to yes/no picks, and stop truncating notifications
Coach mode's line-up screen no longer has lines/slots -- just a yes/no
toggle per available roster player, grouped by their roster position, both
in coach mode and on the published member-side view. Replaces LineupUnit/
LineupSlot with a single LineupSelection model.

Notifications now show their full body text (no more truncatechars) and the
unread colour bar spans the full row height via self-stretch, matching the
calendar row's own marker, so it still reads correctly once a body wraps to
several lines.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-22 16:29:21 +02:00
dba28ffc24 Improve hero RSVP readability: whiter text, bordered buttons, a cohesive reason panel
The "are you in?" subtext and the In/Out button labels were both dim
grey (text-on-dark-dim/text-on-dark) against a photo backdrop -- bumped
to white, and gave both buttons a shared border (border-white/30) for
definition against the image, same colour on both so they read as a
matched pair.

The Out reason step (label, textarea, Cancel/Confirm) used to be loose
floating controls directly on the photo -- now wrapped in one bordered/
backed panel (same border colour as the In/Out pair) so it reads as one
cohesive control instead of scattered text and buttons.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-22 15:03:33 +02:00
614c35861b Capture a private reason when declining an event RSVP
Attendance.note already existed but nothing wrote to it. Picking "Out"
anywhere (Home's hero, Coach Today's "Also yours", event_detail's per-
person answers) now opens an optional reason field before submitting,
via an Alpine two-step within the same form rather than a separate
confirmation screen. EventDetailView.post stores it only when the status
is actually "absent", and clears it the moment someone flips back to
In/Maybe -- a stale "sick" note under a since-changed answer would just
be confusing.

Private by construction, not by a permission check: nothing anywhere
renders another member's own note -- the squad-response view stays
counts-only like it already was. The only two places that read it back
are this member/family's own "Your answers" card and Coach mode's bench
attendance screen, both already scoped to people the viewer has a real
claim on.

Also, on the shared hero In/Out buttons (Home + Coach Today): dropped the
green "In" styling -- nobody's answered yet at that point, so a green
default misleadingly read as an already-recorded answer -- and added
min-w-0 to both buttons so flex-1 actually splits the row evenly; a
longer label's own intrinsic width was winning it a bigger share
otherwise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-22 13:01:07 +02:00
1d22a57208 Coach header: back to dark ink, ice-blue only on the active switcher pill
The previous turn's all-ice-blue header wasn't what the design doc actually
called for -- its own text is explicit: "the role switcher with Coach
active (bg-ice)", i.e. the accent belongs on the switcher's active segment,
not the header background. Reverted .coach-header to dark ink (matching
Member mode's own navy) and added .role-switcher-item-active-ice (bg-ice/
ice-ink) for just the Manager pill, leaving Member mode's own white/ink
active state untouched. All the header_extra text this touched (Attendance,
Line-up) reverts to white/on-dark to match.

Fixed the switcher jumping position between modes: both shells' top info
row now carries the same min-h-11 (previously only implicit in the member
header, via its bell button's own height), and the switcher sits directly
below that row in both -- the coach header's team-picker pill row (multi-
team accounts) now renders *after* the switcher instead of before it, so
its conditional presence can't shift the switcher down anymore.

Tab bar order swapped per feedback -- Today / Squad / Schedule / [+],
not Today / Squad / [+] / Schedule -- the "+" reads better as the last,
most prominent item than sandwiched in the middle.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 23:28:23 +02:00
1d159ca5de Redesign the Coach mode shell: ice-blue header, flat sheet, Squad/Schedule tabs
Reworks the coach shell per feedback that the previous dark-ink header
didn't read as visually distinct from Member mode's own navy header, and
that the rounded "sheet overlaps header" treatment wasn't landing:

- .coach-header is now solid ice-blue (--color-ice, never club-themed --
  Coach mode's own signature colour) with --color-ice-ink foreground text,
  instead of dark ink with white text. Every header_extra block across the
  coach templates (attendance, lineup) is recoloured to match.
- .coach-sheet drops the 20px rounded-top/negative-margin overlap -- flush
  edge-to-edge below the header now, same join the member shell already uses.
- The header's "Head coach" label was hardcoded regardless of the account's
  actual role -- CoachScopeMixin now resolves active_team_role from the
  person's own current-season StaffAssignment.position on the active team,
  so a team manager or physio sees their real title, not someone else's.

Tab bar is now Today / Squad / [+] / Schedule -- no Me tab (the account's
own settings already live in Member mode via the role switcher, no need
for a second one). Squad (CoachSquadView) is a new roster+staff screen for
the active team; Schedule (CoachScheduleView) is a new full upcoming-events
list for the team, each row routed straight to the coach-relevant action
(Attendance for a practice, Line-up for a game) rather than the Member-
shell RSVP page. The "+" is a raised ice-blue circle opening a small popup
with New event/New post/Add player -- three genuinely different actions,
so the button opens a menu rather than committing to one destination.
Today's own inline New event/New post/Add player buttons are gone now that
the tab bar covers the same ground.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 23:21:27 +02:00
1ced0619a3 Enable hx-boost app-wide for SPA-like navigation; Me page polish
hx-boost="true" on both shells' <body> turns every same-shell link/form
into an AJAX navigation (swapping <body>'s children, pushing the URL)
instead of a full browser reload -- htmx.js was already loaded on every
page but had zero actual usage anywhere in the codebase until now.

Only <body>'s children swap under a boost, never the tag itself, so
anything that crosses between the two differently-styled shells (Member's
bg-paper vs Coach's bg-ink, different data attributes) is marked
hx-boost="false" to force a real navigation instead: both role-switcher
links, the Me page's "Teams I coach/manage" row (leads into Coach mode),
and Coach Today's "Also yours" quick-RSVP forms (post to the Member-shell
event_detail view). The calendar-sync "Add to calendar" webcal:// link is
also excluded -- it's meant to hand off to the OS calendar app, not be
treated as in-app navigation.

Also, on the Me page:
- Renamed the mode switcher and the "Teams I coach" card to "Manager"/
  "Teams I coach/manage" -- a team manager assigned there isn't
  necessarily a coach.
- Fixed the team badge, which was slicing short_name to 2 characters
  ("U16" showing as "U1") -- replaced with a plain icon instead of text,
  so there's nothing left to truncate (the full team name is still the
  row's own label).
- Styled the whole card dark (m-card-dark), matching the design mock's
  own dark "Coach mode" promo card treatment for this part of M5.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 22:59:47 +02:00
53b3c56594 Add Coach mode C3: game line-up, the last of the six coach screens
New models (events/models.py): Lineup (one per event), LineupUnit (Line 1,
Defence pair 1, ... -- coach-entered labels, no fixed sport structure since
neither Club nor Team carries one), LineupSlot (one position, optionally
filled by a member). events/services/lineup.py's publish_lineup is the
reason Attendance.AttendanceStatus.SELECTED/NOT_SELECTED existed at all --
publishing flips every slotted member to SELECTED and every other available
(non-out, non-silent) roster member to NOT_SELECTED, then notifies only the
selected players.

Placement is a native <select> per slot, batch-saved with one "Save
line-up" submit, not the design mock's drag-and-drop -- this codebase has
no established htmx interaction pattern yet (htmx.js is loaded but nothing
uses it) to build a live per-tap version on, and a reliable plain form beats
a first, unproven real-time interaction for an already-large screen.
place_member handles "swap" semantics for it: placing a member vacates any
other slot of theirs in the same lineup, bumping whoever was already in the
target slot back to the available pool.

Wires the missing pieces from C1's own docstring: the "needs you" line-up-
not-published row and the tonight card's "Line-up" button, both deferred
in that stage specifically because this model didn't exist yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 22:47:42 +02:00
ccd4e0aa13 Add Coach mode C6: bulk-add players to the active team's roster
management.forms.TeamMembershipForm is shaped for one member at a time
(per-row jersey number/position/captain flags), which doesn't fit a "tap a
few names, add them" flow -- the design mock itself shows plain checkboxes,
no inline position picker. Reuses the same two eligibility rules the form
applies internally (teams.services.eligible_roster_members, minus whoever's
already on this team+season) directly instead, and lets a coach fill in
jersey number/position afterward on the desktop -- the model's own
help_text already documents a blank position as a normal, expected state.

"Suggested" (on this team last season) is real, computed data via
club.models.Season.before. "Age eligible" from the mock isn't built --
neither Club nor Team carries an age-group field to compare a birth date
against, so faking that filter would just mean it silently matched nothing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 22:36:29 +02:00
995f7ecfc9 Add Coach mode C4: create event, reusing the desktop's own EventForm
management.forms.EventForm already scopes its teams field to
teams_managed_by(user, club) via EventAudienceFormMixin -- exactly the
restriction a coach needs, so it's reused as-is rather than duplicated.
Only a subset of the desktop form's fields is rendered (title, kind, teams,
location, start, answers-close deadline); the rest stays unset, all of it
optional on the model except max_referees, which is dropped from the form
entirely so its own default (2) applies instead of demanding a value this
screen has no field for.

After a successful save, replicates management.views.EventCreateView's own
notify_new_event.delay(...) call by hand, since this isn't a CreateView --
attendance sync itself is automatic via events/signals.py.

"Repeat weekly" from the design mock isn't wired up this stage -- the
recurring-series machinery is a separate form with its own fields; adding
it is later work, not something to fake with an inert toggle.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 22:28:44 +02:00
6a9a6204ed Add Coach mode: dark-chrome shell, mode switcher, C1 Today, C2 Bench attendance
First slice of the coach-mode build (design_handoff_rosterchief_platform/
README.md's "Coach mode" section, C1-C6). Ships the foundation together with
the two screens the design doc calls out as the reason coaches install
anything at all, rather than landing a Today screen with a dead "Check
attendance" button:

- mobile/coach_mixins.py's CoachScopeMixin -- the dark-mode mirror of
  PersonScopeMixin, scoped by active team (via club.services.access'
  teams_staffed_by/teams_managed_by) instead of managed people.
- A standalone dark ink/ice shell (mobile/templates/mobile/coach/base.html)
  with the mode's signature 20px-radius overlapping sheet, reusing the
  --color-ice/--color-ink tokens that already existed in assets/mobile.css
  but were unconsumed until now.
- The Coach/Member role switcher is re-added to the member shell, gated on
  a real staff assignment (has_coach_access), replacing the "deliberately
  not rendered yet" placeholder.
- C1 Today: stat tiles, a tonight's-session card, a silent-players "needs
  you" row, and an "Also yours" card reusing HomeView's own hero-RSVP
  pattern scoped to the coach's own member record.
- C2 Bench attendance: writes through events.services.attendance.
  record_check_in, which existed for exactly this and had no caller yet.
  Read-only for staff on a team without a management position.

Line-up (C3), create event (C4), post news (C5), and add-to-roster (C6)
follow in later stages -- see the coach-mode plan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 22:21:25 +02:00
9fbcc89646 Fix the staff-row divider on the team detail page to match the player table
divide-y was stacking two borders: the custom .divide-y > * + * rule (top,
--color-rule) plus Tailwind's own generated divide-y utility (bottom width/
style with no color, so it fell back to a dark currentColor). Give each
staff row its own border-b border-rule instead, the same single-border
approach the roster table already uses via .table tbody tr.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 22:09:50 +02:00
4b5a4a81ec Show everything past next week on Calendar, grouped by month
The agenda used to hard-cut at a ~14-day lookahead with no way to see
anything past it. Drop the cutoff and bucket whatever falls beyond
"Next week" into per-month groups (each its own sticky header), so the
screen stays a full upcoming agenda instead of losing events that were
still worth seeing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 20:50:54 +02:00
9c16141d06 Stretch the calendar row's kind-marker bar to the full content height
Fixed at h-9.5 it matched the old two-line layout, but fell short next
to a three-line row (title + meta + location). self-stretch keeps it
matching whatever the content column actually renders.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 20:45:10 +02:00
9e581370e4 Add a club event-background photo, shown in black & white on event heroes
New Club.event_background, uploaded from the identity page. Individual
events have no photo of their own (an established, deliberate scope
decision for this build), so this is the one club-wide stand-in for the
photo the design canvas's own hero mockups call for -- shown under the
same dark gradient the hero already used, filtered to grayscale so it
never fights the club's own brand colours. Falls back to the existing
plain dark background when nothing's uploaded.

Applied to both event hero treatments: M2's own event-detail screen (the
"big black thing" this was reported against) and Home's "next up" card.
The image and gradient are separate absolutely-positioned layers behind
a z-10 content wrapper, not a filter on the card itself, so the
grayscale treatment never touches the text/buttons drawn on top of it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 18:11:02 +02:00
c7887637ff Add a club legal address, used for invoices/referee forms ahead of the home ground
New Club.legal_address/legal_zip_code/legal_city, editable from the
identity page (management:club_settings). Official document headers
(the dues invoice, the referee payment form) previously borrowed the
club's home Location for this -- conflating "where we play" with "our
registered address", which aren't always the same place. New
club.services.invoicing.resolve_document_address(club) picks the club's
own legal address when set, falling back to the home Location exactly as
before when it isn't, so nothing breaks for a club that hasn't set one
yet. Location.is_home now means only what it always should have: telling
a home game from an away one.

Renamed the shared "home_location" template/context variable to
"document_address" on both PDFs to match -- it was never accurate once a
legal address could win instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 17:50:08 +02:00
5bfb6ce76e Bigger unread badge with a real count, and a Clear all action for notifications
The header bell's unread indicator was an 8px dot -- now a proper count
badge (capped at "9+"), matching the size/legibility of similar badges
elsewhere in the app. Notifications also gains "Clear all" next to
"Mark all read" -- a hard delete of the list (the same clear-all gesture
a phone's own notification centre uses), not another read-state flip.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 17:41:14 +02:00
65801a7843 Add a working event-kind filter to Calendar; merge per-screen headers into the shared navy bar
Calendar gains a real "All/Games/Practices" filter (?kind=), a working
version of the design mock's own "Games only" pill -- its List/Month
toggle isn't reproduced, that's a whole second view mode, not a filter.

base.html gains a header_extra block so a screen can add its own title
into the *same* navy app-header instead of stacking a second,
differently-coloured block underneath it. Me's avatar/name/subtitle row
moved there from its own bg-ink block (a visible colour seam that didn't
match the design canvas's own M5 markup, which keeps it all in one navy
area), and Calendar now shows a "Calendar" title the same way.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 16:14:32 +02:00
515480ce51 Add calendar sync: a combined iCal subscription feed per account
New mobile:calendar_feed (/app/calendar/<token>.ics) -- a standard
webcal-style subscription feed calendar apps can poll, so a family's
schedule shows up in Apple/Google/Outlook Calendar alongside everything
else. Token-authenticated rather than session-authenticated (calendar
apps can't do interactive login); CalendarFeedToken is deliberately not
club-scoped, since the same token works under whichever club subdomain
the request is fetched from -- an account active in more than one club
needs only the one link.

Combined across every managed person (their name goes in the event
title when there's more than one), every event they're invited to
regardless of RSVP status, not capped to Calendar's own 2-week window --
a synced calendar app is exactly where someone wants the whole season
visible. A cancelled event stays in the feed as STATUS:CANCELLED rather
than disappearing, so it's removed properly on the subscriber's next
refresh instead of just vanishing.

New mobile:calendar_feed_settings (linked from Me) shows the webcal://
and https:// links plus a "reset my calendar link" action that
immediately invalidates the old one. Uses the icalendar package for
RFC 5545 generation rather than hand-rolling escaping/line-folding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 15:15:11 +02:00
96fc58e453 Home/Calendar polish: person switcher placement, full-width Calendar, sponsors
- Home: the person switcher (chip row) now lives in the white content area
  instead of the navy header, matching the design canvas's own M1 markup,
  and renders smaller. Home is now the only screen that shows it at all.
- Calendar: dropped the person switcher and the "My schedule"/"All members"
  toggle entirely -- it always shows every event self.managed_people is
  invited to, full stop. Rows now run edge-to-edge (-mx-4) instead of
  living in a rounded, inset card, matching the M3 design canvas's own
  full-bleed layout. (The design mock's List/Month/"Games only" controls
  aren't reproduced -- no real functionality behind them yet.)
- Home's "Needs your answer" card caps at 5 items with a "+N more in
  Calendar" link, so it can't crowd the dues/news cards below it off the
  first screenful.
- Home gains a sponsors strip at the very bottom (horizontally scrolling,
  scrollbar hidden) -- active sponsors only, reshuffled on every request.
  club.services.sponsors.active_sponsors factors the "what counts as
  active" query out of club/api.py's public sponsors endpoint so both it
  and Home share one definition.
- Also removed mobile/views.py's now-fully-dead _PlaceholderScreen and its
  template -- every M1-M7 screen has had a real implementation for a while
  and nothing subclassed it anymore.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 15:00:33 +02:00
4dcf50c4e3 Hide the scrollbar on the person-switcher chip row
Still scrolls the same way, just without the visible scrollbar chrome --
a new .scrollbar-hide utility (scrollbar-width/-ms-overflow-style plus
the WebKit pseudo-element) rather than anything scroll-behaviour-affecting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 14:43:29 +02:00
119341001a Round the app header crest and show initials without a logo
The header crest was management.css's hexagon clip-path mark, and its
no-logo fallback rendered an empty coloured hexagon with no initials at
all. Both now render as a plain circle, and the fallback shows the
club's initials on its own colour -- same source (Club.logo/Club.initials)
the PWA install icon already uses, just inline in the header instead of
a generated PNG.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 14:27:56 +02:00
2cdba6a571 Build M6 Edit personal info for the mobile app
The last of the seven Member-mode screens. A real editable form for the
fields Member actually has (first/last name, date of birth, email,
phone, emergency phone) -- the design mock's national-register-number,
address, allergies/notes and consent-toggle rows have no backing field
and are omitted rather than added as new schema for a screen-building
pass. Two mock rows do have real data and are shown read-only instead:
every guardian via Member.guardians ("Emergency contact"), and any open
onboarding requirement for the person's current-season membership (via
club.services.onboarding.checklist_for) as a banner -- no upload/complete
action, that stays staff-only elsewhere in the platform.

Authorization: the target Member (from the URL) must be one of the
signed-in account's managed_people, checked on both GET and POST -- an
unmanaged id 404s, same as another club's Event/News already does
elsewhere in this app.

This completes M1-M7 (Home, Event detail, Calendar, News article, Me &
my people, Edit personal info, Notifications). Coach mode (C1-C6) is a
separate, later phase per the design doc and is intentionally not
started here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 14:11:56 +02:00
19e1acb93a Build M5 Me & my people for the mobile app
An account-level hub: a header for the signed-in account's own record
(member-since year, a "Team manager {team}" label for someone managing
1-2 teams, otherwise "Staff"), a "People I manage" list linking each
person into M6's edit screen, and a settings card linking into M6 (for
the account's own record) and M7 (Notifications).

The mockup's "Household & contacts"/"Payments & dues" rows and its
"Coach mode" promo have nowhere to lead in this build -- no dedicated
screen for the former, no Coach mode screens at all for the latter (same
precedent base.html's own role switcher already established) -- so
they're omitted rather than built as dead links. "Licence OK" has no
backing field either; each row's meta line uses real current-season
roster data (team + jersey number) instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 14:04:04 +02:00
3559c936f7 Build M4 News article for the mobile app
A photo-hero permalink for one published news item -- same dark-gradient
hero treatment as M2's event detail, with the club's main_photo where one
exists. Visibility mirrors news.tasks.notify_news_published's own
"actually live" gate, so a scheduled-but-not-yet-published item 404s here
the same way. Shows the English translation only when the request's
active language actually is English; native text otherwise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 13:57:50 +02:00
f41a255b61 Build M7 Notifications for the mobile app
A flat, day-grouped notification list (Today / Earlier this week / Older)
scoped to every person the account manages, not just the switched-to one
-- matching how the header's unread badge already counts. The design
mock's per-type cards (RSVP-needed, invoice-due, medical-form, ...) have
no backing model support yet -- only news publishing creates a member
notification today -- so this stays generic rather than fabricating
categories; a notification whose source resolves to a News item links
through to it. Mark-all-read and mark-one-read actions on the same URL,
plus a first UI trigger for the push-subscribe plumbing built earlier.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 13:52:44 +02:00
13f369e10c Build M2 Event detail ("answer for several") for the mobile app
Full event-detail screen: hero header, Face-off/Meet/Where facts (the
design mock's Kit/dressing-room details have no backing Event field, so
they're simply omitted), a per-managed-person RSVP card so a parent with
several kids on the same event can answer for each independently, and a
club-visible squad-response aggregate (counts only, never who answered
what).

Extends the existing quick-RSVP POST (built for M1's Home hero) to also
accept "maybe" for M2's three-way buttons, and adds an optional
next=event_detail redirect target so answering here doesn't bounce back
to Home -- M1's and M3's existing forms are unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 13:44:53 +02:00
29acd22b7f Build M3 Calendar for the mobile app
A bounded chronological agenda (this week / next week, no month paging --
the mobile screen doesn't need the desktop week/month grid
events.services.calendar was built for) scoped to the person switcher's
current selection, plus an "All members" toggle for the whole club's
schedule. Each row links into the still-placeholder-GET event-detail
screen that M2 builds out next.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 10:52:59 +02:00
18627d2843 Build M1 Home for the mobile app
Hero card for the soonest upcoming event with a quick In/Out RSVP,
a "needs your answer" list, a season-dues card, and a news teaser --
all scoped to the person switcher's current selection. The RSVP quick
action posts to the (still placeholder-GET) event-detail URL that a
later screen (M2) will build out fully.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 10:39:18 +02:00
09df5d25b8 Scaffold the mobile member app: PWA shell, push, and app-shell tokens
New `mobile` Django app mounted at /app/ -- the installed PWA for Member
mode (M1-M7, see design_handoff_rosterchief_platform/README.md). Coach
mode (C1-C6) is a later phase and has no routes yet.

Foundation pieces:
- assets/mobile.css: Tailwind v4 theme reusing management.css's design
  tokens (same per-club --tenant-* theming pattern), plus the ice/coach
  accent and mobile's 14px card radius.
- PushSubscription model + pywebpush-based sender (mobile/services/push.py),
  wired to notifications.Notification via a post_save signal so the
  existing notification system gains a push channel without knowing about
  PWAs itself.
- Per-club manifest.webmanifest + service worker (served at /app/sw.js)
  + a server-rendered fallback home-screen icon (club initials on
  secondary_color) for clubs without an uploaded logo -- confirmed with
  the user as the fallback, never a generic RosterChief mark.
- App shell (base.html): navy header, person switcher (every child a
  signed-in parent manages, plus "Me"), bottom tab bar, safe-area insets.
  Vendored htmx + Alpine for the screens built on top of it.
- Placeholder views/routes for all seven M1-M7 screens so the shell is
  fully wired end-to-end before each screen is built out individually.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 10:27:13 +02:00
0ecdeac354 Add a news review workflow and a staff notification area
News gains a PENDING_REVIEW status between draft and published. A
non-editor author (can_add_news but not can_publish_news -- a
coach_manager, not an ADMIN/EDITOR) gets a "Send for review" button
instead of Publish; an editor/admin always sees Publish directly, no
review step. Submitting notifies every ADMIN/EDITOR in-app only (see
notify_members' new send_email=False) -- a review queue that emailed
on every submission would get noisy fast.

The notification area itself: a topbar bell (badge + dropdown, same
<details>/<summary> convention as the sidebar's user-menu, generalised
to a shared .dismissable-details close handler) visible on every page,
plus a fuller "Notifications" card on the dashboard, both fed by a new
notification_bell context processor. "Mark all read" clears the
signed-in staff member's own unread notifications for this club.

This is the reusable notification system's first consumer beyond news
publishing itself -- validates that notify_members()/Notification
generalise the way they were meant to.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 09:52:59 +02:00
5b155fe4d4 Match the identity page's tab switcher to the calendar's bigger pill style
Moved into the filter strip, right-aligned like every other page's own
Calendar/List or Week/Month/Season toggle, and switched from the
bespoke .preview-tab/.preview-tab-active classes (now unused, removed)
to the same raw bg-steel pill markup event_list.html already uses.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 08:58:38 +02:00
3ff882d705 Move email previews onto the Club identity page; add PDF previews
Three top-level tabs (Identity/Email/PDF) replace the standalone
Settings > Email previews page and the small mock that used to live
inside the Identity tab's own Email sub-tab. Email and PDF now show
every real branded email/generated PDF this club can send, rendered
with sample data via a shared _document_preview_card.html partial
(email cards keep the HTML/plain-text toggle; PDF cards -- the
underlying HTML WeasyPrint would turn into a PDF, shown directly
rather than round-tripping through WeasyPrint -- don't need one).

Added a password-reset email preview (previously missing from the
audit -- it's allauth's own send path, not one of this app's, so it
didn't show up in the earlier grep for EmailMultiAlternatives/.send()
calls). Dropped parent-claim-approved: not directly used right now.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 08:52:52 +02:00
d5e396102e Add a Settings > Email previews page showing every branded email with sample data
Renders the real templates every send function uses (claim approved,
membership invoice, invoice reminder) against hand-built placeholder
context -- no real Member/ClubMembership/DuesInvoice row needed, so
nothing here can leak real data. Each card shows the subject line and
toggles between the HTML render (in an iframe, so the email's own
markup can't clash with the page's) and the plain-text body, using
the same bg-steel pill-toggle pattern as the news preview's NL/EN
switch (generalised to .view-toggle-btn, kept .news-lang-btn as an
alias). Admin-only, alongside Club identity in Settings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 22:54:08 +02:00
7ab7ac1982 Fix control panel dropdown text clipping at the bottom of the field
.select's content box (2.125rem height minus padding and border) left
only ~16px for a 14px line -- descenders (g/y/p/q) rendered clipped in
some browsers, most visibly on the Competition edit modal's Sport
dropdown. Bumped .input/.select to 2.25rem for headroom, and .btn/
.btn-square the same amount so a button next to a field in the same
row still lines up -- .btn itself never had the clipping problem
(flex centring), this is purely for row alignment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 22:47:35 +02:00
5889b3740e Add a Competitions card to the control panel's Features page
Competition rows (events.services.competitions' per-club data-source
gate) could previously only be managed through the Django admin.
Adds a card alongside Flags/Switches with create/edit/delete, mirroring
the Flags card's own modal pattern -- no cascading effects to weigh on
delete since Event.competition matches by name, not a foreign key.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 21:40:13 +02:00
3159064e2a Merge the identity page's App/Website preview tabs and make Email functional
App and Website were always shown together already, so giving them
separate tabs implied a toggle that didn't exist -- merged into one
"App & Website" tab. The Email tab now shows a real, colour-bound
sample (plain header with a secondary-coloured crest badge, a
primary-coloured button) matching the actual branded emails this app
already sends -- audited every mail-sending call site (claims.py,
the new invoicing service, and platform billing's club-facing
reminders) and the first two already use the branded shell; billing's
is intentionally unbranded since it's RosterChief invoicing the club,
not the club invoicing its own members.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 21:33:07 +02:00
94d6cbd4e9 Add a dues-invoicing feature: send, track and remind on membership fees
New DuesInvoice model (one per membership, resendable) plus
club.services.invoicing: resolves the best email to invoice (the
member's own, else a parent/guardian's), snapshots the outstanding
balance and a due date on send, and mails a branded HTML invoice
(same club-colour email shell as the parent-claim email) with a
WeasyPrint PDF attached when the native libs are available.

Dues & billing gains a bulk "Send invoice" action (checkbox selection
+ a shared due-in-days prompt), a per-row invoice status column, a
staff-facing invoice detail/PDF page, and a push-button "Send
reminders" action for every sent, unpaid invoice past its own due
date.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 21:23:06 +02:00
44ddf7b6eb Redesign referee management to match the rest of the app's visual language
KPIs now use the same flat card+caption anatomy as the dashboard and
finance pages instead of the older colored-border style. The range
picker moved into the filter strip as a segmented pill control (same
pattern as the calendar's Week/Month/Season toggle), and each game
card now carries a left accent stripe instead of a full-border colour
override, matching parent claims/news list conventions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 09:37:55 +02:00
45380dc282 Remove OnboardingRequirement.order; restrict checklist actions to admin/MEMBER_ADMIN
Every active requirement blocks equally and there's no set order to complete
them in, so the configurable "order" field (and its ordering-by-number) is
gone -- requirements list alphabetically now, both in the admin UI and the
Onboarding requirements settings page.

Also closes a real permission gap found while checking this: marking a
checklist item complete, bypassing it, or reopening it (management/views.py's
MemberRequirementCompleteView/BypassView/IncompleteView) was open to *any*
staff member with page access, not just admin/MEMBER_ADMIN, despite the
member detail page's own Documents card implying otherwise. Switched all
three to MemberAdminRequiredMixin and hid the corresponding buttons/dialog
from anyone who can't use them. The Sign-up page's Bypass action was already
admin-only end to end (the whole page is ClubAdminRequiredMixin-gated), so
no change in practice there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 09:27:05 +02:00
224a3fe4ed Rework the Live preview to match D9's actual screenshot
Replaces the earlier sidebar+small-phone mock with D9's real layout: App/
Website/Email tabs (App is the only one with real content, so these are
static labels not a working control), a full-size phone mock, and a public-
site mock, plus the "Where the brand shows up" list. The phone's "Next up"
card now uses primary, not secondary -- it's a notice, not a call to action,
so it shouldn't compete visually with an actual accent-coloured button.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 09:19:23 +02:00
59d1fce674 Add Club.website; rebuild Colours per D9's exact markup; add a mobile app preview
- club.website (URLField, blank) editable from both the club's own Identity
  page and the platform control panel's club form.
- Colours card rebuilt from the design canvas's literal D9 markup rather
  than the prior in-between version: swatch + mono hex merged into one
  bordered row (the swatch input and hex text input are the same real form
  field, just laid out together), and a contrast-check row of two colour-
  filled boxes instead of a plain text list -- labelled dynamically
  ("Black"/"White" on primary/secondary) since a club can pick a colour
  light enough that black, not white, is the real computed text colour.
- Logo card restyled to D9's dashed drop-zone look.
- Live preview gains a mobile app mock (marked "Coming soon") alongside the
  existing sidebar mock, using the same primary/secondary colours, ahead of
  that surface actually being built.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 09:13:03 +02:00
a0226d51a9 Match the News list rows and filter chips to D8's exact spec
Pulled the literal markup from the design canvas (RosterChief Platform.dc.html)
rather than the prose summary: filter chips are 28px/6px-radius with a dark
ink background when active (not the club accent colour, and not a pill/
button shape), and each row is D8's real three-line anatomy -- status pill +
mono meta on their own line, a 20px condensed headline, then a muted author/
audience line -- not the single dense line from the last pass. It reads as a
list because rows are plain hairline-divided strips (#EEF0F3, exactly this
app's --color-rule), not because the type was shrunk.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 09:07:12 +02:00
e5427c31dd Widen News' filter row, drop nested count badges; row Attendance/Documents/Referee cards
News list: the left pane is wider (380px -> 460px) so the four status chips
fit on one line, and each chip's count is now plain text in the pill itself
("All 42") rather than a nested badge -- matches D8's own "All 42 / Drafts 3"
wording literally. Member detail: Attendance, Documents and Referee
eligibility now sit side by side in one row instead of stacking as three
full-width cards.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 09:03:37 +02:00
fc3901fb5a Add a live D9-style preview to the Club identity page
Two columns (form left, preview right, matching D9) with a mini mock of this
app's own sidebar + a sample button -- primary_color/secondary_color are
what those actually control (base.html's --tenant-* properties), so the
preview reflects a real surface rather than D9's aspirational App/Website/
Email tabs. Colour swatch pickers sync with the existing hex text fields in
both directions, and a contrast-ratio row mirrors Club._content_color_for's
exact WCAG math client-side, so the ✓/⚠ shown is the real number the server
computes on save.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 09:00:13 +02:00
8baf3f7b3d Add the D7 attendance sparkline to member detail; tighten the News list rows
Member detail: a 12-bar season attendance sparkline (present/absent/upcoming)
beside Present/Absent/No-reply totals, shown for any rostered non-guardian
member with events this season (events.services.attendance.member_attendance_
sparkline/_counts). News list: rows are one dense line (status pill inline
with the headline, everything else folded into a single meta line) instead
of the previous multi-line stacked layout that read as a small card per item.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 08:55:44 +02:00
331b0b8227 Redesign the News page around D8's list + preview split view
Replaces the flat news list with a D8-style two-pane layout: a filterable
list on the left (All/Drafts/Scheduled/Published chips with counts) and a
preview of whichever item is selected on the right, reusing the same
article/photos/publish markup news_detail.html already had (now factored
into _news_preview.html so both pages share it). Adds an NL/EN language
toggle to the article preview -- the club's two content languages are now
switchable in place, with a fallback note when a translation is missing,
instead of a separate "English" card that only appeared once translated.
The standalone news_detail.html permalink page is unchanged behaviourally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 08:30:24 +02:00
441367362d Build the D5 events calendar, redesign event forms, and polish the events UI
Adds the Week/Month/Season calendar to the Events page (team/group-scoped
visibility, kind-colored blocks, a repeating-series indicator) alongside the
existing table as a List view, and redesigns event/event-series creation
around a kind-first picker with progressive disclosure instead of one long
flat form. Also: an accessible radio-based kind picker (no redundant
select-next-to-cards), a fixed week-grid layout bug, a filterable single-list
redesign of the event detail page's RSVP/responses modal, a tidied-up
recurrence field layout, and assorted button/field height and width fixes
across the referee panel and event forms.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-20 01:08:53 +02:00
adf1120358 Checkpoint: management app redesign, onboarding/signup workflow, and events calendar backend
Large uncommitted body of work accumulated across sessions on this branch --
committing as a checkpoint so it's tracked and future worktree-isolated agents
see the real codebase instead of a stale ancestor commit. Covers the
management app's dedicated Tailwind theme and templates, the club onboarding
requirement/signup workflow (club/services/onboarding.py, requirement/status
models, sign-up dashboard), fee/status auto-activation decoupling, referee
management, and the new events calendar grid service layer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-19 23:34:43 +02:00