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
This commit is contained in:
2026-08-21 22:36:29 +02:00
parent e211584696
commit ccd4e0aa13
6 changed files with 252 additions and 1 deletions

View File

@@ -0,0 +1,50 @@
{% extends "mobile/coach/base.html" %}
{% load i18n %}
{% comment %}
C6 -- design_handoff_rosterchief_platform/README.md's C6 section: a
checkbox per eligible candidate rather than one member at a time (see
CoachAddPlayerView's own docstring for why, and what's scoped down from
the mock -- no "Age eligible" filter, no fixed footer).
{% 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 to {{ team }}{% endblocktrans %}</span>
<span class="font-mono text-xs text-on-dark">{% blocktrans count counter=squad_count %}{{ counter }} on squad{% plural %}{{ counter }} on squad{% endblocktrans %}</span>
</div>
{% endblock header_extra %}
{% 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" %}
</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 == "suggested" %}bg-ink text-white{% else %}border border-line bg-white text-muted{% endif %}" href="?filter=suggested">
{% trans "Suggested" %}
</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 == "no_team" %}bg-ink text-white{% else %}border border-line bg-white text-muted{% endif %}" href="?filter=no_team">
{% trans "No team" %}
</a>
</div>
<form method="post" action="{% url "mobile:coach_add_player" %}" x-data="{ count: 0 }">
{% csrf_token %}
<div class="m-card 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 matches this filter." %}</div>
{% endfor %}
</div>
<button class="btn btn-dark mt-4 w-full" type="submit" :disabled="count === 0">
<span x-show="count === 0">{% trans "Select players to add" %}</span>
<span x-show="count > 0" x-cloak>{% trans "Add" %} (<span x-text="count"></span>)</span>
</button>
</form>
{% endblock content %}

View File

@@ -20,6 +20,7 @@
<div class="flex gap-2">
<a class="btn btn-dark flex-1" href="{% url "mobile:coach_create_event" %}">{% trans "New event" %}</a>
<a class="btn btn-secondary flex-1" href="{% url "mobile:coach_create_news" %}">{% trans "New post" %}</a>
<a class="btn btn-secondary flex-1" href="{% url "mobile:coach_add_player" %}">{% trans "Add player" %}</a>
</div>
{% endif %}