From d0e9dde8ed6c7cdbd3eaeede325cd7467685356d Mon Sep 17 00:00:00 2001 From: Bernard Siebens Date: Sun, 23 Aug 2026 12:34:38 +0200 Subject: [PATCH] Add a Declined chip to the bench attendance sheet, drop silent's yellow tint Silent and declined are both left out of the default "Responded" view, but only silent had its own chip to review -- declined now gets one too. The silent-row background tint is gone; nothing else on this screen tints rows, and the chip itself already says which bucket you're looking at. Co-Authored-By: Claude Sonnet 5 --- mobile/coach_views.py | 8 +++++-- mobile/templates/mobile/coach/attendance.html | 5 +++- mobile/tests.py | 23 +++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/mobile/coach_views.py b/mobile/coach_views.py index 343bf3f..fe0c5cc 100644 --- a/mobile/coach_views.py +++ b/mobile/coach_views.py @@ -169,8 +169,9 @@ class CoachAttendanceView(CoachScopeMixin, LoginRequiredMixin, TemplateView): #: param) means "Responded" (IN_STATUSES: present/selected/maybe), the #: default view. A coach doesn't need to check in someone silent or #: declined -- neither is expected to show up -- so those are left out of - #: the default rather than needing to be filtered away each time. - FILTERS = {"silent"} + #: the default rather than needing to be filtered away each time; each + #: still gets its own chip to review who's in either bucket. + FILTERS = {"silent", "declined"} def get_event(self): if self.active_team is None: @@ -195,6 +196,8 @@ class CoachAttendanceView(CoachScopeMixin, LoginRequiredMixin, TemplateView): filter_param = "" if filter_param == "silent": rows = [row for row in attendances if row.is_silent] + elif filter_param == "declined": + rows = [row for row in attendances if row.status in OUT_STATUSES] else: rows = [row for row in attendances if row.status in IN_STATUSES] @@ -204,6 +207,7 @@ class CoachAttendanceView(CoachScopeMixin, LoginRequiredMixin, TemplateView): total_count=len(attendances), responded_count=sum(1 for row in attendances if row.status in IN_STATUSES), silent_count=sum(1 for row in attendances if row.is_silent), + declined_count=sum(1 for row in attendances if row.status in OUT_STATUSES), checked_in_count=sum(1 for row in attendances if row.showed_up is not None), filter_param=filter_param, **kwargs, diff --git a/mobile/templates/mobile/coach/attendance.html b/mobile/templates/mobile/coach/attendance.html index b918d92..972e86e 100644 --- a/mobile/templates/mobile/coach/attendance.html +++ b/mobile/templates/mobile/coach/attendance.html @@ -31,13 +31,16 @@ {% trans "Silent" %} {{ silent_count }} + + {% trans "Declined" %} {{ declined_count }} +
{% csrf_token %}
{% for row in rows %} -
+
{{ row.membership.jersey_number|default:"—" }}
{{ row.member.get_full_name }}
diff --git a/mobile/tests.py b/mobile/tests.py index 7336223..3c54cbe 100644 --- a/mobile/tests.py +++ b/mobile/tests.py @@ -3113,6 +3113,18 @@ class CoachAttendanceViewTests(TestCase): self.assertEqual([row.member for row in rows], [self.player]) self.assertEqual(response.context["responded_count"], 1) + def test_declined_filter_narrows_to_declined_rows(self): + declined_member = Member.objects.create(first_name="Cara", last_name="Declined") + TeamMembership.objects.create(team=self.team, member=declined_member, season=self.season) + Attendance.objects.update_or_create(event=self.event, member=declined_member, defaults={"status": Attendance.AttendanceStatus.ABSENT}) + self.client.force_login(self.user) + + response = self._get(filter="declined") + + rows = response.context["rows"] + self.assertEqual([row.member for row in rows], [declined_member]) + self.assertEqual(response.context["declined_count"], 1) + def test_the_goalies_chip_is_gone(self): self.client.force_login(self.user) @@ -3120,6 +3132,17 @@ class CoachAttendanceViewTests(TestCase): self.assertNotContains(response, "Goalies") self.assertContains(response, "Responded") + self.assertContains(response, "Declined") + + def test_silent_rows_have_no_special_background(self): + silent_member = Member.objects.create(first_name="Ben", last_name="Silent") + TeamMembership.objects.create(team=self.team, member=silent_member, season=self.season) + Attendance.objects.update_or_create(event=self.event, member=silent_member, defaults={"status": Attendance.AttendanceStatus.NO_RESPONSE}) + self.client.force_login(self.user) + + response = self._get(filter="silent") + + self.assertNotContains(response, "bg-warn-bg") def test_event_from_another_team_is_not_reachable(self): other_team = Team.objects.create(club=self.club, name="U14", short_name="U14")