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
This commit is contained in:
2026-08-21 23:21:27 +02:00
parent 64d0fec547
commit 1d159ca5de
13 changed files with 421 additions and 45 deletions

View File

@@ -26,7 +26,7 @@ from management.forms import EventForm, NewsForm
from members.models import Member
from news.models import News
from news.services import notify_editors_of_pending_review
from teams.models import Team, TeamMembership
from teams.models import StaffAssignment, Team, TeamMembership
from teams.services import eligible_roster_members
from .coach_mixins import CoachScopeMixin
@@ -535,3 +535,46 @@ class CoachLineupPublishView(CoachScopeMixin, LoginRequiredMixin, View):
publish_lineup(lineup)
notify(request, f"s|{_('Line-up published')}|{_('Selected players have been notified.')}")
return HttpResponseRedirect(reverse("mobile:coach_lineup", kwargs={"event_id": event.pk}))
class CoachSquadView(CoachScopeMixin, LoginRequiredMixin, TemplateView):
"""Bottom-tab "Squad" -- the active team's roster and staff for the
current season, view-only beyond the "Add player" entry point (which
reuses CoachAddPlayerView/C6). No per-row edit here (jersey number,
position, captaincy) -- that stays a desktop-only action for now via
management.forms.TeamMembershipForm; this screen is about seeing the
squad, not managing individual rows from a phone.
"""
template_name = "mobile/coach/squad.html"
screen_title = _("Squad")
active_tab = "coach_squad"
def get_context_data(self, **kwargs):
season = current_season(self.request.club)
roster, staff = [], []
if self.active_team is not None and season is not None:
roster = list(TeamMembership.objects.filter(team=self.active_team, season=season).select_related("member", "position").order_by("position__ordering", "member__last_name"))
staff = list(StaffAssignment.objects.filter(team=self.active_team, season=season).select_related("member", "position").order_by("position__ordering", "member__last_name"))
return super().get_context_data(roster=roster, staff=staff, **kwargs)
class CoachScheduleView(CoachScopeMixin, LoginRequiredMixin, TemplateView):
"""Bottom-tab "Schedule" -- every upcoming event for the active team, full
stop (not Today's own "just the next session" scope). Each row jumps
straight into the coach-relevant action -- Bench attendance for a
practice, the Line-up for a game -- rather than mobile:event_detail (the
Member-shell RSVP page a coach browsing their own team's schedule has no
use for)."""
template_name = "mobile/coach/schedule.html"
screen_title = _("Schedule")
active_tab = "coach_schedule"
def get_context_data(self, **kwargs):
events = []
if self.active_team is not None:
events = list(Event.objects.filter(teams=self.active_team, cancelled=False, start__gte=timezone.now()).order_by("start"))
return super().get_context_data(events=events, **kwargs)