Capture a private reason when declining an event RSVP

Attendance.note already existed but nothing wrote to it. Picking "Out"
anywhere (Home's hero, Coach Today's "Also yours", event_detail's per-
person answers) now opens an optional reason field before submitting,
via an Alpine two-step within the same form rather than a separate
confirmation screen. EventDetailView.post stores it only when the status
is actually "absent", and clears it the moment someone flips back to
In/Maybe -- a stale "sick" note under a since-changed answer would just
be confusing.

Private by construction, not by a permission check: nothing anywhere
renders another member's own note -- the squad-response view stays
counts-only like it already was. The only two places that read it back
are this member/family's own "Your answers" card and Coach mode's bench
attendance screen, both already scoped to people the viewer has a real
claim on.

Also, on the shared hero In/Out buttons (Home + Coach Today): dropped the
green "In" styling -- nobody's answered yet at that point, so a green
default misleadingly read as an already-recorded answer -- and added
min-w-0 to both buttons so flex-1 actually splits the row evenly; a
longer label's own intrinsic width was winning it a bigger share
otherwise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
2026-08-22 13:01:07 +02:00
parent 3426ea8447
commit 614c35861b
8 changed files with 160 additions and 57 deletions

View File

@@ -444,7 +444,15 @@ class EventDetailView(PersonScopeMixin, LoginRequiredMixin, TemplateView):
if event.deadline is not None and event.deadline < timezone.now():
return HttpResponseBadRequest(_("Replies are closed for this event."))
Attendance.objects.update_or_create(event=event, member=member, defaults={"status": status})
# A reason is only ever meaningful attached to "Out" -- clearing it the
# moment someone flips back to In/Maybe avoids a stale "sick" note
# hanging around under an answer it no longer explains. Private by
# construction, not by a visibility flag: nothing renders another
# member's own note anywhere -- only this member/family's own screens
# (event_detail's "Your answers") and Coach mode's bench attendance
# (mobile/templates/mobile/coach/attendance.html) ever read it.
note = request.POST.get("note", "").strip() if status == Attendance.AttendanceStatus.ABSENT else ""
Attendance.objects.update_or_create(event=event, member=member, defaults={"status": status, "note": note})
if request.POST.get("next") == "event_detail":
return HttpResponseRedirect(reverse("mobile:event_detail", kwargs={"pk": event.pk}))