Coach mode: bench-attendance filter overhaul, squad player detail, add-staff
- Attendance sheet: "All"/"Goalies" chips replaced with "Responded" (the new default -- present/selected/maybe) and "Silent"; goalies are just another player for a practice, and neither silent nor declined members are expected to show up, so the default view skips both. - Today's KPI header gains an "Out" tile alongside Squad/In/Silent. - Squad screen: each roster row now opens a per-player detail sheet with season attendance stats, tap-to-call buttons (the player's own phone/ emergency phone, plus each guardian's for a child), and -- for whoever manages the team -- the position/jersey/captaincy edit and a remove- from-roster action that used to be desktop-only. - Staff section gets its own "Add" entry point, mirroring the roster's bulk-add flow with a shared position picker (StaffAssignment.position is required, unlike a roster spot's). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
48
mobile/templates/mobile/coach/add_staff.html
Normal file
48
mobile/templates/mobile/coach/add_staff.html
Normal file
@@ -0,0 +1,48 @@
|
||||
{% extends "mobile/coach/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% comment %}
|
||||
Squad screen's staff "Add" entry point -- see CoachAddStaffView's own
|
||||
docstring for why this needs a shared position picker up top, unlike
|
||||
CoachAddPlayerView/C6's plain checkbox list (a roster spot's position is
|
||||
optional; StaffAssignment's is required).
|
||||
{% endcomment %}
|
||||
|
||||
{% block header_extra %}
|
||||
<div class="mt-3 flex items-center justify-between">
|
||||
<span class="font-display text-lg font-extrabold text-white uppercase">{% blocktrans with team=active_team.name %}Add staff to {{ team }}{% endblocktrans %}</span>
|
||||
</div>
|
||||
{% endblock header_extra %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" action="{% url "mobile:coach_add_staff" %}" x-data="{ count: 0 }" hx-boost="false">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="m-card p-3.5">
|
||||
<label class="mb-1 block text-xs font-semibold text-muted uppercase tracking-wide">{% trans "Position" %}</label>
|
||||
<select class="h-11 w-full rounded-lg border border-stroke bg-paper px-3 text-[15px] text-ink focus:border-ink focus:outline-none" name="position" required>
|
||||
<option value="">{% trans "Choose a position" %}</option>
|
||||
{% for position in positions %}
|
||||
<option value="{{ position.pk }}">{{ position.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="m-card mt-3 flex flex-col overflow-hidden">
|
||||
{% for candidate in candidates %}
|
||||
<label class="flex items-center gap-3 px-4 py-2.5 {% if not forloop.last %}border-b border-rule{% endif %}">
|
||||
{% include "mobile/_avatar.html" with person=candidate size_class="h-9 w-9" text_class="text-xs" %}
|
||||
<span class="min-w-0 flex-1 text-sm font-semibold text-ink">{{ candidate.get_full_name }}</span>
|
||||
<input class="h-5 w-5 shrink-0 accent-ink" type="checkbox" name="member" value="{{ candidate.pk }}" @change="count = $el.form.querySelectorAll('input[name=member]:checked').length">
|
||||
</label>
|
||||
{% empty %}
|
||||
<div class="px-4 py-6 text-center text-sm text-muted">{% trans "No one is eligible to be added." %}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<button class="btn btn-dark mt-4 w-full" type="submit" :disabled="count === 0">
|
||||
<span x-show="count === 0">{% trans "Select people to add" %}</span>
|
||||
<span x-show="count > 0" x-cloak>{% trans "Add" %} (<span x-text="count"></span>)</span>
|
||||
</button>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
@@ -26,14 +26,11 @@
|
||||
{% block content %}
|
||||
<div class="flex gap-2">
|
||||
<a class="flex h-9 flex-1 items-center justify-center rounded-full font-display text-xs font-extrabold tracking-wide uppercase {% if not filter_param %}bg-ink text-white{% else %}border border-line bg-white text-muted{% endif %}" href="?">
|
||||
{% trans "All" %} {{ total_count }}
|
||||
{% trans "Responded" %} {{ responded_count }}
|
||||
</a>
|
||||
<a class="flex h-9 flex-1 items-center justify-center rounded-full font-display text-xs font-extrabold tracking-wide uppercase {% if filter_param == "silent" %}bg-ink text-white{% else %}border border-line bg-white text-muted{% endif %}" href="?filter=silent">
|
||||
{% trans "Silent" %} {{ silent_count }}
|
||||
</a>
|
||||
<a class="flex h-9 flex-1 items-center justify-center rounded-full font-display text-xs font-extrabold tracking-wide uppercase {% if filter_param == "goalies" %}bg-ink text-white{% else %}border border-line bg-white text-muted{% endif %}" href="?filter=goalies">
|
||||
{% trans "Goalies" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{% url "mobile:coach_attendance" event.pk %}" hx-boost="false">
|
||||
|
||||
97
mobile/templates/mobile/coach/roster_member.html
Normal file
97
mobile/templates/mobile/coach/roster_member.html
Normal file
@@ -0,0 +1,97 @@
|
||||
{% extends "mobile/coach/base.html" %}
|
||||
{% load i18n lucide %}
|
||||
|
||||
{% comment %}
|
||||
Squad screen's per-player detail sheet -- see CoachRosterMemberView's own
|
||||
docstring. Call buttons/guardian section mirror management/templates/
|
||||
management/member_detail.html's "Personal information" card (same tel:
|
||||
pattern, same guardians-only-if-non-empty gate), just laid out for a
|
||||
phone instead of a desktop two-column grid.
|
||||
{% endcomment %}
|
||||
|
||||
{% block header_extra %}
|
||||
<div class="mt-3 flex items-center gap-3">
|
||||
{% include "mobile/_avatar.html" with person=member size_class="h-12 w-12" text_class="text-base" %}
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="truncate font-display text-lg leading-none font-extrabold text-white uppercase">{{ member.get_full_name }}</span>
|
||||
{% if membership.is_captain %}<span class="font-display text-xs font-extrabold text-ice">C</span>{% endif %}
|
||||
{% if membership.is_alternate_captain %}<span class="font-display text-xs font-extrabold text-ice">A</span>{% endif %}
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-on-dark">{{ membership.position|default:_("No position set") }} {% if membership.jersey_number %}· #{{ membership.jersey_number }}{% endif %}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock header_extra %}
|
||||
|
||||
{% block content %}
|
||||
<div class="m-card p-4">
|
||||
<div class="mb-2 font-display text-xs font-extrabold tracking-wide text-muted uppercase">{% trans "Contact" %}</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
{% if member.phone %}
|
||||
<a class="btn w-full gap-2 border border-line bg-white text-ink" href="tel:{{ member.phone.as_international }}">{% lucide "phone" size=16 %} {% trans "Call" %}</a>
|
||||
{% endif %}
|
||||
{% if member.emergency_phone %}
|
||||
<a class="btn w-full gap-2 border border-club-dark bg-white text-club-dark" href="tel:{{ member.emergency_phone.as_international }}">{% lucide "shield-alert" size=16 %} {% trans "Emergency call" %}</a>
|
||||
{% endif %}
|
||||
{% for guardian in guardians %}
|
||||
{% if guardian.phone %}
|
||||
<a class="btn w-full gap-2 border border-line bg-white text-ink" href="tel:{{ guardian.phone.as_international }}">{% lucide "phone" size=16 %} {% blocktrans with name=guardian.get_full_name %}Call {{ name }}{% endblocktrans %}</a>
|
||||
{% endif %}
|
||||
{% if guardian.emergency_phone %}
|
||||
<a class="btn w-full gap-2 border border-club-dark bg-white text-club-dark" href="tel:{{ guardian.emergency_phone.as_international }}">{% lucide "shield-alert" size=16 %} {% blocktrans with name=guardian.get_full_name %}Emergency: {{ name }}{% endblocktrans %}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if not member.phone and not member.emergency_phone and not guardians %}
|
||||
<p class="text-sm text-muted">{% trans "No phone numbers on file." %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m-card mt-3 p-4">
|
||||
<div class="mb-2 font-display text-xs font-extrabold tracking-wide text-muted uppercase">{% trans "Attendance this season" %}</div>
|
||||
<div class="flex gap-4">
|
||||
<div>
|
||||
<div class="font-display text-2xl leading-none font-extrabold text-ok tabular-nums">{{ attendance_counts.present }}</div>
|
||||
<div class="mt-1 font-display text-[10px] font-extrabold tracking-wide text-muted uppercase">{% trans "Present" %}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-display text-2xl leading-none font-extrabold text-club tabular-nums">{{ attendance_counts.absent }}</div>
|
||||
<div class="mt-1 font-display text-[10px] font-extrabold tracking-wide text-muted uppercase">{% trans "Absent" %}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-display text-2xl leading-none font-extrabold text-dim tabular-nums">{{ attendance_counts.no_reply }}</div>
|
||||
<div class="mt-1 font-display text-[10px] font-extrabold tracking-wide text-muted uppercase">{% trans "No reply" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if form %}
|
||||
<form class="m-card mt-3 flex flex-col gap-3 p-4" method="post" action="{% url "mobile:coach_roster_member" membership.pk %}" hx-boost="false">
|
||||
{% csrf_token %}
|
||||
<div class="mb-1 font-display text-xs font-extrabold tracking-wide text-muted uppercase">{% trans "Roster details" %}</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold text-muted uppercase tracking-wide">{% trans "Position" %}</label>
|
||||
{{ form.position }}
|
||||
{% if form.position.errors %}<p class="mt-1 text-xs text-club-dark">{{ form.position.errors.0 }}</p>{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-semibold text-muted uppercase tracking-wide">{% trans "Jersey number" %}</label>
|
||||
{{ form.jersey_number }}
|
||||
{% if form.jersey_number.errors %}<p class="mt-1 text-xs text-club-dark">{{ form.jersey_number.errors.0 }}</p>{% endif %}
|
||||
</div>
|
||||
<label class="flex items-center gap-2 text-sm text-ink">
|
||||
{{ form.is_captain }} {% trans "Captain" %}
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm text-ink">
|
||||
{{ form.is_alternate_captain }} {% trans "Alternate captain" %}
|
||||
</label>
|
||||
<button class="btn btn-dark mt-1 w-full" type="submit">{% trans "Save" %}</button>
|
||||
</form>
|
||||
|
||||
{% trans "Remove this player from the roster? This cannot be undone." as remove_confirm_text %}
|
||||
<form class="mt-3" method="post" action="{% url "mobile:coach_roster_remove" membership.pk %}" hx-boost="false" onsubmit="return confirm('{{ remove_confirm_text|escapejs }}')">
|
||||
{% csrf_token %}
|
||||
<button class="btn w-full gap-2 bg-club text-white" type="submit">{% lucide "trash-2" size=16 %} {% trans "Remove from roster" %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
{% comment %}
|
||||
Bottom-tab "Squad" -- roster + staff for the active team, current season.
|
||||
See CoachSquadView's own docstring for why there's no per-row edit here
|
||||
(jersey number/position/captaincy stays a desktop-only action for now).
|
||||
Each roster row links through to CoachRosterMemberView (stats, tap-to-
|
||||
call, edit, remove) -- see that view's own docstring.
|
||||
{% endcomment %}
|
||||
|
||||
{% block content %}
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="m-card overflow-hidden">
|
||||
{% for membership in roster %}
|
||||
{% if not forloop.first %}<div class="h-px bg-rule"></div>{% endif %}
|
||||
<div class="flex items-center gap-3 p-3.5">
|
||||
<a class="flex items-center gap-3 p-3.5" href="{% url "mobile:coach_roster_member" membership.pk %}">
|
||||
<div class="w-8 shrink-0 text-center font-display text-lg font-extrabold text-ink tabular-nums">{{ membership.jersey_number|default:"—" }}</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-[15px] font-semibold text-ink">
|
||||
@@ -35,7 +35,8 @@
|
||||
</div>
|
||||
<div class="text-xs text-muted">{{ membership.position|default:"—" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% lucide "chevron-right" size=18 class="shrink-0 text-dim" %}
|
||||
</a>
|
||||
{% empty %}
|
||||
<div class="p-6 text-center text-sm text-muted">{% trans "No one on the roster for this season yet." %}</div>
|
||||
{% endfor %}
|
||||
@@ -43,7 +44,14 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="mb-2 font-display text-xs font-extrabold tracking-wide text-muted uppercase">{% trans "Staff" %}</div>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<span class="font-display text-xs font-extrabold tracking-wide text-muted uppercase">{% trans "Staff" %}</span>
|
||||
{% if can_manage_active_team %}
|
||||
<a class="flex items-center gap-1 font-display text-xs font-extrabold tracking-wide text-club uppercase" href="{% url "mobile:coach_add_staff" %}">
|
||||
{% lucide "user-plus" size=14 %} {% trans "Add" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="m-card overflow-hidden">
|
||||
{% for assignment in staff %}
|
||||
{% if not forloop.first %}<div class="h-px bg-rule"></div>{% endif %}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid grid-cols-3 gap-2.5">
|
||||
<div class="grid grid-cols-4 gap-2">
|
||||
<div class="m-card p-3 text-center">
|
||||
<div class="font-display text-2xl leading-none font-extrabold text-ink">{{ squad_count }}</div>
|
||||
<div class="mt-1 font-display text-[10px] font-extrabold tracking-wide text-muted uppercase">{% trans "Squad" %}</div>
|
||||
@@ -37,7 +37,11 @@
|
||||
<div class="mt-1 font-display text-[10px] font-extrabold tracking-wide text-muted uppercase">{% trans "In" %}</div>
|
||||
</div>
|
||||
<div class="m-card p-3 text-center">
|
||||
<div class="font-display text-2xl leading-none font-extrabold text-club">{{ silent_count }}</div>
|
||||
<div class="font-display text-2xl leading-none font-extrabold text-club">{{ out_count }}</div>
|
||||
<div class="mt-1 font-display text-[10px] font-extrabold tracking-wide text-muted uppercase">{% trans "Out" %}</div>
|
||||
</div>
|
||||
<div class="m-card p-3 text-center">
|
||||
<div class="font-display text-2xl leading-none font-extrabold text-warn-text">{{ silent_count }}</div>
|
||||
<div class="mt-1 font-display text-[10px] font-extrabold tracking-wide text-muted uppercase">{% trans "Silent" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user