Files
RosterChief/management/templates/management/team_detail.html
Bernard Siebens adf1120358 Checkpoint: management app redesign, onboarding/signup workflow, and events calendar backend
Large uncommitted body of work accumulated across sessions on this branch --
committing as a checkpoint so it's tracked and future worktree-isolated agents
see the real codebase instead of a stale ancestor commit. Covers the
management app's dedicated Tailwind theme and templates, the club onboarding
requirement/signup workflow (club/services/onboarding.py, requirement/status
models, sign-up dashboard), fee/status auto-activation decoupling, referee
management, and the new events calendar grid service layer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-19 23:34:43 +02:00

307 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "management/base.html" %}
{% load i18n lucide static ui %}
{% block heading %}{{ team.name }}{% endblock heading %}
{% block topbar_context %}
<span class="badge badge-outline">{{ team.short_name }}</span>
{% if seasons %}
<form method="get" class="flex items-center gap-2">
<select name="season" class="select w-auto">
{% for season in seasons %}
<option value="{{ season.pk }}" {% if season.pk == selected_season.pk %}selected{% endif %}>{% trans "Season" %} {{ season.start_date|date:"Y" }} - {{ season.end_date|date:"Y" }}</option>
{% endfor %}
</select>
{# No btn-sm: .select is height:2.25rem, matching plain .btn -- btn-sm would sit 6px shorter and misalign in the row. #}
<button class="btn btn-outline gap-2" type="submit">{% lucide "filter" size=14 %} {% trans "View" %}</button>
</form>
{% endif %}
{% endblock topbar_context %}
{% block actions %}
{% if is_club_admin %}
<a class="btn btn-outline gap-2" href="{% url 'management:team_update' team.pk %}">{% lucide "pencil" size=16 %} {% trans "Edit" %}</a>
{% endif %}
{% endblock actions %}
{% block panel %}
{% if not selected_season %}
<div class="alert alert-warning">
{% lucide "calendar-x" size=20 %}
<span>{% trans "This club has no seasons yet, so there's no roster or staff to show." %}</span>
</div>
{% else %}
{% if can_manage %}
{% if team_photo %}
{% trans "Replace photo" as photo_modal_title %}
{% else %}
{% trans "Upload photo" as photo_modal_title %}
{% endif %}
{% url 'management:team_photo_set' team.pk selected_season.pk as team_photo_set_url %}
{% include "controlpanel/_modal_form.html" with modal_id="team_photo_modal" title=photo_modal_title form=team_photo_form action_url=team_photo_set_url submit_label="Save" submit_icon="save" %}
{% if team_photo %}
{% trans "Remove team photo?" as remove_photo_body %}
{% trans "Remove photo" as remove_photo_title %}
{% url 'management:team_photo_delete' team.pk selected_season.pk as team_photo_delete_url %}
{% include "controlpanel/_confirm_modal.html" with modal_id="team_photo_delete_modal" title=remove_photo_title body=remove_photo_body action_url=team_photo_delete_url submit_label="Remove" submit_icon="trash-2" %}
{% endif %}
{% endif %}
{# --- hero: team cover photo (or an ink fallback), headline meta, photo controls and the bulk-add CTA --- #}
<div class="relative flex h-[180px] shrink-0 flex-col justify-between overflow-hidden rounded-box bg-ink p-6">
{% if team_photo %}
<img class="absolute inset-0 h-full w-full object-cover" src="{{ team_photo.image.url }}" alt="">
<div class="absolute inset-0" style="background:linear-gradient(90deg, rgba(11,18,32,.95) 30%, rgba(11,18,32,.4))"></div>
{% endif %}
<div class="relative flex items-center justify-end gap-2">
{% if can_manage %}
{% trans "Upload photo" as upload_photo_label %}
{% trans "Replace photo" as replace_photo_label %}
{% if not team_photo %}
<span class="text-xs text-on-dark-faint">{% trans "No photo uploaded for this season yet." %}</span>
{% endif %}
<button class="btn btn-ghost btn-sm gap-2 bg-white/15 text-white hover:bg-white/25" type="button" onclick="document.getElementById('team_photo_modal').showModal()">
{% if team_photo %}
{% lucide "pencil" size=14 %} {{ replace_photo_label }}
{% else %}
{% lucide "upload" size=14 %} {{ upload_photo_label }}
{% endif %}
</button>
{% if team_photo %}
<button class="btn btn-ghost btn-sm gap-2 bg-white/15 text-white hover:bg-white/25" type="button" onclick="document.getElementById('team_photo_delete_modal').showModal()">{% lucide "trash-2" size=14 %} {% trans "Remove" %}</button>
{% endif %}
{% endif %}
</div>
<div class="relative flex items-end justify-between gap-4">
<div class="min-w-0 text-white">
<div class="font-display text-xs font-extrabold tracking-[.14em] text-info uppercase">{% trans "Season" %} {{ selected_season.start_date|date:"Y" }}{{ selected_season.end_date|date:"Y" }}</div>
<div class="font-display text-5xl leading-[.94] font-extrabold uppercase">{{ team.name }}</div>
<div class="mt-2 flex gap-5 text-sm text-on-dark">
<span>{% blocktrans count counter=roster|length %}{{ counter }} player{% plural %}{{ counter }} players{% endblocktrans %}</span>
<span>{% blocktrans count counter=staff|length %}{{ counter }} staff member{% plural %}{{ counter }} staff members{% endblocktrans %}</span>
</div>
</div>
{% if can_manage %}
<a class="btn btn-primary shrink-0 gap-2" href="{% url 'management:team_bulk_add' team.pk selected_season.pk %}">{% lucide "users" size=16 %} {% trans "Add multiple" %}</a>
{% endif %}
</div>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="card p-4">
<div class="mb-3 flex items-center gap-1.5 font-mono text-[10px] tracking-[.08em] text-muted uppercase">{% lucide "user-check" size=13 %} {% trans "Attendance rate" %}</div>
<div class="font-display text-4xl leading-none font-extrabold tabular-nums {% if attendance_rate is None %}text-info{% elif attendance_rate < 30 %}text-club-dark{% elif attendance_rate < 65 %}text-warn{% else %}text-ok{% endif %}">
{% if attendance_rate is None %}
{% trans "N/A" %}
{% else %}
{{ attendance_rate }}%
{% endif %}
</div>
<div class="mt-2">
{% if attendance_rate is None %}
<span class="text-xs text-muted">{% trans "No past events this season" %}</span>
{% else %}
<progress class="progress {% if attendance_rate < 30 %}progress-error{% elif attendance_rate < 65 %}progress-warning{% else %}progress-success{% endif %}" value="{{ attendance_rate }}" max="100"></progress>
{% endif %}
</div>
</div>
<div class="card p-4">
<div class="card-title mb-1">{% lucide "trending-up" size=16 %} {% trans "Top attenders" %}</div>
<ul class="divide-y">
{% for entry in top_attenders %}
<li class="flex items-center justify-between gap-4 py-2">
<span class="text-sm text-ink">{{ entry.member }}</span>
<span class="font-mono text-sm font-semibold text-ink tabular-nums">{{ entry.rate }}%</span>
</li>
{% empty %}
<li class="py-2 text-center text-sm text-muted">{% trans "Not enough data yet." %}</li>
{% endfor %}
</ul>
</div>
<div class="card p-4">
<div class="card-title mb-1">{% lucide "trending-down" size=16 %} {% trans "Needs attention" %}</div>
<ul class="divide-y">
{% for entry in bottom_attenders %}
<li class="flex items-center justify-between gap-4 py-2">
<span class="text-sm text-ink">{{ entry.member }}</span>
<span class="font-mono text-sm font-semibold text-ink tabular-nums">{{ entry.rate }}%</span>
</li>
{% empty %}
<li class="py-2 text-center text-sm text-muted">{% trans "Not enough data yet." %}</li>
{% endfor %}
</ul>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="card p-4">
<div class="card-title mb-1">{% lucide "alert-triangle" size=16 %} {% trans "Missed the last 2 practices" %}</div>
<ul class="divide-y">
{% for member in missed_practices %}
<li class="py-2 text-sm text-ink">{{ member }}</li>
{% empty %}
<li class="py-2 text-center text-sm text-muted">{% trans "No one -- or not enough practice history yet." %}</li>
{% endfor %}
</ul>
</div>
<div class="card p-4">
<div class="card-title mb-1">{% lucide "user-x" size=16 %} {% trans "No-shows" %}</div>
<ul class="divide-y">
{% for entry in no_shows %}
<li class="flex items-center justify-between gap-4 py-2">
<div class="min-w-0">
<span class="text-sm font-semibold text-ink">{{ entry.member }}</span>
<span class="text-sm text-muted">&middot; {{ entry.event.title }}</span>
</div>
<div class="shrink-0 font-mono text-xs whitespace-nowrap text-muted">{{ entry.event.start|date:"j M" }}</div>
</li>
{% empty %}
<li class="py-2 text-center text-sm text-muted">{% trans "None recorded." %}</li>
{% endfor %}
</ul>
</div>
</div>
<div class="card flex flex-col overflow-hidden">
<div class="flex items-center gap-2 border-b border-line px-4 py-3">
<span class="font-display text-sm font-extrabold tracking-[.1em] text-ink uppercase">{% trans "Roster" %}</span>
<span class="flex-1"></span>
{% if can_manage %}
<button class="btn btn-outline btn-sm gap-2" type="button" onclick="document.getElementById('add_player_modal').showModal()">{% lucide "user-plus" size=14 %} {% trans "Add player" %}</button>
{% endif %}
</div>
<table class="table">
<thead>
<tr>
<th>{% trans "No." %}</th>
<th>{% trans "Player" %}</th>
<th>{% trans "Position" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for membership in roster %}
<tr>
<td class="font-display text-lg font-extrabold text-ink tabular-nums">{{ membership.jersey_number|default:"—" }}</td>
<td class="font-semibold text-ink">
{{ membership.member }}
{% if membership.is_captain %}<span class="ml-1 font-display text-xs font-extrabold tracking-[.08em] text-club-dark" title="{% trans 'Captain' %}">C</span>{% endif %}
{% if membership.is_alternate_captain %}<span class="ml-1 font-display text-xs font-extrabold tracking-[.08em] text-club-dark" title="{% trans 'Alternate captain' %}">A</span>{% endif %}
</td>
<td class="text-slate">{{ membership.position|default:"—" }}</td>
<td class="text-right">
{% if can_manage %}
<div class="flex justify-end gap-1">
<button class="btn btn-outline btn-sm" type="button" onclick="document.getElementById('{{ membership.pk|dom_id:"edit_player_modal" }}').showModal()" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</button>
<button class="btn btn-outline btn-error btn-sm" type="button" onclick="document.getElementById('{{ membership.pk|dom_id:"remove_player_modal" }}').showModal()" aria-label="{% trans 'Remove' %}">{% lucide "user-minus" size=14 %} {% trans "Remove" %}</button>
</div>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center text-muted">{% trans "No one on the roster for this season yet." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card flex flex-col">
<div class="flex items-center gap-2 border-b border-line px-4 py-3">
<span class="font-display text-sm font-extrabold tracking-[.1em] text-ink uppercase">{% trans "Staff" %}</span>
<span class="flex-1"></span>
{% if can_manage %}
<button class="btn btn-outline btn-sm gap-2" type="button" onclick="document.getElementById('add_staff_modal').showModal()">{% lucide "user-plus" size=14 %} {% trans "Assign staff" %}</button>
{% endif %}
</div>
<div class="flex flex-col divide-y">
{% for assignment in staff %}
<div class="flex items-center gap-3 px-4 py-2.5">
<div class="avatar avatar-placeholder">
<div class="h-8 w-8 {% if assignment.position.management_position %}bg-club{% endif %}">
<span class="text-xs">{{ assignment.member.first_name|slice:":1" }}{{ assignment.member.last_name|slice:":1" }}</span>
</div>
</div>
<div class="min-w-0 flex-1">
<div class="text-sm font-semibold text-ink">{{ assignment.member }}</div>
<div class="text-xs text-muted">{{ assignment.position }}</div>
</div>
<span class="font-mono text-xs text-dim">{% blocktrans with year=assignment.created|date:"Y" %}since {{ year }}{% endblocktrans %}</span>
{% if can_manage %}
<div class="flex shrink-0 gap-1">
<button class="btn btn-outline btn-xs" type="button" onclick="document.getElementById('{{ assignment.pk|dom_id:"edit_staff_modal" }}').showModal()" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=12 %}</button>
<button class="btn btn-outline btn-error btn-xs" type="button" onclick="document.getElementById('{{ assignment.pk|dom_id:"remove_staff_modal" }}').showModal()" aria-label="{% trans 'Remove' %}">{% lucide "user-minus" size=12 %}</button>
</div>
{% endif %}
</div>
{% empty %}
<div class="px-4 py-4 text-center text-sm text-muted">{% trans "No staff assigned for this season yet." %}</div>
{% endfor %}
</div>
</div>
<div class="card p-4">
<div class="card-title mb-1">{% lucide "flag" size=16 %} {% trans "Eligible referees" %}</div>
{% if eligible_referees is None %}
<p class="text-sm text-muted">{% trans "Referees for this team's home games are managed by the federation, not the club -- nothing to configure here." %}</p>
{% else %}
<p class="text-sm text-muted">{% trans "Members who can be assigned to referee this team's home games. Set from each member's own page." %}</p>
{% if eligible_referees %}
<div class="flex flex-wrap gap-2 mt-2">
{% for referee in eligible_referees %}
<a class="badge badge-outline transition-colors hover:bg-ink hover:text-white" href="{% url 'management:member_detail' referee.pk %}">{{ referee }}</a>
{% endfor %}
</div>
{% else %}
<p class="text-sm text-dim">{% trans "No one yet." %}</p>
{% endif %}
{% endif %}
</div>
{% if can_manage %}
{% trans "Add player" as add_player_label %}
{% url 'management:team_roster_add' team.pk selected_season.pk as add_player_url %}
{% include "controlpanel/_modal_form.html" with modal_id="add_player_modal" title=add_player_label form=roster_form action_url=add_player_url submit_label=add_player_label submit_icon="user-plus" %}
{% trans "Assign staff" as add_staff_label %}
{% url 'management:team_staff_add' team.pk selected_season.pk as add_staff_url %}
{% include "controlpanel/_modal_form.html" with modal_id="add_staff_modal" title=add_staff_label form=staff_form action_url=add_staff_url submit_label=add_staff_label submit_icon="user-plus" %}
{% trans "Edit player" as edit_player_label %}
{% trans "Save" as save_label %}
{% for membership in roster %}
{% url 'management:team_roster_update' team.pk membership.pk as edit_player_url %}
{% include "controlpanel/_modal_form.html" with modal_id=membership.pk|dom_id:"edit_player_modal" title=edit_player_label form=membership.edit_form action_url=edit_player_url submit_label=save_label submit_icon="save" %}
{% blocktrans asvar remove_player_body with member=membership.member %}Remove {{ member }} from the roster?{% endblocktrans %}
{% trans "Remove player" as remove_player_title %}
{% trans "Remove" as remove_label %}
{% url 'management:team_roster_remove' team.pk membership.pk as remove_player_url %}
{% include "controlpanel/_confirm_modal.html" with modal_id=membership.pk|dom_id:"remove_player_modal" title=remove_player_title body=remove_player_body action_url=remove_player_url submit_label=remove_label submit_icon="user-minus" %}
{% endfor %}
{% trans "Edit staff assignment" as edit_staff_label %}
{% for assignment in staff %}
{% url 'management:team_staff_update' team.pk assignment.pk as edit_staff_url %}
{% include "controlpanel/_modal_form.html" with modal_id=assignment.pk|dom_id:"edit_staff_modal" title=edit_staff_label form=assignment.edit_form action_url=edit_staff_url submit_label=save_label submit_icon="save" %}
{% blocktrans asvar remove_staff_body with member=assignment.member %}Remove {{ member }} from staff?{% endblocktrans %}
{% trans "Remove staff" as remove_staff_title %}
{% url 'management:team_staff_remove' team.pk assignment.pk as remove_staff_url %}
{% include "controlpanel/_confirm_modal.html" with modal_id=assignment.pk|dom_id:"remove_staff_modal" title=remove_staff_title body=remove_staff_body action_url=remove_staff_url submit_label=remove_label submit_icon="user-minus" %}
{% endfor %}
{% endif %}
{% endif %}
{% endblock panel %}
{% block extra_body %}
<script src="{% static 'js/searchable-select.js' %}"></script>
{% endblock extra_body %}