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:
@@ -2,11 +2,6 @@
|
||||
icon, push subscribe) they all sit on top of. Coach mode (C1-C6) is a later
|
||||
phase -- see design_handoff_rosterchief_platform/README.md -- and has no
|
||||
routes here yet.
|
||||
|
||||
The M1-M7 views below are placeholders: each renders a "coming soon" card
|
||||
inside the real app shell (base.html), at its final URL name, so the shell
|
||||
(header, role switcher, tab bar, person switcher) can be verified end-to-end
|
||||
before every screen is built out one at a time.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
@@ -28,6 +23,7 @@ from club.models import ClubMembership
|
||||
from club.services.access import current_season, has_management_access, teams_managed_by
|
||||
from club.services.fees import remaining_balance
|
||||
from club.services.onboarding import checklist_for
|
||||
from club.services.sponsors import active_sponsors
|
||||
from controlpanel.messages import notify
|
||||
from events.models import Attendance, Event
|
||||
from events.services.calendar import week_bounds
|
||||
@@ -124,19 +120,6 @@ class PushSubscribeView(LoginRequiredMixin, ClubScopedPublicMixin, View):
|
||||
return JsonResponse({"status": "ok"})
|
||||
|
||||
|
||||
class _PlaceholderScreen(PersonScopeMixin, LoginRequiredMixin, TemplateView):
|
||||
"""Stand-in for an M-screen not built yet. Each subclass below is replaced
|
||||
entirely -- view and template -- when its screen is built; only the URL
|
||||
name/path in mobile/urls.py needs to stay put."""
|
||||
|
||||
template_name = "mobile/_placeholder.html"
|
||||
screen_title = ""
|
||||
active_tab = ""
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
return super().get_context_data(screen_title=self.screen_title, active_tab=self.active_tab, **kwargs)
|
||||
|
||||
|
||||
class HomeView(PersonScopeMixin, LoginRequiredMixin, TemplateView):
|
||||
"""M1 -- design_handoff_rosterchief_platform/README.md's M1 section: a
|
||||
hero card for the soonest upcoming event across everyone currently in
|
||||
@@ -157,6 +140,10 @@ class HomeView(PersonScopeMixin, LoginRequiredMixin, TemplateView):
|
||||
screen_title = _("Home")
|
||||
active_tab = "home"
|
||||
|
||||
#: Keeps the card from crowding the dues/news cards below it off the first
|
||||
#: screenful -- Calendar is the place to see everything still awaiting a reply.
|
||||
NEEDS_ANSWER_LIMIT = 5
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
people = self.people_in_scope
|
||||
now = timezone.now()
|
||||
@@ -164,6 +151,7 @@ class HomeView(PersonScopeMixin, LoginRequiredMixin, TemplateView):
|
||||
hero_attendance = None
|
||||
rsvp_closed = False
|
||||
needs_answer = []
|
||||
needs_answer_total = 0
|
||||
dues_rows = []
|
||||
news_item = None
|
||||
|
||||
@@ -183,7 +171,8 @@ class HomeView(PersonScopeMixin, LoginRequiredMixin, TemplateView):
|
||||
needs_answer_qs = upcoming.filter(status__in=[Attendance.AttendanceStatus.NO_RESPONSE, Attendance.AttendanceStatus.MAYBE]).order_by("event__start")
|
||||
if hero_attendance is not None:
|
||||
needs_answer_qs = needs_answer_qs.exclude(pk=hero_attendance.pk)
|
||||
needs_answer = list(needs_answer_qs)
|
||||
needs_answer_total = needs_answer_qs.count()
|
||||
needs_answer = list(needs_answer_qs[: self.NEEDS_ANSWER_LIMIT])
|
||||
|
||||
season = current_season(self.request.club)
|
||||
if season is not None:
|
||||
@@ -213,9 +202,15 @@ class HomeView(PersonScopeMixin, LoginRequiredMixin, TemplateView):
|
||||
hero_attendance=hero_attendance,
|
||||
rsvp_closed=rsvp_closed,
|
||||
needs_answer=needs_answer,
|
||||
needs_answer_remaining=max(needs_answer_total - len(needs_answer), 0),
|
||||
dues_rows=dues_rows,
|
||||
news_item=news_item,
|
||||
news_team=news_item.teams.first() if news_item is not None else None,
|
||||
# Club-wide, not person-specific -- shown regardless of managed_people,
|
||||
# unlike every other card on this screen. Reshuffled on every request
|
||||
# (see club.services.sponsors.active_sponsors) rather than once per
|
||||
# session, same as the public-website sponsor strip it shares logic with.
|
||||
sponsors=active_sponsors(self.request.club, randomize=True),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@@ -229,10 +224,12 @@ class CalendarView(PersonScopeMixin, LoginRequiredMixin, TemplateView):
|
||||
"Later"/past bucket, no ?month= paging) -- a simple, bounded agenda rather
|
||||
than a full season browser.
|
||||
|
||||
``?scope=all`` is the design doc's extra "All members" scope on top of
|
||||
the normal per-person chip switcher (mobile/mixins.py's scope_person):
|
||||
every club event instead of just scope_person's own invites, since
|
||||
there's no single person's Attendance row to key off.
|
||||
Always scoped to every one of ``self.managed_people`` -- unlike Home,
|
||||
this screen has no person switcher and no "every club event" toggle: it's
|
||||
just "what is my family invited to", full stop. (The design mock's own
|
||||
"All members"/list-vs-month/games-only controls aren't built -- they'd
|
||||
need real functionality behind them, not just markup; flagged rather than
|
||||
faked.)
|
||||
"""
|
||||
|
||||
template_name = "mobile/calendar.html"
|
||||
@@ -251,25 +248,16 @@ class CalendarView(PersonScopeMixin, LoginRequiredMixin, TemplateView):
|
||||
}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
scope_all = self.request.GET.get("scope") == "all"
|
||||
now = timezone.now()
|
||||
_this_week_start, this_week_end = week_bounds(timezone.localdate())
|
||||
next_week_end = this_week_end + datetime.timedelta(days=7)
|
||||
window_end = timezone.make_aware(datetime.datetime.combine(next_week_end, datetime.time.max))
|
||||
|
||||
rows = []
|
||||
if scope_all:
|
||||
events = (
|
||||
Event.objects.filter(club=self.request.club, cancelled=False, start__gte=now, start__lte=window_end)
|
||||
.select_related("location", "opponent")
|
||||
.prefetch_related("teams")
|
||||
.order_by("start")
|
||||
)
|
||||
rows = [{"event": event, "pill_class": "pill-info", "pill_label": event.get_kind_display()} for event in events]
|
||||
elif self.people_in_scope:
|
||||
if self.managed_people:
|
||||
attendances = (
|
||||
Attendance.objects.filter(
|
||||
member__in=self.people_in_scope,
|
||||
member__in=self.managed_people,
|
||||
event__club=self.request.club,
|
||||
event__cancelled=False,
|
||||
event__start__gte=now,
|
||||
@@ -279,10 +267,11 @@ class CalendarView(PersonScopeMixin, LoginRequiredMixin, TemplateView):
|
||||
.prefetch_related("event__teams")
|
||||
.order_by("event__start")
|
||||
)
|
||||
# Only worth naming whose row it is once "everyone" is aggregating more
|
||||
# than one person -- a single scoped person's own agenda doesn't need it.
|
||||
# Only worth naming whose row it is once there's more than one managed
|
||||
# person to tell apart -- a lone member's own agenda doesn't need it.
|
||||
show_member = len(self.managed_people) > 1
|
||||
rows = [
|
||||
{"event": attendance.event, "pill_class": self.STATUS_PILL_CLASSES.get(attendance.status, "pill-neutral"), "pill_label": attendance.get_status_display(), "member": attendance.member if self.scope_everyone else None}
|
||||
{"event": attendance.event, "pill_class": self.STATUS_PILL_CLASSES.get(attendance.status, "pill-neutral"), "pill_label": attendance.get_status_display(), "member": attendance.member if show_member else None}
|
||||
for attendance in attendances
|
||||
]
|
||||
|
||||
@@ -291,7 +280,7 @@ class CalendarView(PersonScopeMixin, LoginRequiredMixin, TemplateView):
|
||||
bucket = this_week if timezone.localtime(row["event"].start).date() <= this_week_end else next_week
|
||||
bucket.append(row)
|
||||
|
||||
return super().get_context_data(scope_all=scope_all, this_week=this_week, next_week=next_week, **kwargs)
|
||||
return super().get_context_data(this_week=this_week, next_week=next_week, **kwargs)
|
||||
|
||||
|
||||
class EventDetailView(PersonScopeMixin, LoginRequiredMixin, TemplateView):
|
||||
|
||||
Reference in New Issue
Block a user