From 2f45220c5da895ffbecae5980c9b7e28914f65bf Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Tue, 17 Mar 2026 01:14:44 +0800 Subject: [PATCH] Show shared-invitee icon on owner's calendar for events with active guests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds has_active_invitees flag to the events GET response. The Users icon now appears on the owner's calendar view when an event has accepted or tentative invitees, giving visual feedback that the event is actively shared. Single batch query with set lookup — no N+1. Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/app/routers/events.py | 18 ++++++++++++++++++ .../src/components/calendar/CalendarPage.tsx | 4 +++- frontend/src/types/index.ts | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/backend/app/routers/events.py b/backend/app/routers/events.py index e743f47..5a9525e 100644 --- a/backend/app/routers/events.py +++ b/backend/app/routers/events.py @@ -35,6 +35,7 @@ def _event_to_dict( display_calendar_name: str | None = None, display_calendar_color: str | None = None, can_modify: bool = False, + has_active_invitees: bool = False, ) -> dict: """Serialize a CalendarEvent ORM object to a response dict including calendar info.""" # For invited events: use display calendar if set, otherwise fallback to "Invited"/gray @@ -74,6 +75,7 @@ def _event_to_dict( "invitation_id": invitation_id, "display_calendar_id": display_calendar_id, "can_modify": can_modify, + "has_active_invitees": has_active_invitees, } return d @@ -243,6 +245,21 @@ async def get_events( all_event_ids = [e.id for e in events] override_map = await get_invitation_overrides_for_user(db, current_user.id, all_event_ids) + # Batch-fetch event IDs that have accepted/tentative invitees (for owner's shared icon) + active_invitee_set: set[int] = set() + if all_event_ids: + active_inv_result = await db.execute( + select(EventInvitation.event_id).where( + EventInvitation.event_id.in_(all_event_ids), + EventInvitation.status.in_(["accepted", "tentative"]), + ).distinct() + ) + active_invitee_set = {r[0] for r in active_inv_result.all()} + # Also mark parent events: if a parent has active invitees, all its children should show the icon + parent_ids = {e.parent_event_id for e in events if e.parent_event_id and e.parent_event_id in active_invitee_set} + if parent_ids: + active_invitee_set.update(e.id for e in events if e.parent_event_id in active_invitee_set) + response: List[dict] = [] for e in events: # Determine if this event is from an invitation @@ -272,6 +289,7 @@ async def get_events( display_calendar_name=disp_cal_name, display_calendar_color=disp_cal_color, can_modify=inv_can_modify, + has_active_invitees=(parent_id in active_invitee_set or e.id in active_invitee_set), )) # Fetch the user's Birthdays system calendar; only generate virtual events if visible diff --git a/frontend/src/components/calendar/CalendarPage.tsx b/frontend/src/components/calendar/CalendarPage.tsx index 3cec58e..dc58d13 100644 --- a/frontend/src/components/calendar/CalendarPage.tsx +++ b/frontend/src/components/calendar/CalendarPage.tsx @@ -389,6 +389,7 @@ export default function CalendarPage() { calendarColor: event.calendar_color || 'hsl(var(--accent-color))', is_invited: event.is_invited, can_modify: event.can_modify, + has_active_invitees: event.has_active_invitees, }, })); @@ -531,6 +532,7 @@ export default function CalendarPage() { const isAllDay = arg.event.allDay; const isRecurring = arg.event.extendedProps.is_recurring || arg.event.extendedProps.parent_event_id; const isInvited = arg.event.extendedProps.is_invited; + const hasActiveInvitees = arg.event.extendedProps.has_active_invitees; const calColor = arg.event.extendedProps.calendarColor as string; // Sync --event-color on the parent FC element so CSS rules (background, hover) @@ -544,7 +546,7 @@ export default function CalendarPage() { const icons = ( <> - {isInvited && } + {(isInvited || hasActiveInvitees) && } {isRecurring && } ); diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 357a297..0bbc8d1 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -117,6 +117,7 @@ export interface CalendarEvent { invitation_id?: number | null; display_calendar_id?: number | null; can_modify?: boolean; + has_active_invitees?: boolean; created_at: string; updated_at: string; }