Add the D7 attendance sparkline to member detail; tighten the News list rows

Member detail: a 12-bar season attendance sparkline (present/absent/upcoming)
beside Present/Absent/No-reply totals, shown for any rostered non-guardian
member with events this season (events.services.attendance.member_attendance_
sparkline/_counts). News list: rows are one dense line (status pill inline
with the headline, everything else folded into a single meta line) instead
of the previous multi-line stacked layout that read as a small card per item.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
2026-08-20 08:55:44 +02:00
parent 71a96a8212
commit 8baf3f7b3d
8 changed files with 223 additions and 21 deletions

View File

@@ -167,6 +167,38 @@
</div>
</div>
{% if show_attendance %}
{# D7's season card: a 12-bar attendance sparkline (present/absent/upcoming, oldest first) beside the season's Present/Absent/No-reply totals. #}
<div class="card card-body mt-4">
<h2 class="card-title">{% lucide "calendar-check" size=18 %} {% trans "Attendance" %}</h2>
{% if not attendance_sparkline %}
<p class="text-sm text-muted">{% trans "No events yet this season." %}</p>
{% else %}
<div class="flex flex-wrap items-center gap-8">
<div class="attendance-sparkline">
{% for bar in attendance_sparkline %}
<span class="attendance-bar attendance-bar-{{ bar.state }}" title="{{ bar.event.title }} — {{ bar.event.start|date:"j M" }}"></span>
{% endfor %}
</div>
<div class="flex gap-6">
<div>
<div class="font-display text-2xl leading-none font-extrabold text-ok tabular-nums">{{ attendance_counts.present }}</div>
<div class="mt-1 font-display text-[11px] font-bold tracking-[.1em] text-muted uppercase">{% trans "Present" %}</div>
</div>
<div>
<div class="font-display text-2xl leading-none font-extrabold text-club tabular-nums">{{ attendance_counts.absent }}</div>
<div class="mt-1 font-display text-[11px] font-bold tracking-[.1em] text-muted uppercase">{% trans "Absent" %}</div>
</div>
<div>
<div class="font-display text-2xl leading-none font-extrabold text-dim tabular-nums">{{ attendance_counts.no_reply }}</div>
<div class="mt-1 font-display text-[11px] font-bold tracking-[.1em] text-muted uppercase">{% trans "No reply" %}</div>
</div>
</div>
</div>
{% endif %}
</div>
{% endif %}
{% comment %}
Onboarding checklist -- club.services.onboarding.checklist_for, paired with
MemberRequirementStatus if one exists. Any staff can mark an item done or

View File

@@ -22,24 +22,26 @@
<div class="card overflow-hidden divide-y">
{% for item in news_items %}
<a href="{% querystring selected=item.pk %}" class="flex flex-col gap-1 border-l-[3px] px-4 py-3 {% if news_item and item.pk == news_item.pk %}border-club bg-row-sel{% else %}border-transparent hover:bg-subhead{% endif %}">
<div class="flex flex-wrap items-center gap-2">
{% if item.status == "draft" %}
<span class="badge badge-sm">{% trans "Draft" %}</span>
{% elif item.is_scheduled %}
<span class="badge badge-info badge-sm">{% trans "Scheduled" %}</span>
{% else %}
<span class="badge badge-success badge-sm">{% trans "Published" %}</span>
{% endif %}
<span class="font-mono text-xs text-dim">
{% if item.status == "draft" %}{% blocktrans with time=item.modified|timesince %}Edited {{ time }} ago{% endblocktrans %}
{% else %}{{ item.published_at|date:"j M Y" }}{% endif %}
</span>
</div>
<div class="truncate font-display text-base font-extrabold text-ink uppercase">{{ item.title }}</div>
<div class="truncate text-[13px] text-muted">
{% if item.created_by %}{{ item.created_by }} &middot; {% endif %}
{% for team in item.teams.all %}{{ team.short_name }}{% if not forloop.last %}, {% endif %}{% empty %}{% trans "Club-wide" %}{% endfor %}
{# One dense line per row (D8's actual list-pane density), not a stacked mini-card: status pill inline with the headline, everything else folded into one muted meta line below. #}
<a href="{% querystring selected=item.pk %}" class="flex items-start gap-2.5 border-l-[3px] px-3.5 py-2.5 {% if news_item and item.pk == news_item.pk %}border-club bg-row-sel{% else %}border-transparent hover:bg-subhead{% endif %}">
{% if item.status == "draft" %}
<span class="badge badge-sm mt-0.5 shrink-0">{% trans "Draft" %}</span>
{% elif item.is_scheduled %}
<span class="badge badge-info badge-sm mt-0.5 shrink-0">{% trans "Scheduled" %}</span>
{% else %}
<span class="badge badge-success badge-sm mt-0.5 shrink-0">{% trans "Published" %}</span>
{% endif %}
<div class="min-w-0 flex-1">
<div class="truncate font-display text-sm font-bold text-ink uppercase">{{ item.title }}</div>
<div class="truncate text-xs text-muted">
<span class="font-mono text-dim">
{% if item.status == "draft" %}{% blocktrans with time=item.modified|timesince %}Edited {{ time }} ago{% endblocktrans %}
{% else %}{{ item.published_at|date:"j M Y" }}{% endif %}
</span>
&middot;
{% if item.created_by %}{{ item.created_by }} &middot; {% endif %}
{% for team in item.teams.all %}{{ team.short_name }}{% if not forloop.last %}, {% endif %}{% empty %}{% trans "Club-wide" %}{% endfor %}
</div>
</div>
</a>
{% empty %}