diff --git a/components/calendar/calendar-sidebar-panel.tsx b/components/calendar/calendar-sidebar-panel.tsx index f1325a8e..e93ab590 100644 --- a/components/calendar/calendar-sidebar-panel.tsx +++ b/components/calendar/calendar-sidebar-panel.tsx @@ -16,6 +16,35 @@ import { ContextMenu, ContextMenuItem, ContextMenuSeparator, ContextMenuSubMenu import { useContextMenu } from "@/hooks/use-context-menu"; import type { IJMAPClient } from '@/lib/jmap/client-interface'; +/** + * Split a per-account calendar list into "owned" (the user's own) and + * "shared" sub-buckets, then group shared by the owning principal so each + * delegator gets its own sub-section. + */ +type AccountCalendarSplit = { + owned: Calendar[]; + sharedGroups: { label: string; calendars: Calendar[] }[]; +}; + +function splitAccountCalendars(list: Calendar[]): AccountCalendarSplit { + const owned: Calendar[] = []; + const sharedBuckets = new Map(); + for (const cal of list) { + if (cal.isShared) { + const key = cal.accountId || cal.accountName || cal.id; + const bucket = sharedBuckets.get(key); + if (bucket) { + bucket.calendars.push(cal); + } else { + sharedBuckets.set(key, { label: cal.accountName || key, calendars: [cal] }); + } + } else { + owned.push(cal); + } + } + return { owned, sharedGroups: Array.from(sharedBuckets.values()) }; +} + interface CalendarSidebarPanelProps { calendars: Calendar[]; selectedCalendarIds: string[]; @@ -128,14 +157,14 @@ export function CalendarSidebarPanel({ list.push(cal); byAccount.set(key, list); } - const ordered: { key: string; label: string; calendars: Calendar[] }[] = []; + const ordered: { key: string; label: string; split: AccountCalendarSplit }[] = []; // Active account first. if (activeLocalAccountId && byAccount.has(activeLocalAccountId)) { const acct = localAccounts.find(a => a.id === activeLocalAccountId); ordered.push({ key: activeLocalAccountId, label: acct?.label || acct?.email || acct?.username || activeLocalAccountId, - calendars: byAccount.get(activeLocalAccountId)!, + split: splitAccountCalendars(byAccount.get(activeLocalAccountId)!), }); byAccount.delete(activeLocalAccountId); } @@ -145,7 +174,7 @@ export function CalendarSidebarPanel({ ordered.push({ key: acct.id, label: acct.label || acct.email || acct.username, - calendars: byAccount.get(acct.id)!, + split: splitAccountCalendars(byAccount.get(acct.id)!), }); byAccount.delete(acct.id); } @@ -154,7 +183,7 @@ export function CalendarSidebarPanel({ const fallbackLabel = key === '__other__' ? t('my_calendars') : list[0]?.accountName || key; - ordered.push({ key, label: fallbackLabel, calendars: list }); + ordered.push({ key, label: fallbackLabel, split: splitAccountCalendars(list) }); } return ordered; }, [multiAccountMode, calendars, localAccounts, activeLocalAccountId, t]); @@ -348,6 +377,7 @@ export function CalendarSidebarPanel({ {localAccountGroups.map((group, idx) => { const expanded = !collapsedAccountGroups.has(group.key); const isActive = group.key === activeLocalAccountId; + const { owned, sharedGroups } = group.split; return (