Files
RosterChief/mobile/templates/mobile/base.html
Bernard Siebens 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

124 lines
7.4 KiB
HTML

{% load static i18n %}
{% comment %}
App shell for Member mode (M1-M7) -- design_handoff_rosterchief_platform/README.md.
Standalone, like management/base.html and controlpanel/base.html: its own
stylesheet (assets/mobile.css), no daisyUI, no shared nav. Mobile-first,
viewport-fit=cover + env(safe-area-inset-*) throughout (see mobile.css's own
file banner) rather than the iOS bezel in the design canvas, which is
presentation-only.
The Coach/Member role switcher renders once has_coach_access is true (an
account with >=1 current-season staff assignment -- mobile/mixins.py's
PersonScopeMixin) -- Coach mode (C1-C6, mobile/coach_views.py) is being
built out screen by screen; only what's actually shipped is linked to.
{% endcomment %}
<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE|default:"en" }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="{{ club.primary_color|default:"#101e36" }}">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="mobile-web-app-capable" content="yes">
<title>{% block title %}{{ screen_title }} &middot; {{ club.name }}{% endblock title %}</title>
<link rel="manifest" href="{% url "mobile:manifest" %}">
<link rel="apple-touch-icon" href="{% url "mobile:icon" size=192 %}">
<link rel="icon" href="{% url "mobile:icon" size=192 %}">
<link rel="stylesheet" href="{% static "css/mobile.css" %}">
{% if club.secondary_color %}
<style>
:root {
--tenant-club: {{ club.secondary_color }};
--tenant-club-dark: color-mix(in srgb, {{ club.secondary_color }} 80%, black);
--tenant-club-content: {{ club.secondary_content_color }};
}
</style>
{% endif %}
{% if club.primary_color %}
<style>
:root { --tenant-navy: {{ club.primary_color }}; }
</style>
{% endif %}
<script src="{% static "js/htmx.js" %}" defer></script>
<script src="{% static "js/alpine.js" %}" defer></script>
{% block extra_head %}{% endblock extra_head %}
</head>
<body class="flex h-screen flex-col overflow-hidden bg-paper font-sans text-slate" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' data-vapid-public-key="{{ vapid_public_key }}" data-csrftoken="{{ csrf_token }}">
<header class="app-header">
<div class="flex items-center gap-2.5">
<a class="flex items-center gap-2.5" href="{% url "mobile:home" %}">
{% if club.logo %}
<img class="app-crest" src="{{ club.logo.url }}" alt="">
{% else %}
<span class="app-crest app-crest-fallback">{{ club.initials }}</span>
{% endif %}
<span class="min-w-0">
<span class="block truncate font-display text-[19px] leading-none font-extrabold text-white uppercase">{{ club.name }}</span>
{% if season %}<span class="block font-mono text-xs text-on-dark">{% blocktrans with name=season.name %}Season {{ name }}{% endblocktrans %}</span>{% endif %}
</span>
</a>
<div class="flex-1"></div>
<a class="relative flex h-11 w-11 shrink-0 items-center justify-center" href="{% url "mobile:notifications" %}" aria-label="{% trans "Notifications" %}">
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
{% if unread_notification_count %}
<span class="absolute -top-1 -right-1 flex h-[19px] min-w-[19px] items-center justify-center rounded-full bg-club px-1 font-mono text-[11px] font-bold text-white" style="border: 2px solid var(--color-navy)">
{% if unread_notification_count > 9 %}9+{% else %}{{ unread_notification_count }}{% endif %}
</span>
{% endif %}
</a>
</div>
{% if has_coach_access %}
<div class="role-switcher">
<a class="role-switcher-item role-switcher-item-active" href="{% url "mobile:home" %}">{% trans "Member" %}</a>
<a class="role-switcher-item" href="{% url "mobile:coach_today" %}">{% trans "Coach" %}</a>
</div>
{% endif %}
{% block header_extra %}{% endblock header_extra %}
</header>
<main class="flex-1 overflow-y-auto">
<div class="flex flex-col gap-4 px-4 py-4">
{% if messages %}
<div class="flex flex-col gap-2">
{% for message in messages %}
<div class="m-card p-3 text-sm font-medium text-ink">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% block content %}{% endblock content %}
</div>
</main>
<nav class="tab-bar">
<a class="tab-bar-item {% if active_tab == "home" %}tab-bar-item-active{% endif %}" href="{% url "mobile:home" %}">
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9.5 12 3l9 6.5V21a1 1 0 0 1-1 1h-5v-7H9v7H4a1 1 0 0 1-1-1Z"/></svg>
<span class="tab-bar-label">{% trans "Home" %}</span>
</a>
<a class="tab-bar-item {% if active_tab == "calendar" %}tab-bar-item-active{% endif %}" href="{% url "mobile:calendar" %}">
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
<span class="tab-bar-label">{% trans "Calendar" %}</span>
</a>
<a class="tab-bar-item {% if active_tab == "news" %}tab-bar-item-active{% endif %}" href="{% url "mobile:news_list" %}">
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 20H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h9l5 5v9a2 2 0 0 1-2 2Z"/><path d="M9 13h6M9 17h6M9 9h1"/></svg>
<span class="tab-bar-label">{% trans "News" %}</span>
</a>
<a class="tab-bar-item {% if active_tab == "me" %}tab-bar-item-active{% endif %}" href="{% url "mobile:me" %}">
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4.4 3.6-8 8-8s8 3.6 8 8"/></svg>
<span class="tab-bar-label">{% trans "Me" %}</span>
</a>
</nav>
<script src="{% static "js/mobile-app.js" %}" defer></script>
{% block extra_body %}{% endblock extra_body %}
</body>
</html>