Creating (or re-teaming) a home game for a club-managed team now auto- invites every eligible referee (teams.RefereeProfile) via a new RefereeSignup model, notifying them the same way news/events already do. They see it as its own distinct row on the mobile Calendar (own accent colour) and can accept or decline right there -- accepting routes through the existing capacity-checked assign_referee (assigned_by=None marks it self-service), so it lands as a real EventReferee row with no separate sync step. The desktop referee-management screen and event detail page both surface pending invites and flag self-signed-up referees distinctly from admin assignments. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
135 lines
8.9 KiB
HTML
135 lines
8.9 KiB
HTML
{% load i18n lucide ui %}
|
|
{% comment %}
|
|
Shared by the event detail page and the referee management dashboard --
|
|
context: event, referees (EventReferee rows -- fee/km/km_rate read straight
|
|
off the model instance, no separate form object needed), referee_candidates,
|
|
referees_full, pending_signups (RefereeSignup rows still INVITED -- the
|
|
self-service counterpart, events.services.referees.sync_referee_invites/
|
|
accept_referee_signup), can_manage_referees, and an optional next_url to
|
|
return to after a POST (defaults to the event detail page when blank).
|
|
|
|
A referee row with no assigned_by (events.services.referees.
|
|
accept_referee_signup passes assigned_by=None) is one who signed
|
|
themselves up rather than one an admin picked -- flagged "Self sign-up"
|
|
instead of "Assigned by ..." so that stays visible here without a
|
|
separate screen.
|
|
|
|
The fee is entered right here, inline, rather than behind a "Fee" button
|
|
that opened a second (and on the dashboard, nested-inside-a-dialog) modal
|
|
-- that extra click-through was the easiest way for a fee to slip past
|
|
everyone's attention until PDF export time. Travel (km/rate) stays behind
|
|
a small disclosure since most assignments never need it, but posts in the
|
|
same form as the fee so there's still only one Save per referee.
|
|
{% endcomment %}
|
|
<div class="flex flex-wrap items-center justify-between gap-2">
|
|
<h2 class="card-title">
|
|
{% lucide "flag" size=18 %} {% trans "Referees" %}
|
|
<span class="badge badge-sm {% if referees_full %}badge-neutral{% else %}badge-outline{% endif %}">
|
|
{{ referees|length }} / {{ event.max_referees }}
|
|
</span>
|
|
</h2>
|
|
{% if can_manage_referees %}
|
|
<a class="btn btn-outline btn-sm gap-1" href="{% url 'management:event_referee_form_pdf' event.pk %}">{% lucide "file-down" size=12 %} {% trans "Referee form (PDF)" %}</a>
|
|
{% endif %}
|
|
</div>
|
|
<ul class="divide-y">
|
|
{% for referee in referees %}
|
|
<li class="flex flex-wrap items-center justify-between gap-3 py-2.5">
|
|
<div class="flex min-w-0 flex-col gap-0.5">
|
|
<div class="flex flex-wrap items-center gap-1.5">
|
|
<span class="text-sm font-semibold text-ink">{{ referee.display_name }}</span>
|
|
{% if referee.is_external %}<span class="badge badge-neutral badge-xs">{% trans "External" %}</span>{% endif %}
|
|
{% if can_manage_referees and not referee.fee %}<span class="badge badge-warning badge-xs">{% trans "Fee not set" %}</span>{% endif %}
|
|
</div>
|
|
{% if referee.assigned_by %}
|
|
<span class="text-xs text-muted">{% blocktrans with name=referee.assigned_by %}Assigned by {{ name }}{% endblocktrans %}</span>
|
|
{% elif not referee.is_external %}
|
|
<span class="badge badge-outline badge-xs w-fit">{% trans "Self sign-up" %}</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if can_manage_referees %}
|
|
<div class="flex shrink-0 items-center gap-1.5">
|
|
{% url 'management:event_referee_fee_update' event.pk referee.pk as fee_action_url %}
|
|
<form method="post" action="{{ fee_action_url }}?next={{ next_url|urlencode }}" class="flex items-center gap-1.5">
|
|
{% csrf_token %}
|
|
<label class="flex items-center gap-1">
|
|
<span class="font-mono text-xs text-dim">€</span>
|
|
<input type="number" step="0.01" min="0" name="fee" value="{{ referee.fee }}" class="input w-20 text-right" aria-label="{% trans 'Fee' %}">
|
|
</label>
|
|
<details class="relative">
|
|
<summary class="btn btn-square btn-outline btn-xs cursor-pointer list-none" title="{% trans 'Travel reimbursement' %}">{% lucide "car" size=12 %}</summary>
|
|
<div class="absolute right-0 z-10 mt-1 flex w-44 flex-col gap-2 rounded-lg border border-line bg-white p-2.5 shadow-lg">
|
|
<label class="flex flex-col gap-0.5 text-xs text-muted">{% trans "Km" %}
|
|
<input type="number" step="0.1" min="0" name="km" value="{{ referee.km|default_if_none:'' }}" class="input">
|
|
</label>
|
|
<label class="flex flex-col gap-0.5 text-xs text-muted">{% trans "Rate/km" %}
|
|
<input type="number" step="any" min="0" name="km_rate" value="{{ referee.km_rate|default_if_none:'' }}" class="input">
|
|
</label>
|
|
</div>
|
|
</details>
|
|
<button class="btn btn-square btn-outline btn-xs" type="submit" title="{% trans 'Save fee' %}" aria-label="{% trans 'Save fee' %}">{% lucide "check" size=12 %}</button>
|
|
</form>
|
|
{% if referee.km %}
|
|
<span class="font-mono text-xs text-muted">{% blocktrans with total=referee.total_payable|floatformat:2 %}{{ total }} due{% endblocktrans %}</span>
|
|
{% endif %}
|
|
<form method="post" action="{% url 'management:event_referee_remove' event.pk referee.pk %}">
|
|
{% csrf_token %}
|
|
{% if next_url %}<input type="hidden" name="next" value="{{ next_url }}">{% endif %}
|
|
<button class="btn btn-square btn-outline btn-error btn-xs" type="submit" title="{% trans 'Remove' %}" aria-label="{% trans 'Remove' %}">{% lucide "x" size=12 %}</button>
|
|
</form>
|
|
</div>
|
|
{% elif referee.total_payable %}
|
|
<span class="shrink-0 font-mono text-xs text-muted">{% blocktrans with total=referee.total_payable|floatformat:2 %}€{{ total }} due{% endblocktrans %}</span>
|
|
{% endif %}
|
|
</li>
|
|
{% empty %}
|
|
<li class="py-2.5 text-sm text-muted">{% trans "No referees assigned yet." %}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
|
|
{% if pending_signups %}
|
|
<p class="mt-2 text-xs text-muted">
|
|
{% blocktrans count counter=pending_signups|length %}Invited, no response yet: {% plural %}Invited, no response yet: {% endblocktrans %}
|
|
{% for signup in pending_signups %}{{ signup.member }}{% if not forloop.last %}, {% endif %}{% endfor %}
|
|
</p>
|
|
{% endif %}
|
|
|
|
{% if can_manage_referees and not referees_full %}
|
|
<div class="mt-3 flex flex-col gap-2 border-t border-rule pt-3">
|
|
<span class="font-mono text-[11px] tracking-[.08em] text-dim uppercase">{% trans "Add a referee" %}</span>
|
|
{% if referee_candidates %}
|
|
<form method="post" action="{% url 'management:event_referee_assign' event.pk %}" class="flex flex-wrap items-center gap-2">
|
|
{% csrf_token %}
|
|
{% if next_url %}<input type="hidden" name="next" value="{{ next_url }}">{% endif %}
|
|
<select name="member" class="select w-auto">
|
|
{% for candidate in referee_candidates %}
|
|
<option value="{{ candidate.pk }}" {% if candidate.has_conflict %}title="{% blocktrans with events=candidate.conflict_titles %}Also expected at: {{ events }}{% endblocktrans %}"{% endif %}>
|
|
{{ candidate }}{% if candidate.has_conflict %} ⚠{% endif %}
|
|
</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 "user-plus" size=14 %} {% trans "Assign" %}</button>
|
|
</form>
|
|
{% else %}
|
|
<p class="text-sm text-muted">
|
|
{% trans "No eligible referees for this team yet." %}
|
|
<a class="link link-hover" href="{% url 'management:referee_level_list' %}">{% trans "Link a referee level to this team." %}</a>
|
|
</p>
|
|
{% endif %}
|
|
|
|
<form method="post" action="{% url 'management:event_referee_add_external' event.pk %}" class="flex flex-wrap items-center gap-2">
|
|
{% csrf_token %}
|
|
{% if next_url %}<input type="hidden" name="next" value="{{ next_url }}">{% endif %}
|
|
<input type="text" name="name" class="input w-56" placeholder="{% trans 'External referee name' %}">
|
|
{# No btn-sm: .input 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 "user-plus" size=14 %} {% trans "Add external" %}</button>
|
|
</form>
|
|
|
|
<p class="text-xs text-muted">{% trans "⚠ = also expected at another event around this time -- shown as a warning, not blocked." %}</p>
|
|
</div>
|
|
{% elif can_manage_referees and referees_full %}
|
|
<p class="mt-2 text-sm text-muted">{% trans "This game already has its maximum number of referees." %}</p>
|
|
{% endif %}
|