The In/Out hero buttons (and Out's reason confirm) stopped working: Alpine owns the toggle between a row's two sibling forms (buttons vs. the reason prompt), and hx-boost="true" on <body> had htmx *also* intercepting the same submit -- both ended up fighting over it. Fixed by marking every write-action <form> across the mobile app hx-boost="false" (link navigation, where the smooth-navigation feature actually matters, is untouched). The one exception worth calling out: coach/lineup.html's form uses three submit buttons sharing one <form> via formaction overrides -- htmx's boost reads the form's own action rather than the submitter's formaction override, so a boosted click there would always have posted to the wrong endpoint regardless of the Alpine conflict. Also: - A reason for Out is now mandatory, not just captured -- empty and punctuation-only "answers" (a bare ".", "-", "??") are rejected server-side (the authoritative check) with textarea required/minlength as a client-side nudge on top. - Maybe can now carry an optional reason too, visible to the same audience as Out's (this member/family, and Coach mode's bench attendance) -- one shared reason form in event_detail.html's per-person row, its hidden status input following whichever of Maybe/Out was tapped. - The 3-way In/Maybe/Out row (and the 2-way hero In/Out) now use min-w-0 on every button so flex-1 actually splits the row evenly -- a longer label's own intrinsic width was winning it a bigger share otherwise. - HomeView's "Needs your answer" list now excludes events whose registration deadline has already passed -- replying is no longer possible there (same rule EventDetailView.post already enforces), so it doesn't belong in a "still needs a reply" list. hero_attendance is unaffected -- it always shows the true next event, falling back to a read-only pill once its own deadline closes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
77 lines
5.0 KiB
HTML
77 lines
5.0 KiB
HTML
{% extends "mobile/coach/base.html" %}
|
|
{% load i18n lucide %}
|
|
|
|
{% comment %}
|
|
C2 -- design_handoff_rosterchief_platform/README.md's C2 section: check
|
|
off who actually showed up, a separate axis from their RSVP (see
|
|
CoachAttendanceView's own docstring). The mock's "fixed white footer"
|
|
Save button is a plain in-flow button instead -- every other coach/member
|
|
sub-page in this codebase keeps its action inline rather than adding a
|
|
second fixed bar above the shell's own tab bar.
|
|
{% endcomment %}
|
|
|
|
{% block header_extra %}
|
|
<div class="mt-3">
|
|
<div class="flex items-center justify-between">
|
|
<span class="font-display text-xl leading-none font-extrabold text-white uppercase">{% trans "Attendance" %}</span>
|
|
<span class="font-mono text-sm text-on-dark">{{ checked_in_count }}/{{ total_count }}</span>
|
|
</div>
|
|
<div class="mt-1 text-xs text-on-dark-dim">{{ event.title }} · {{ event.start|date:"D d M H:i" }}</div>
|
|
<div class="mt-2 h-1.5 overflow-hidden rounded-full bg-steel">
|
|
<div class="h-full bg-ice" style="width: {% widthratio checked_in_count total_count|default:1 100 %}%"></div>
|
|
</div>
|
|
</div>
|
|
{% endblock header_extra %}
|
|
|
|
{% block content %}
|
|
<div class="flex gap-2">
|
|
<a class="flex h-9 flex-1 items-center justify-center rounded-full font-display text-xs font-extrabold tracking-wide uppercase {% if not filter_param %}bg-ink text-white{% else %}border border-line bg-white text-muted{% endif %}" href="?">
|
|
{% trans "All" %} {{ total_count }}
|
|
</a>
|
|
<a class="flex h-9 flex-1 items-center justify-center rounded-full font-display text-xs font-extrabold tracking-wide uppercase {% if filter_param == "silent" %}bg-ink text-white{% else %}border border-line bg-white text-muted{% endif %}" href="?filter=silent">
|
|
{% trans "Silent" %} {{ silent_count }}
|
|
</a>
|
|
<a class="flex h-9 flex-1 items-center justify-center rounded-full font-display text-xs font-extrabold tracking-wide uppercase {% if filter_param == "goalies" %}bg-ink text-white{% else %}border border-line bg-white text-muted{% endif %}" href="?filter=goalies">
|
|
{% trans "Goalies" %}
|
|
</a>
|
|
</div>
|
|
|
|
<form method="post" action="{% url "mobile:coach_attendance" event.pk %}" hx-boost="false">
|
|
{% csrf_token %}
|
|
<div class="m-card flex flex-col overflow-hidden">
|
|
{% for row in rows %}
|
|
<div class="flex items-center gap-3 px-4 py-2.5 {% if not forloop.last %}border-b border-rule{% endif %} {% if row.is_silent %}bg-warn-bg{% endif %}" {% if can_manage_active_team %}x-data="{ state: '{{ row.showed_up|yesno:'true,false,' }}' }"{% endif %}>
|
|
<div class="w-8 shrink-0 text-center font-display text-lg font-extrabold text-ink tabular-nums">{{ row.membership.jersey_number|default:"—" }}</div>
|
|
<div class="min-w-0 flex-1">
|
|
<div class="text-sm font-semibold text-ink">{{ row.member.get_full_name }}</div>
|
|
<div class="text-xs text-muted">{{ row.membership.position|default:"—" }}</div>
|
|
{% if row.note %}
|
|
{# Only ever set alongside absent/maybe -- EventDetailView.post clears it for present. #}
|
|
<div class="mt-0.5 truncate text-xs text-club-dark italic">“{{ row.note }}”</div>
|
|
{% endif %}
|
|
</div>
|
|
{% if can_manage_active_team %}
|
|
<input type="hidden" name="showed_up_{{ row.pk }}" :value="state">
|
|
<div class="flex h-11 w-23 shrink-0 overflow-hidden rounded-lg">
|
|
<button type="button" class="flex-1" :class="state === 'true' ? 'bg-ok text-white' : 'bg-rule text-dim'" @click="state = 'true'" aria-label="{% trans "In" %}">
|
|
{% lucide "check" size=18 stroke_width=2.4 class="mx-auto" %}
|
|
</button>
|
|
<button type="button" class="flex-1" :class="state === 'false' ? 'bg-club text-white' : 'bg-rule text-dim'" @click="state = 'false'" aria-label="{% trans "Out" %}">
|
|
{% lucide "x" size=18 stroke_width=2.4 class="mx-auto" %}
|
|
</button>
|
|
</div>
|
|
{% elif row.showed_up is not None %}
|
|
<span class="pill {% if row.showed_up %}pill-ok{% else %}pill-danger{% endif %} shrink-0">{% if row.showed_up %}{% trans "In" %}{% else %}{% trans "Out" %}{% endif %}</span>
|
|
{% endif %}
|
|
</div>
|
|
{% empty %}
|
|
<div class="px-4 py-6 text-center text-sm text-muted">{% trans "No one matches this filter." %}</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
{% if can_manage_active_team %}
|
|
<button class="btn btn-dark mt-4 w-full" type="submit">{% trans "Save attendance" %}</button>
|
|
{% endif %}
|
|
</form>
|
|
{% endblock content %}
|