Show blocked sign-ups on mobile instead of silently hiding the event
A member excluded from an event's Attendance sync by an open onboarding requirement (events.services.attendance.effective_members) previously just never saw that event anywhere -- no row, no explanation. Two new read-side functions mirror that exclusion instead of hiding it: club.services. onboarding.open_requirements_blocking (per-member, "why") and events. services.attendance.blocked_upcoming_events_for_member (which of their upcoming events are affected). The Calendar now shows those events as a distinct muted "Blocked" row naming the outstanding requirement, and the event detail page shows a "Can't sign up yet" card for the same reason -- no RSVP buttons, no lineup/ referee actions, just the explanation. Write-side blocking (who actually gets an Attendance row, who a coach can select) is untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
40
mobile/templates/mobile/_calendar_blocked_row.html
Normal file
40
mobile/templates/mobile/_calendar_blocked_row.html
Normal file
@@ -0,0 +1,40 @@
|
||||
{% load i18n lucide %}
|
||||
{% comment %}
|
||||
One "can't sign up yet" row on M3's Calendar -- a game/practice a managed
|
||||
person would normally be invited to (via their team/group), but isn't,
|
||||
because an open onboarding requirement blocks that event's kind (club.
|
||||
services.onboarding.open_requirements_blocking). Without this the event
|
||||
would just silently never appear anywhere (events.services.attendance.
|
||||
effective_members already excludes it from Attendance sync) -- shown
|
||||
instead, muted, with which requirement is in the way, same "explain, don't
|
||||
just hide" reasoning as the referee row's own distinct treatment.
|
||||
|
||||
Expects ``row`` ({event, blocked_requirements, blocked_member}) in scope --
|
||||
blocked_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.
|
||||
No action here at all, not even a link styled as one -- there's nothing
|
||||
to tap into doing, just an explanation, so it links straight to the event
|
||||
like every other row rather than growing its own inert button.
|
||||
{% endcomment %}
|
||||
<a class="flex items-center gap-3 bg-white px-4 py-3 opacity-70" href="{% url "mobile:event_detail" row.event.pk %}">
|
||||
<div class="w-9.5 shrink-0 text-center">
|
||||
<div class="font-mono text-[10px] tracking-wide text-muted uppercase">{{ row.event.start|date:"D" }}</div>
|
||||
<div class="font-display text-2xl leading-none font-extrabold text-ink">{{ row.event.start|date:"d" }}</div>
|
||||
</div>
|
||||
<div class="w-[3px] shrink-0 self-stretch rounded-full bg-line"></div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-1 text-sm font-semibold text-ink">
|
||||
{% lucide "lock" size=13 stroke_width=2.4 class="shrink-0 text-dim" %}
|
||||
<span class="truncate">{{ row.event.title }}</span>
|
||||
</div>
|
||||
<div class="truncate text-xs text-muted">
|
||||
{{ row.event.start|date:"H:i" }}
|
||||
{% if row.blocked_member %}· {{ row.blocked_member.first_name }}{% endif %}
|
||||
{% for team in row.event.teams.all %}· {{ team.name }}{% endfor %}
|
||||
</div>
|
||||
<div class="truncate text-xs text-club-dark">
|
||||
{% blocktrans with name=row.blocked_requirements.0.name %}Complete “{{ name }}” to sign up{% endblocktrans %}
|
||||
</div>
|
||||
</div>
|
||||
<span class="pill pill-neutral shrink-0">{% trans "Blocked" %}</span>
|
||||
</a>
|
||||
@@ -8,14 +8,20 @@
|
||||
docstring -- later_months is a list of {month_start, rows}). No person
|
||||
switcher and no club-wide toggle here -- always every event
|
||||
self.managed_people is invited to, plus (merged into the same
|
||||
chronological list, own accent colour) any home game self.me is
|
||||
eligible to referee -- see mobile/_calendar_referee_row.html. A real,
|
||||
working ?kind=
|
||||
filter (All/Games/Practices) stands in for the design mock's "Games only"
|
||||
pill; its List/Month toggle isn't reproduced -- that's a second view
|
||||
mode, not a filter. -mx-4 (on the row groups only, not this filter row)
|
||||
breaks the rows out of base.html's shared page padding so they run
|
||||
edge-to-edge, matching the design canvas's own M3 markup.
|
||||
chronological list, each its own accent colour/look):
|
||||
- any home game a managed person is eligible to referee -- see
|
||||
mobile/_calendar_referee_row.html;
|
||||
- any event a managed person would normally be invited to but can't sign
|
||||
up for yet, because an open onboarding requirement blocks it -- see
|
||||
mobile/_calendar_blocked_row.html. Without this row it would just
|
||||
silently never appear (events.services.attendance.effective_members
|
||||
already excludes it), with no explanation at all.
|
||||
A real, working ?kind= filter (All/Games/Practices) stands in for the
|
||||
design mock's "Games only" pill; its List/Month toggle isn't reproduced --
|
||||
that's a second view mode, not a filter. -mx-4 (on the row groups only,
|
||||
not this filter row) breaks the rows out of base.html's shared page
|
||||
padding so they run edge-to-edge, matching the design canvas's own M3
|
||||
markup.
|
||||
{% endcomment %}
|
||||
|
||||
{% block header_extra %}
|
||||
@@ -63,7 +69,7 @@
|
||||
<div class="bg-paper px-4 py-2 font-display text-xs font-extrabold tracking-wide text-muted uppercase">{% trans "This week" %}</div>
|
||||
<div class="flex flex-col gap-px bg-line">
|
||||
{% for row in this_week %}
|
||||
{% if row.referee_signup %}{% include "mobile/_calendar_referee_row.html" %}{% else %}{% include "mobile/_calendar_row.html" %}{% endif %}
|
||||
{% if row.blocked_requirements %}{% include "mobile/_calendar_blocked_row.html" %}{% elif row.referee_signup %}{% include "mobile/_calendar_referee_row.html" %}{% else %}{% include "mobile/_calendar_row.html" %}{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,7 +80,7 @@
|
||||
<div class="bg-paper px-4 py-2 font-display text-xs font-extrabold tracking-wide text-muted uppercase">{% trans "Next week" %}</div>
|
||||
<div class="flex flex-col gap-px bg-line">
|
||||
{% for row in next_week %}
|
||||
{% if row.referee_signup %}{% include "mobile/_calendar_referee_row.html" %}{% else %}{% include "mobile/_calendar_row.html" %}{% endif %}
|
||||
{% if row.blocked_requirements %}{% include "mobile/_calendar_blocked_row.html" %}{% elif row.referee_signup %}{% include "mobile/_calendar_referee_row.html" %}{% else %}{% include "mobile/_calendar_row.html" %}{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,7 +91,7 @@
|
||||
<div class="bg-paper px-4 py-2 font-display text-xs font-extrabold tracking-wide text-muted uppercase">{{ month.month_start|date:"F Y" }}</div>
|
||||
<div class="flex flex-col gap-px bg-line">
|
||||
{% for row in month.rows %}
|
||||
{% if row.referee_signup %}{% include "mobile/_calendar_referee_row.html" %}{% else %}{% include "mobile/_calendar_row.html" %}{% endif %}
|
||||
{% if row.blocked_requirements %}{% include "mobile/_calendar_blocked_row.html" %}{% elif row.referee_signup %}{% include "mobile/_calendar_referee_row.html" %}{% else %}{% include "mobile/_calendar_row.html" %}{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -212,6 +212,28 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if blocked_signups %}
|
||||
<div class="m-card p-4">
|
||||
<span class="font-display text-xs font-extrabold tracking-wide text-muted uppercase">{% trans "Can't sign up yet" %}</span>
|
||||
<div class="mt-3 flex flex-col gap-3">
|
||||
{% for row in blocked_signups %}
|
||||
{% if not forloop.first %}<div class="h-px bg-rule"></div>{% endif %}
|
||||
<div class="flex items-center gap-2.5">
|
||||
{% include "mobile/_avatar.html" with person=row.member size_class="h-8 w-8" %}
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-[15px] font-semibold text-ink">{{ row.member.get_full_name }}</div>
|
||||
<div class="text-xs text-muted">
|
||||
{% for requirement in row.requirements %}
|
||||
{% blocktrans with name=requirement.name %}Complete “{{ name }}”{% endblocktrans %}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if squad_summary %}
|
||||
<div class="m-card p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -231,7 +253,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not your_answers and not squad_summary and not lineup and not referee_signups %}
|
||||
{% if not your_answers and not squad_summary and not lineup and not referee_signups and not blocked_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>
|
||||
|
||||
Reference in New Issue
Block a user