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; }