Build M7 Notifications for the mobile app

A flat, day-grouped notification list (Today / Earlier this week / Older)
scoped to every person the account manages, not just the switched-to one
-- matching how the header's unread badge already counts. The design
mock's per-type cards (RSVP-needed, invoice-due, medical-form, ...) have
no backing model support yet -- only news publishing creates a member
notification today -- so this stays generic rather than fabricating
categories; a notification whose source resolves to a News item links
through to it. Mark-all-read and mark-one-read actions on the same URL,
plus a first UI trigger for the push-subscribe plumbing built earlier.

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 13:52:44 +02:00
parent 13f369e10c
commit f41a255b61
6 changed files with 286 additions and 2 deletions

View File

@@ -0,0 +1,25 @@
{% load i18n %}
{% comment %}
One M7 notification row -- included from notifications.html once per day
bucket. Expects ``row`` ({notification, news_item}) in scope. The whole
row is the tap target (a submit button styled full-width, since a POST
form can't wrap another form/link) -- tapping always marks it read, and
also navigates to the linked News item when there is one (see
NotificationsView.post). A club-coloured bar marks it unread, same
treatment as management/templates/management/home.html's own
notifications card.
{% endcomment %}
<form method="post" action="{% url "mobile:notifications" %}">
{% csrf_token %}
<input type="hidden" name="action" value="mark_read">
<input type="hidden" name="notification_id" value="{{ row.notification.pk }}">
<button type="submit" class="m-card flex w-full items-center gap-3 p-3.5 text-left">
<span class="h-8.5 w-1.5 shrink-0 rounded-sm {% if not row.notification.read_at %}bg-club{% else %}bg-line{% endif %}"></span>
<span class="min-w-0 flex-1">
<span class="block text-sm font-semibold text-ink">{{ row.notification.title }}</span>
<span class="block truncate text-xs text-muted">{{ row.notification.body|truncatechars:120 }}</span>
{% if row.news_item %}<span class="block font-display text-[11px] font-extrabold text-club uppercase tracking-wide">{% trans "Club news" %}</span>{% endif %}
</span>
<span class="shrink-0 font-mono text-[11px] text-dim">{% blocktrans with time=row.notification.created|timesince %}{{ time }} ago{% endblocktrans %}</span>
</button>
</form>

View File

@@ -0,0 +1,75 @@
{% extends "mobile/base.html" %}
{% load i18n %}
{% comment %}
M7 -- design_handoff_rosterchief_platform/README.md's M7 section ("Inbox").
See NotificationsView's own docstring for why this is a flat, generic,
day-grouped list rather than the mockup's per-type cards: the underlying
model has no type/category field, and nothing but news publishing creates
a notification yet. Scoped to every managed_people, not just scope_person
-- same as unread_notification_count itself (mobile/mixins.py).
{% endcomment %}
{% block content %}
{% if 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 notifications will show up here." %}</p>
</div>
{% else %}
<div class="m-card flex items-center gap-3 p-4" x-data="{ requested: false }" x-show="!requested">
<div class="min-w-0 flex-1">
<div class="text-sm font-semibold text-ink">{% trans "Enable push notifications" %}</div>
<div class="text-xs text-muted">{% trans "Get notified on this device as soon as something new comes in." %}</div>
</div>
<button type="button" class="btn btn-dark shrink-0" @click="window.rosterchiefPush.subscribe(); requested = true">{% trans "Enable" %}</button>
</div>
{% if unread_notification_count %}
<form method="post" action="{% url "mobile:notifications" %}">
{% csrf_token %}
<input type="hidden" name="action" value="mark_all_read">
<button class="font-display text-xs font-extrabold tracking-wide text-club uppercase" type="submit">{% trans "Mark all read" %}</button>
</form>
{% endif %}
{% if not today and not earlier_this_week and not older %}
<div class="m-card p-6 text-center">
<p class="text-sm text-muted">{% trans "Nothing here yet." %}</p>
</div>
{% else %}
{% if today %}
<div>
<div class="sticky top-0 z-10 bg-paper py-1 font-display text-xs font-extrabold tracking-wide text-muted uppercase">{% trans "Today" %}</div>
<div class="flex flex-col gap-2">
{% for row in today %}
{% include "mobile/_notification_row.html" %}
{% endfor %}
</div>
</div>
{% endif %}
{% if earlier_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 "Earlier this week" %}</div>
<div class="flex flex-col gap-2">
{% for row in earlier_this_week %}
{% include "mobile/_notification_row.html" %}
{% endfor %}
</div>
</div>
{% endif %}
{% if older %}
<div>
<div class="sticky top-0 z-10 bg-paper py-1 font-display text-xs font-extrabold tracking-wide text-muted uppercase">{% trans "Older" %}</div>
<div class="flex flex-col gap-2">
{% for row in older %}
{% include "mobile/_notification_row.html" %}
{% endfor %}
</div>
</div>
{% endif %}
{% endif %}
{% endif %}
{% endblock content %}