From f64e181fbea6e1b3d1d0e95e0bbeb993ba0f5e4c Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Mon, 23 Feb 2026 13:10:31 +0800 Subject: [PATCH] Fix template form: location picker auto-open and event title wording - LocationPicker: skip initial mount effect so dropdown doesn't auto-open when form loads with an existing location value - EventForm: separate templateData/templateName props from event prop so template-based creation shows "Create Event from X Template" title instead of "Edit Event", and correctly uses Create button + no Delete - CalendarPage: pass templateName through to EventForm Co-Authored-By: Claude Opus 4.6 --- .../src/components/calendar/CalendarPage.tsx | 7 ++- .../src/components/calendar/EventForm.tsx | 43 ++++++++++++------- .../src/components/ui/location-picker.tsx | 7 +++ 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/calendar/CalendarPage.tsx b/frontend/src/components/calendar/CalendarPage.tsx index d0d93d3..92a562e 100644 --- a/frontend/src/components/calendar/CalendarPage.tsx +++ b/frontend/src/components/calendar/CalendarPage.tsx @@ -43,6 +43,7 @@ export default function CalendarPage() { const [calendarTitle, setCalendarTitle] = useState(''); const [templateEvent, setTemplateEvent] = useState | null>(null); + const [templateName, setTemplateName] = useState(null); // Scope dialog state const [scopeDialogOpen, setScopeDialogOpen] = useState(false); @@ -284,6 +285,7 @@ export default function CalendarPage() { setShowForm(false); setEditingEvent(null); setTemplateEvent(null); + setTemplateName(null); setActiveEditScope(null); setSelectedStart(null); setSelectedEnd(null); @@ -300,6 +302,7 @@ export default function CalendarPage() { is_starred: template.is_starred, recurrence_rule: template.recurrence_rule || undefined, } as Partial); + setTemplateName(template.name); setEditingEvent(null); setShowForm(true); }; @@ -385,7 +388,9 @@ export default function CalendarPage() { {showForm && ( | null; + templateName?: string | null; initialStart?: string | null; initialEnd?: string | null; initialAllDay?: boolean; @@ -88,10 +90,12 @@ function plusOneHour(dt: string): string { return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; } -export default function EventForm({ event, initialStart, initialEnd, initialAllDay, editScope, onClose }: EventFormProps) { +export default function EventForm({ event, templateData, templateName, initialStart, initialEnd, initialAllDay, editScope, onClose }: EventFormProps) { const queryClient = useQueryClient(); const { data: calendars = [] } = useCalendars(); - const isAllDay = event?.all_day ?? initialAllDay ?? false; + // Merge template data as defaults for new events + const source = event || templateData; + const isAllDay = source?.all_day ?? initialAllDay ?? false; // Default to current time / +1 hour when creating a new event with no selection const defaultStart = nowLocal(); @@ -100,23 +104,24 @@ export default function EventForm({ event, initialStart, initialEnd, initialAllD const rawEnd = event?.end_datetime || initialEnd || defaultEnd; const defaultCalendar = calendars.find((c) => c.is_default); - const initialCalendarId = event?.calendar_id?.toString() || defaultCalendar?.id?.toString() || ''; + const initialCalendarId = source?.calendar_id?.toString() || defaultCalendar?.id?.toString() || ''; + const isEditing = !!event?.id; // For all-day events, adjust end date for display (FullCalendar exclusive end) const displayEnd = isAllDay ? adjustAllDayEndForDisplay(rawEnd) : rawEnd; const [formData, setFormData] = useState({ - title: event?.title || '', - description: event?.description || '', + title: source?.title || '', + description: source?.description || '', start_datetime: formatForInput(rawStart, isAllDay, '09:00'), end_datetime: formatForInput(displayEnd, isAllDay, '10:00'), all_day: isAllDay, - location_id: event?.location_id?.toString() || '', + location_id: source?.location_id?.toString() || '', calendar_id: initialCalendarId, - is_starred: event?.is_starred || false, + is_starred: source?.is_starred || false, }); - const existingRule = parseRecurrenceRule(event?.recurrence_rule); + const existingRule = parseRecurrenceRule(source?.recurrence_rule); const [recurrenceType, setRecurrenceType] = useState(existingRule?.type || ''); const [recurrenceInterval, setRecurrenceInterval] = useState(existingRule?.interval || 2); const [recurrenceWeekday, setRecurrenceWeekday] = useState(existingRule?.weekday ?? 1); @@ -132,7 +137,7 @@ export default function EventForm({ event, initialStart, initialEnd, initialAllD }); // Location picker state - const existingLocation = locations.find((l) => l.id === event?.location_id); + const existingLocation = locations.find((l) => l.id === source?.location_id); const [locationSearch, setLocationSearch] = useState(existingLocation?.name || ''); const selectableCalendars = calendars.filter((c) => !c.is_system); @@ -176,11 +181,11 @@ export default function EventForm({ event, initialStart, initialEnd, initialAllD recurrence_rule: rule, }; - if (event?.id) { + if (isEditing) { if (editScope) { payload.edit_scope = editScope; } - const response = await api.put(`/events/${event.id}`, payload); + const response = await api.put(`/events/${event!.id}`, payload); return response.data; } else { const response = await api.post('/events', payload); @@ -191,11 +196,11 @@ export default function EventForm({ event, initialStart, initialEnd, initialAllD queryClient.invalidateQueries({ queryKey: ['calendar-events'] }); queryClient.invalidateQueries({ queryKey: ['dashboard'] }); queryClient.invalidateQueries({ queryKey: ['upcoming'] }); - toast.success(event ? 'Event updated' : 'Event created'); + toast.success(isEditing ? 'Event updated' : 'Event created'); onClose(); }, onError: (error) => { - toast.error(getErrorMessage(error, event ? 'Failed to update event' : 'Failed to create event')); + toast.error(getErrorMessage(error, isEditing ? 'Failed to update event' : 'Failed to create event')); }, }); @@ -226,7 +231,13 @@ export default function EventForm({ event, initialStart, initialEnd, initialAllD - {event ? 'Edit Event' : 'New Event'} + + {isEditing + ? 'Edit Event' + : templateName + ? `Create Event from ${templateName} Template` + : 'New Event'} +
@@ -429,7 +440,7 @@ export default function EventForm({ event, initialStart, initialEnd, initialAllD
- {event && ( + {isEditing && (
diff --git a/frontend/src/components/ui/location-picker.tsx b/frontend/src/components/ui/location-picker.tsx index 073e17a..1f94ede 100644 --- a/frontend/src/components/ui/location-picker.tsx +++ b/frontend/src/components/ui/location-picker.tsx @@ -25,8 +25,15 @@ export default function LocationPicker({ value, onChange, onSelect, placeholder const debounceRef = useRef>(); const requestIdRef = useRef(0); const containerRef = useRef(null); + const hasMountedRef = useRef(false); useEffect(() => { + // Skip the initial mount to prevent auto-opening the dropdown + if (!hasMountedRef.current) { + hasMountedRef.current = true; + return; + } + if (debounceRef.current) clearTimeout(debounceRef.current); if (!value || value.length < 2) {