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
86 lines
4.3 KiB
Python
86 lines
4.3 KiB
Python
"""Shared scaffolding for every Coach-mode screen (C1-C6) -- the dark-chrome
|
|
mirror of mobile/mixins.py's PersonScopeMixin. Scoped by *team*, not by
|
|
managed people: a coach acts on one team at a time (the header's team-picker
|
|
pill), switching which team is "active" rather than aggregating across
|
|
several people the way Member mode's person switcher does.
|
|
"""
|
|
|
|
from club.services.access import current_season, teams_managed_by, teams_staffed_by
|
|
from members.models import Member
|
|
from members.views import ClubScopedPublicMixin
|
|
from teams.models import StaffAssignment
|
|
|
|
#: Session key remembering which team was last active, so navigating between
|
|
#: coach screens (or leaving and coming back) doesn't reset the picker to
|
|
#: whichever team happens to sort first.
|
|
ACTIVE_TEAM_SESSION_KEY = "coach_active_team_id"
|
|
|
|
|
|
class CoachScopeMixin(ClubScopedPublicMixin):
|
|
"""Resolves the signed-in account's Coach-mode standing: which teams
|
|
they're on the staff of at all (``staffed_teams`` -- visibility, any
|
|
position, see club.services.access.teams_staffed_by), which of those
|
|
they actually manage (``managed_teams`` -- management position only,
|
|
current season, teams_managed_by), and which one is currently "active"
|
|
(the team-picker pill on C1's header).
|
|
|
|
Every Coach view still needs its own ``LoginRequiredMixin`` (kept
|
|
separate, same as PersonScopeMixin, so a view composes whichever other
|
|
mixins it needs on top). Nothing here 404s when ``staffed_teams`` is
|
|
empty -- each screen renders its own "not staffing a team yet" empty
|
|
state instead, same judgment call as HomeView's "No one to show yet".
|
|
|
|
Actions gated on ``can_manage_active_team`` are hidden in templates, not
|
|
disabled -- a coach on staff but without a management position (e.g. a
|
|
physio) can see Coach mode but shouldn't see edit affordances they don't
|
|
have the authority to use.
|
|
|
|
``active_team_role`` is ``self.me``'s own StaffAssignment.position on
|
|
``active_team`` this season (e.g. "Team manager", "Physio") -- the
|
|
header shows this instead of a hardcoded "Head coach", since not every
|
|
staffed team is one the account holder actually coaches.
|
|
"""
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.me = Member.objects.filter(user=request.user).first() if request.user.is_authenticated else None
|
|
self.staffed_teams = list(teams_staffed_by(request.user, request.club)) if request.user.is_authenticated else []
|
|
self.managed_teams = list(teams_managed_by(request.user, request.club)) if request.user.is_authenticated else []
|
|
self.active_team = self._resolve_active_team(request)
|
|
self.can_manage_active_team = self.active_team is not None and self.active_team in self.managed_teams
|
|
self.active_team_role = self._resolve_active_team_role(request)
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def _resolve_active_team_role(self, request):
|
|
if self.me is None or self.active_team is None:
|
|
return None
|
|
assignment = StaffAssignment.objects.filter(member=self.me, team=self.active_team, season=current_season(request.club)).select_related("position").first()
|
|
return assignment.position if assignment is not None else None
|
|
|
|
def _resolve_active_team(self, request):
|
|
requested_id = request.GET.get("team")
|
|
for team in self.staffed_teams:
|
|
if requested_id and str(team.pk) == requested_id:
|
|
request.session[ACTIVE_TEAM_SESSION_KEY] = str(team.pk)
|
|
return team
|
|
|
|
stored_id = request.session.get(ACTIVE_TEAM_SESSION_KEY)
|
|
for team in self.staffed_teams:
|
|
if stored_id and str(team.pk) == stored_id:
|
|
return team
|
|
|
|
return self.staffed_teams[0] if self.staffed_teams else None
|
|
|
|
def get_context_data(self, **kwargs):
|
|
kwargs.setdefault("active_tab", getattr(self, "active_tab", ""))
|
|
kwargs.setdefault("screen_title", getattr(self, "screen_title", ""))
|
|
|
|
return super().get_context_data(
|
|
me=self.me,
|
|
staffed_teams=self.staffed_teams,
|
|
active_team=self.active_team,
|
|
can_manage_active_team=self.can_manage_active_team,
|
|
active_team_role=self.active_team_role,
|
|
season=current_season(self.request.club),
|
|
**kwargs,
|
|
)
|