From f45b7a211554db26113e1b2efa8899400a01596e Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Fri, 6 Mar 2026 06:23:45 +0800 Subject: [PATCH] Fix 4 reported bugs from Phase 4 testing 1. Invite auto-sends at read_only: now stages connection with permission selector (Read Only / Create Modify / Full Access) before sending 2. Shared calendars missing from event create dropdown: members with create_modify+ permission now see shared calendars in calendar picker 3. Shared calendar category not showing for owner: owner's shared calendars now appear under SHARED CALENDARS section with "Owner" badge 4. Event creation not updating calendar: handlePanelClose now invalidates calendar-events query to ensure FullCalendar refreshes Co-Authored-By: Claude Opus 4.6 --- .../src/components/calendar/CalendarForm.tsx | 66 +++++++++++++++---- .../components/calendar/CalendarSidebar.tsx | 21 +++--- .../components/calendar/EventDetailPanel.tsx | 9 ++- .../calendar/SharedCalendarSection.tsx | 40 ++++++++++- 4 files changed, 113 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/calendar/CalendarForm.tsx b/frontend/src/components/calendar/CalendarForm.tsx index d718ec8..842b694 100644 --- a/frontend/src/components/calendar/CalendarForm.tsx +++ b/frontend/src/components/calendar/CalendarForm.tsx @@ -1,4 +1,4 @@ -import { useState, FormEvent } from 'react'; +import { useState, FormEvent, useCallback } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import api, { getErrorMessage } from '@/lib/api'; @@ -15,6 +15,7 @@ import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; import { Switch } from '@/components/ui/switch'; +import { Select } from '@/components/ui/select'; import { useConnections } from '@/hooks/useConnections'; import { useSharedCalendars } from '@/hooks/useSharedCalendars'; import CalendarMemberSearch from './CalendarMemberSearch'; @@ -36,6 +37,8 @@ export default function CalendarForm({ calendar, onClose }: CalendarFormProps) { const [color, setColor] = useState(calendar?.color || '#3b82f6'); const [isShared, setIsShared] = useState(calendar?.is_shared ?? false); + const [pendingInvite, setPendingInvite] = useState<{ conn: Connection; permission: CalendarPermission } | null>(null); + const { connections } = useConnections(); const { invite, isInviting, updateMember, removeMember } = useSharedCalendars(); @@ -93,14 +96,19 @@ export default function CalendarForm({ calendar, onClose }: CalendarFormProps) { mutation.mutate(); }; - const handleInvite = async (conn: Connection) => { - if (!calendar) return; + const handleSelectConnection = useCallback((conn: Connection) => { + setPendingInvite({ conn, permission: 'read_only' }); + }, []); + + const handleSendInvite = async () => { + if (!calendar || !pendingInvite) return; await invite({ calendarId: calendar.id, - connectionId: conn.id, - permission: 'read_only', + connectionId: pendingInvite.conn.id, + permission: pendingInvite.permission, canAddOthers: false, }); + setPendingInvite(null); }; const handleUpdatePermission = async (memberId: number, permission: CalendarPermission) => { @@ -179,12 +187,48 @@ export default function CalendarForm({ calendar, onClose }: CalendarFormProps) { - + {pendingInvite ? ( +
+
+ + {pendingInvite.conn.connected_preferred_name || pendingInvite.conn.connected_umbral_name} + + +
+
+ + +
+
+ ) : ( + + )}
- {/* Owned calendars list */} + {/* Owned calendars list (non-shared only) */}
- {calendars.map((cal) => ( + {calendars.filter((c) => !c.is_shared).map((cal) => (
- {/* Shared calendars section */} - + {/* Shared calendars section -- owned + member */} + {(calendars.some((c) => c.is_shared) || sharedCalendars.length > 0) && ( + c.is_shared)} + memberships={sharedCalendars} + visibleSharedIds={visibleSharedIds} + onVisibilityChange={handleSharedVisibilityChange} + onEditCalendar={handleEdit} + onToggleCalendar={handleToggle} + /> + )} {/* Templates section */}
diff --git a/frontend/src/components/calendar/EventDetailPanel.tsx b/frontend/src/components/calendar/EventDetailPanel.tsx index 1aa2449..b4e0e0e 100644 --- a/frontend/src/components/calendar/EventDetailPanel.tsx +++ b/frontend/src/components/calendar/EventDetailPanel.tsx @@ -227,8 +227,13 @@ export default function EventDetailPanel({ isSharedEvent = false, }: EventDetailPanelProps) { const queryClient = useQueryClient(); - const { data: calendars = [] } = useCalendars(); - const selectableCalendars = calendars.filter((c) => !c.is_system); + const { data: calendars = [], sharedData: sharedMemberships = [] } = useCalendars(); + const selectableCalendars = [ + ...calendars.filter((c) => !c.is_system), + ...sharedMemberships + .filter((m) => m.permission === 'create_modify' || m.permission === 'full_access') + .map((m) => ({ id: m.calendar_id, name: m.calendar_name, color: m.local_color || m.calendar_color, is_default: false })), + ]; const defaultCalendar = calendars.find((c) => c.is_default); const { data: locations = [] } = useQuery({ diff --git a/frontend/src/components/calendar/SharedCalendarSection.tsx b/frontend/src/components/calendar/SharedCalendarSection.tsx index 9d52124..d42308c 100644 --- a/frontend/src/components/calendar/SharedCalendarSection.tsx +++ b/frontend/src/components/calendar/SharedCalendarSection.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { Ghost, Pencil } from 'lucide-react'; import { Checkbox } from '@/components/ui/checkbox'; -import type { SharedCalendarMembership } from '@/types'; +import type { Calendar, SharedCalendarMembership } from '@/types'; import SharedCalendarSettings from './SharedCalendarSettings'; const STORAGE_KEY = 'umbra_shared_cal_visibility'; @@ -19,19 +19,25 @@ function saveVisibility(v: Record) { } interface SharedCalendarSectionProps { + ownedSharedCalendars?: Calendar[]; memberships: SharedCalendarMembership[]; visibleSharedIds: Set; onVisibilityChange: (calendarId: number, visible: boolean) => void; + onEditCalendar?: (calendar: Calendar) => void; + onToggleCalendar?: (calendar: Calendar) => void; } export default function SharedCalendarSection({ + ownedSharedCalendars = [], memberships, visibleSharedIds, onVisibilityChange, + onEditCalendar, + onToggleCalendar, }: SharedCalendarSectionProps) { const [settingsFor, setSettingsFor] = useState(null); - if (memberships.length === 0) return null; + if (memberships.length === 0 && ownedSharedCalendars.length === 0) return null; return ( <> @@ -43,6 +49,36 @@ export default function SharedCalendarSection({
+ {ownedSharedCalendars.map((cal) => ( +
+ onToggleCalendar?.(cal)} + className="shrink-0" + style={{ + accentColor: cal.color, + borderColor: cal.is_visible ? cal.color : undefined, + backgroundColor: cal.is_visible ? cal.color : undefined, + }} + /> + + {cal.name} + Owner + +
+ ))} {memberships.map((m) => { const color = m.local_color || m.calendar_color; const isVisible = visibleSharedIds.has(m.calendar_id);