Let a parent sign up a referee-eligible managed child, and hide scrollbars

Referee sign-up (Calendar row and event detail's own card) was scoped to
self.me only; a referee-eligible child is exactly as real as a referee-
eligible parent, and a parent signing one up is no different from
answering an RSVP on their behalf -- both surfaces and the respond view now
cover every managed person, and the calendar row names whose invite it is
once there's more than one managed person to tell apart. Event detail's
single-card layout became a per-person list, same shape as "Your answers".

Also hide the scrollbar on both mobile shells (the member shell's own
document scroll, and the coach shell's still-inner-scrolling .coach-sheet).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
2026-08-22 18:15:38 +02:00
parent e349fbbf03
commit 5db885c809
6 changed files with 139 additions and 78 deletions

View File

@@ -1,20 +1,22 @@
{% load i18n lucide %}
{% comment %}
One referee sign-up row on M3's Calendar -- a home game the signed-in
account is eligible (and, once responded, confirmed) to referee for.
Merged into the same chronological list as the account's own RSVP rows
(mobile/_calendar_row.html), but in the referee accent (assets/mobile.
css's --color-referee/.pill-referee) so it reads as a different kind of
commitment at a glance -- and scoped to self.me only (mobile/views.py's
CalendarView), never managed_people, since a referee is an adult acting
on their own behalf, not something a parent does for a child.
account (or a managed person -- a referee-eligible child is exactly as
real as a referee-eligible parent, mobile/views.py's CalendarView) is
eligible (and, once responded, confirmed) to referee for. Merged into
the same chronological list as the account's own RSVP rows (mobile/
_calendar_row.html), but in the referee accent (assets/mobile.css's
--color-referee/.pill-referee) so it reads as a different kind of
commitment at a glance.
Expects ``row`` ({event, referee_signup}) in scope. Accept/Decline are
two small forms, not a single multi-button one -- and, unlike this
app's RSVP forms, boosted (no hx-boost="false"): nothing here is
Alpine-owned/toggled, so there's no htmx/Alpine conflict to dodge, and a
boosted POST avoids the jarring full-page reload a plain form submit
would cause on a page the user is mid-scroll on.
Expects ``row`` ({event, referee_signup, referee_member}) in scope --
referee_member is only set once there's more than one managed person to
tell apart, same rule mobile/_calendar_row.html's own ``member`` uses.
Accept/Decline are two small forms, not a single multi-button one --
and, unlike this app's RSVP forms, boosted (no hx-boost="false"):
nothing here is Alpine-owned/toggled, so there's no htmx/Alpine conflict
to dodge, and a boosted POST avoids the jarring full-page reload a plain
form submit would cause on a page the user is mid-scroll on.
{% endcomment %}
<div class="flex items-center gap-3 bg-white px-4 py-3">
<div class="w-9.5 shrink-0 text-center">
@@ -29,6 +31,7 @@
</div>
<div class="truncate text-xs text-muted">
{{ row.event.start|date:"H:i" }}
{% if row.referee_member %}&middot; {{ row.referee_member.first_name }}{% endif %}
{% for team in row.event.teams.all %}&middot; {{ team.name }}{% endfor %}
{% if row.event.location %}&middot; {{ row.event.location.name }}{% endif %}
</div>

View File

@@ -70,7 +70,7 @@
{% block extra_head %}{% endblock extra_head %}
</head>
<body class="flex min-h-screen flex-col bg-paper font-sans text-slate" hx-boost="true" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' data-vapid-public-key="{{ vapid_public_key }}" data-csrftoken="{{ csrf_token }}">
<body class="scrollbar-hide flex min-h-screen flex-col bg-paper font-sans text-slate" hx-boost="true" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' data-vapid-public-key="{{ vapid_public_key }}" data-csrftoken="{{ csrf_token }}">
<header class="app-header sticky top-0 z-20">
<div class="flex min-h-11 items-center gap-2.5">
<a class="flex items-center gap-2.5" href="{% url "mobile:home" %}">

View File

@@ -98,7 +98,7 @@
{% block header_extra %}{% endblock header_extra %}
</header>
<main class="coach-sheet">
<main class="coach-sheet scrollbar-hide">
<div class="flex flex-col gap-4 px-4 py-5">
{% if messages %}
<div class="flex flex-col gap-2">

View File

@@ -58,31 +58,36 @@
{% endif %}
</div>
{% if referee_signup %}
{% if referee_signups %}
<div class="m-card p-4">
<div class="flex items-center justify-between">
<span class="font-display text-xs font-extrabold tracking-wide text-muted uppercase">{% trans "Refereeing" %}</span>
{% if referee_signup.status == "accepted" %}<span class="pill pill-referee">{% trans "Confirmed" %}</span>{% endif %}
<span class="font-display text-xs font-extrabold tracking-wide text-muted uppercase">{% trans "Refereeing" %}</span>
<div class="mt-3 flex flex-col gap-3">
{% for signup in referee_signups %}
{% if not forloop.first %}<div class="h-px bg-rule"></div>{% endif %}
<div>
<div class="mb-2 flex items-center justify-between gap-2.5">
<span class="text-[15px] font-semibold text-ink">{{ signup.member.get_full_name }}</span>
{% if signup.status == "accepted" %}<span class="pill pill-referee shrink-0">{% trans "Confirmed" %}</span>{% endif %}
</div>
{% if signup.status != "accepted" %}
<div class="grid grid-cols-2 gap-2">
<form method="post" action="{% url "mobile:referee_signup_respond" signup.pk %}">
{% csrf_token %}
<input type="hidden" name="response" value="accept">
<input type="hidden" name="next" value="event_detail">
<button type="submit" class="btn w-full bg-referee text-white">{% trans "I'll ref" %}</button>
</form>
<form method="post" action="{% url "mobile:referee_signup_respond" signup.pk %}">
{% csrf_token %}
<input type="hidden" name="response" value="decline">
<input type="hidden" name="next" value="event_detail">
<button type="submit" class="btn btn-secondary w-full">{% trans "Can't" %}</button>
</form>
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% if referee_signup.status == "accepted" %}
<p class="mt-2 text-sm text-muted">{% trans "You're confirmed to referee this game." %}</p>
{% else %}
<p class="mt-2 text-sm text-ink">{% trans "You're eligible to referee this game -- are you in?" %}</p>
<div class="mt-3 grid grid-cols-2 gap-2">
<form method="post" action="{% url "mobile:referee_signup_respond" referee_signup.pk %}">
{% csrf_token %}
<input type="hidden" name="response" value="accept">
<input type="hidden" name="next" value="event_detail">
<button type="submit" class="btn w-full bg-referee text-white">{% trans "I'll ref" %}</button>
</form>
<form method="post" action="{% url "mobile:referee_signup_respond" referee_signup.pk %}">
{% csrf_token %}
<input type="hidden" name="response" value="decline">
<input type="hidden" name="next" value="event_detail">
<button type="submit" class="btn btn-secondary w-full">{% trans "Can't" %}</button>
</form>
</div>
{% endif %}
</div>
{% endif %}
@@ -226,7 +231,7 @@
</div>
{% endif %}
{% if not your_answers and not squad_summary and not lineup and not referee_signup %}
{% if not your_answers and not squad_summary and not lineup and not referee_signups %}
<div class="m-card p-6 text-center">
<p class="text-sm text-muted">{% trans "No one you manage is invited to this event." %}</p>
</div>