Files
RosterChief/management/templates/management/event_detail.html
Bernard Siebens 309bd4d83e Add referee assignment/eligibility system, team bulk-add, member Groups, and referee management dashboard with PDF export
Builds the referee workflow end to end: club-defined RefereeLevel/RefereeProfile
eligibility tied to teams, EventReferee assignment (member or external, with
fee/km payment tracking), an admin dashboard with KPI tiles, date-grouped game
tiles and range filters, and a downloadable payment form PDF modeled on the
club's existing paper document (using Club.legal_name when set). Also lands
team roster bulk-add, member mass-upload with family linking, and the
members.Group model, developed alongside this work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 21:39:19 +02:00

231 lines
12 KiB
HTML

{% extends "management/base.html" %}
{% load i18n lucide %}
{% block heading %}{{ event.title }}{% endblock heading %}
{% block subheading %}{{ event.get_kind_display }}{% endblock subheading %}
{% block actions %}
{% if can_manage %}
<a class="btn btn-outline gap-2" href="{% url 'management:event_update' event.pk %}">{% lucide "pencil" size=16 %} {% trans "Edit" %}</a>
{% if event.series_id and not event.detached %}
<form method="post" action="{% url 'management:event_detach' event.pk %}">
{% csrf_token %}
<button class="btn btn-outline gap-2" type="submit">{% lucide "unlink" size=16 %} {% trans "Detach from series" %}</button>
</form>
{% endif %}
<button class="btn btn-outline btn-error gap-2" type="button" onclick="document.getElementById('event_delete_modal').showModal()">
{% lucide "trash-2" size=16 %}
{% if event.series_id %}{% trans "Cancel occurrence" %}{% else %}{% trans "Delete" %}{% endif %}
</button>
{% endif %}
{% endblock actions %}
{% block panel %}
{% if event.series_id %}
<div class="alert alert-info mb-6">
{% lucide "repeat" size=20 %}
<span>
{% blocktrans with series=event.series %}Part of the recurring series “{{ series }}”.{% endblocktrans %}
<a class="link" href="{% url 'management:event_series_detail' event.series_id %}">{% trans "View series" %}</a>
{% if event.detached %}<span class="badge badge-sm badge-neutral ml-2">{% trans "Detached" %}</span>{% endif %}
</span>
</div>
{% endif %}
<div class="mb-6 grid gap-4 lg:grid-cols-2">
<div class="card bg-base-100 shadow">
<div class="card-body">
<h2 class="card-title text-base">{% lucide "info" size=18 %} {% trans "Details" %}</h2>
<dl class="divide-y divide-base-200">
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Start" %}</dt>
<dd>{{ event.start|date:"j M Y H:i" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "End" %}</dt>
<dd>{{ event.end|date:"j M Y H:i"|default:"—" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Gathering" %}</dt>
<dd>{{ event.gathering|date:"j M Y H:i"|default:"—" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Registration deadline" %}</dt>
<dd>{{ event.deadline|date:"j M Y H:i"|default:"—" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Location" %}</dt>
<dd>{{ event.location|default:"—" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Opponent" %}</dt>
<dd>{{ event.opponent|default:"—" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Teams" %}</dt>
<dd>{% for team in event.teams.all %}{{ team.name }}{% if not forloop.last %}, {% endif %}{% empty %}—{% endfor %}</dd>
</div>
</dl>
</div>
</div>
<div class="card bg-base-100 shadow">
<div class="card-body">
<div class="flex items-center justify-between">
<h2 class="card-title text-base">{% lucide "user-check" size=18 %} {% trans "RSVPs" %}</h2>
{% if has_attendance_rows %}
<button class="btn btn-outline btn-sm gap-2" type="button" onclick="document.getElementById('rsvp_modal').showModal()">
{% lucide "list" size=14 %} {% trans "View responses" %}
</button>
{% endif %}
</div>
<dl class="divide-y divide-base-200">
{% for group in attendance_groups %}
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{{ group.label }}</dt>
<dd class="font-semibold tabular-nums font-mono">{{ group.rows|length }}</dd>
</div>
{% endfor %}
</dl>
{% if not has_attendance_rows %}
<p class="text-sm opacity-60">{% trans "No one invited yet." %}</p>
{% endif %}
</div>
</div>
</div>
{% if event.kind == "game" %}
<div class="mb-6 card bg-base-100 shadow">
<div class="card-body">
<div class="flex items-center justify-between">
<h2 class="card-title text-base">
{% lucide "trophy" size=18 %} {% trans "Game" %}
{% if event.is_live %}<span class="badge badge-error gap-1 animate-pulse">{% lucide "circle" size=10 %} {% trans "Live" %}</span>{% endif %}
</h2>
{% if can_manage and event.competition %}
<form method="post" action="{% url 'management:event_fetch_game_info' event.pk %}">
{% csrf_token %}
<button class="btn btn-outline btn-sm gap-2" type="submit">{% lucide "refresh-cw" size=14 %} {% trans "Fetch new game info" %}</button>
</form>
{% endif %}
</div>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<div class="text-sm opacity-70">{% trans "Score" %}</div>
<div class="text-sm font-semibold tabular-nums font-mono">
{% if event.score_for is not None and event.score_against is not None %}{{ event.score_for }} - {{ event.score_against }}{% else %}—{% endif %}
</div>
</div>
<div>
<div class="text-sm opacity-70">{% trans "Competition" %}</div>
<div class="text-sm">{{ event.competition|default:"—" }}</div>
</div>
<div>
<div class="text-sm opacity-70">{% trans "External game ID" %}</div>
<div class="font-mono text-sm">{{ event.external_game_id|default:"—" }}</div>
</div>
</div>
</div>
</div>
{% endif %}
{% if event.is_home_game and not referee_management_needed %}
<div class="alert alert-info mb-6">
{% lucide "info" size=20 %}
<span>{% trans "Referees for this game are managed by the federation, not the club." %}</span>
</div>
{% endif %}
{% if referee_management_needed %}
<div class="mb-6 card bg-base-100 shadow">
<div class="card-body">
{% include "management/_referee_assignment_panel.html" %}
</div>
</div>
{% endif %}
{% endblock panel %}
{% block extra_body %}
{% if can_manage %}
<dialog id="event_delete_modal" class="modal">
<div class="modal-box">
<h3 class="text-lg font-bold">
{% if event.series_id %}{% trans "Cancel occurrence" %}{% else %}{% trans "Delete event" %}{% endif %}
</h3>
<form method="post" action="{% url 'management:event_delete' event.pk %}" id="event_delete_form">
{% csrf_token %}
{% if event.series_id %}
<p class="py-2 text-sm opacity-70">{% trans "Removes this occurrence from the schedule. It won't be regenerated." %}</p>
<label class="label cursor-pointer justify-start gap-2">
<input type="checkbox" name="keep_record" class="checkbox">
<span class="label-text">{% trans "Keep a record of it (marks it cancelled instead of deleting it)" %}</span>
</label>
{% else %}
<p class="py-2 text-sm opacity-70">{% trans "This cannot be undone." %}</p>
{% endif %}
</form>
<div class="modal-action">
<form method="dialog">
<button class="btn btn-outline gap-2">{% lucide "x" size=16 %} {% trans "Cancel" %}</button>
</form>
<button class="btn btn-error gap-2" type="submit" form="event_delete_form">{% lucide "trash-2" size=16 %} {% trans "Confirm" %}</button>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
{% endif %}
{% if has_attendance_rows %}
<dialog id="rsvp_modal" class="modal">
<div class="modal-box max-w-2xl">
<h3 class="text-lg font-bold">{% trans "Who responded" %}</h3>
<div class="mt-2 space-y-2">
{% for group in attendance_groups %}
{% if group.rows %}
<details class="collapse collapse-arrow border border-base-300 bg-base-100">
<summary class="collapse-title text-sm font-medium">
<span class="badge badge-sm
{% if group.value == "present" %}badge-success
{% elif group.value == "absent" %}badge-error
{% elif group.value == "excused" %}badge-warning
{% elif group.value == "selected" %}badge-info
{% elif group.value == "maybe" %}badge-warning badge-outline
{% elif group.value == "not_selected" %}badge-neutral
{% else %}badge-outline{% endif %}">
{{ group.label }}
</span>
<span class="opacity-60">({{ group.rows|length }})</span>
</summary>
<div class="collapse-content">
<div class="overflow-x-auto">
<table class="table table-sm">
<tbody>
{% for row in group.rows %}
<tr>
<td>{{ row.member }}</td>
<td class="text-sm opacity-70">{{ row.note|default:"—" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</details>
{% endif %}
{% endfor %}
</div>
<div class="modal-action">
<form method="dialog">
<button class="btn btn-outline gap-2">{% lucide "x" size=16 %} {% trans "Close" %}</button>
</form>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
{% endif %}
{% endblock extra_body %}