Home/Calendar polish: person switcher placement, full-width Calendar, sponsors

- Home: the person switcher (chip row) now lives in the white content area
  instead of the navy header, matching the design canvas's own M1 markup,
  and renders smaller. Home is now the only screen that shows it at all.
- Calendar: dropped the person switcher and the "My schedule"/"All members"
  toggle entirely -- it always shows every event self.managed_people is
  invited to, full stop. Rows now run edge-to-edge (-mx-4) instead of
  living in a rounded, inset card, matching the M3 design canvas's own
  full-bleed layout. (The design mock's List/Month/"Games only" controls
  aren't reproduced -- no real functionality behind them yet.)
- Home's "Needs your answer" card caps at 5 items with a "+N more in
  Calendar" link, so it can't crowd the dues/news cards below it off the
  first screenful.
- Home gains a sponsors strip at the very bottom (horizontally scrolling,
  scrollbar hidden) -- active sponsors only, reshuffled on every request.
  club.services.sponsors.active_sponsors factors the "what counts as
  active" query out of club/api.py's public sponsors endpoint so both it
  and Home share one definition.
- Also removed mobile/views.py's now-fully-dead _PlaceholderScreen and its
  template -- every M1-M7 screen has had a real implementation for a while
  and nothing subclassed it anymore.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
2026-08-21 15:00:33 +02:00
parent 4b2b84793f
commit 96fc58e453
12 changed files with 263 additions and 198 deletions

View File

@@ -6,7 +6,7 @@
warn for everything else) next to the date, per the design doc's M3 row
description.
{% endcomment %}
<a class="flex items-center gap-3 bg-white p-3.5 {% if row.event.kind == "game" %}border-l-4 border-club{% endif %}" href="{% url "mobile:event_detail" row.event.pk %}">
<a class="flex items-center gap-3 bg-white px-4 py-3 {% if row.event.kind == "game" %}border-l-4 border-club{% endif %}" 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>

View File

@@ -1,9 +0,0 @@
{% extends "mobile/base.html" %}
{% load i18n %}
{% block content %}
<div class="m-card p-6 text-center">
<p class="font-display text-lg font-bold text-ink uppercase">{{ screen_title }}</p>
<p class="mt-1 text-sm text-muted">{% trans "This screen is coming soon." %}</p>
</div>
{% endblock content %}

View File

@@ -70,23 +70,6 @@
{% if unread_notification_count %}<span class="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-club" style="border: 2px solid var(--color-navy)"></span>{% endif %}
</a>
</div>
{% if managed_people|length > 1 %}
<div class="scrollbar-hide mt-3 flex gap-2 overflow-x-auto pb-0.5">
<a class="person-chip {% if scope_everyone %}person-chip-active{% endif %}" href="?as=all">
<span class="person-chip-avatar">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="3"/><circle cx="17" cy="9" r="2.6"/><path d="M2.5 20c0-3.3 2.5-5.6 5.5-5.6s5.5 2.3 5.5 5.6"/><path d="M14 15c2.6.3 4.5 2.3 4.5 5"/></svg>
</span>
<span class="person-chip-label">{% trans "All" %}</span>
</a>
{% for person in managed_people %}
<a class="person-chip {% if not scope_everyone and person == scope_person %}person-chip-active{% endif %}" href="?as={{ person.pk }}">
<span class="person-chip-avatar">{{ person.first_name|slice:":1" }}</span>
<span class="person-chip-label">{% if person == me %}{% trans "Me" %}{% else %}{{ person.first_name }}{% endif %}</span>
</a>
{% endfor %}
</div>
{% endif %}
</header>
<main class="flex-1 overflow-y-auto">

View File

@@ -2,56 +2,54 @@
{% load i18n %}
{% comment %}
M3 -- design_handoff_rosterchief_platform/README.md's M3 section: a
chronological agenda, grouped under "This week"/"Next week", scoped to
scope_person's own invites by default or, with the "All members" toggle
below, every club event. See CalendarView's own docstring for the
browsing-window judgment call (current + next calendar week, no further
paging).
M3 -- design_handoff_rosterchief_platform/README.md's M3 section: a full-
width chronological agenda, grouped under "This week"/"Next week" (see
CalendarView's own docstring for the browsing-window judgment call --
current + next calendar week, no further paging). No person switcher and
no club-wide toggle here -- always every event self.managed_people is
invited to, full stop. -mx-4 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 (the desktop List/Month/"Games only" controls in that mock
aren't reproduced here -- no real functionality behind them yet).
{% endcomment %}
{% 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 scope_all %}bg-ink text-white{% else %}border border-line bg-white text-muted{% endif %}"
href="?{% if request.GET.as %}as={{ request.GET.as }}{% endif %}">
{% trans "My schedule" %}
</a>
<a class="flex h-9 flex-1 items-center justify-center rounded-full font-display text-xs font-extrabold tracking-wide uppercase {% if scope_all %}bg-ink text-white{% else %}border border-line bg-white text-muted{% endif %}"
href="?scope=all{% if request.GET.as %}&as={{ request.GET.as }}{% endif %}">
{% trans "All members" %}
</a>
<div class="-mx-4">
{% if not managed_people %}
<div class="mx-4">
<div class="m-card p-6 text-center">
<p class="font-display text-lg font-extrabold text-ink uppercase">{% trans "No one to show yet" %}</p>
<p class="mt-1 text-sm text-muted">{% trans "Once you're linked to a member record, their schedule will show up here." %}</p>
</div>
</div>
{% elif not this_week and not next_week %}
<div class="mx-4">
<div class="m-card p-6 text-center">
<p class="text-sm text-muted">{% trans "Nothing scheduled in the next two weeks." %}</p>
</div>
</div>
{% else %}
{% if this_week %}
<div>
<div class="sticky top-0 z-10 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 %}
{% include "mobile/_calendar_row.html" %}
{% endfor %}
</div>
</div>
{% endif %}
{% if next_week %}
<div>
<div class="sticky top-0 z-10 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 %}
{% include "mobile/_calendar_row.html" %}
{% endfor %}
</div>
</div>
{% endif %}
{% endif %}
</div>
{% if not scope_all and not managed_people %}
<div class="m-card p-6 text-center">
<p class="font-display text-lg font-extrabold text-ink uppercase">{% trans "No one to show yet" %}</p>
<p class="mt-1 text-sm text-muted">{% trans "Once you're linked to a member record, their schedule will show up here." %}</p>
</div>
{% elif not this_week and not next_week %}
<div class="m-card p-6 text-center">
<p class="text-sm text-muted">{% trans "Nothing scheduled in the next two weeks." %}</p>
</div>
{% else %}
{% if this_week %}
<div>
<div class="sticky top-0 z-10 bg-paper py-1 font-display text-xs font-extrabold tracking-wide text-muted uppercase">{% trans "This week" %}</div>
<div class="flex flex-col gap-px overflow-hidden rounded-box bg-line">
{% for row in this_week %}
{% include "mobile/_calendar_row.html" %}
{% endfor %}
</div>
</div>
{% endif %}
{% if next_week %}
<div>
<div class="sticky top-0 z-10 bg-paper py-1 font-display text-xs font-extrabold tracking-wide text-muted uppercase">{% trans "Next week" %}</div>
<div class="flex flex-col gap-px overflow-hidden rounded-box bg-line">
{% for row in next_week %}
{% include "mobile/_calendar_row.html" %}
{% endfor %}
</div>
</div>
{% endif %}
{% endif %}
{% endblock content %}

View File

@@ -2,16 +2,37 @@
{% load i18n %}
{% comment %}
M1 -- design_handoff_rosterchief_platform/README.md's M1 section. Four
independently-optional cards, scoped to people_in_scope (mobile/mixins.py)
-- everyone the account manages once there's more than one (the header's
"All" chip, on by default), or just the one person picked from the chip
row. A hero for the soonest upcoming event with a quick In/Out RSVP, a
"needs your answer" list, a season-dues card per person who owes money,
and a news teaser.
M1 -- design_handoff_rosterchief_platform/README.md's M1 section. The
person switcher (chip row) sits in the white content area here, not the
navy header -- matching the design canvas's own M1 markup, and Home is
now the only screen that renders it at all (mobile/mixins.py's scope
resolution still runs for every screen, but only Home's UI exposes it).
Four independently-optional cards below it, scoped to people_in_scope --
everyone the account manages once there's more than one ("All", on by
default), or just the one person picked from the chip row. A hero for
the soonest upcoming event with a quick In/Out RSVP, a "needs your
answer" list, a season-dues card per person who owes money, and a news
teaser.
{% endcomment %}
{% block content %}
{% if managed_people|length > 1 %}
<div class="scrollbar-hide -mt-1 flex gap-2 overflow-x-auto pb-0.5">
<a class="person-chip {% if scope_everyone %}person-chip-active{% endif %}" href="?as=all">
<span class="person-chip-avatar">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="3"/><circle cx="17" cy="9" r="2.6"/><path d="M2.5 20c0-3.3 2.5-5.6 5.5-5.6s5.5 2.3 5.5 5.6"/><path d="M14 15c2.6.3 4.5 2.3 4.5 5"/></svg>
</span>
<span class="person-chip-label">{% trans "All" %}</span>
</a>
{% for person in managed_people %}
<a class="person-chip {% if not scope_everyone and person == scope_person %}person-chip-active{% endif %}" href="?as={{ person.pk }}">
<span class="person-chip-avatar">{{ person.first_name|slice:":1" }}</span>
<span class="person-chip-label">{% if person == me %}{% trans "Me" %}{% else %}{{ person.first_name }}{% endif %}</span>
</a>
{% endfor %}
</div>
{% endif %}
{% if managed_people %}
{% if hero_attendance %}
<div class="m-card-dark overflow-hidden">
@@ -56,7 +77,7 @@
<div class="m-card p-4">
<div class="flex items-center justify-between">
<span class="font-display text-xs font-extrabold text-muted uppercase tracking-wide">{% trans "Needs your answer" %}</span>
<span class="font-display text-lg font-extrabold text-club">{{ needs_answer|length }}</span>
<span class="font-display text-lg font-extrabold text-club">{{ needs_answer|length|add:needs_answer_remaining }}</span>
</div>
<div class="mt-3 flex flex-col gap-2.5">
{% for attendance in needs_answer %}
@@ -74,6 +95,11 @@
</a>
{% endfor %}
</div>
{% if needs_answer_remaining %}
<a class="mt-3 block text-center text-xs font-semibold text-club" href="{% url "mobile:calendar" %}">
{% blocktrans count counter=needs_answer_remaining %}+{{ counter }} more in Calendar{% plural %}+{{ counter }} more in Calendar{% endblocktrans %}
</a>
{% endif %}
</div>
{% endif %}
@@ -128,4 +154,23 @@
<p class="mt-1 text-sm text-muted">{% trans "Once you're linked to a member record, their schedule and updates will show up here." %}</p>
</div>
{% endif %}
{% if sponsors %}
<div>
<div class="mb-2.5 font-display text-xs font-extrabold text-muted uppercase tracking-wide">{% trans "Sponsors" %}</div>
<div class="scrollbar-hide flex gap-3 overflow-x-auto pb-0.5">
{% for sponsor in sponsors %}
{% if sponsor.url %}
<a class="m-card flex h-14 w-28 shrink-0 items-center justify-center p-2" href="{{ sponsor.url }}" target="_blank" rel="noopener">
{% if sponsor.logo %}<img class="max-h-full max-w-full object-contain" src="{{ sponsor.logo.url }}" alt="{{ sponsor.name }}">{% else %}<span class="text-xs font-semibold text-muted">{{ sponsor.name }}</span>{% endif %}
</a>
{% else %}
<div class="m-card flex h-14 w-28 shrink-0 items-center justify-center p-2">
{% if sponsor.logo %}<img class="max-h-full max-w-full object-contain" src="{{ sponsor.logo.url }}" alt="{{ sponsor.name }}">{% else %}<span class="text-xs font-semibold text-muted">{{ sponsor.name }}</span>{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
{% endblock content %}