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 <noreply@anthropic.com>
This commit is contained in:
parent
b21343601b
commit
f64e181fbe
@ -43,6 +43,7 @@ export default function CalendarPage() {
|
||||
const [calendarTitle, setCalendarTitle] = useState('');
|
||||
|
||||
const [templateEvent, setTemplateEvent] = useState<Partial<CalendarEvent> | null>(null);
|
||||
const [templateName, setTemplateName] = useState<string | null>(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<CalendarEvent>);
|
||||
setTemplateName(template.name);
|
||||
setEditingEvent(null);
|
||||
setShowForm(true);
|
||||
};
|
||||
@ -385,7 +388,9 @@ export default function CalendarPage() {
|
||||
|
||||
{showForm && (
|
||||
<EventForm
|
||||
event={editingEvent || (templateEvent as CalendarEvent | null)}
|
||||
event={editingEvent}
|
||||
templateData={templateEvent}
|
||||
templateName={templateName}
|
||||
initialStart={selectedStart}
|
||||
initialEnd={selectedEnd}
|
||||
initialAllDay={selectedAllDay}
|
||||
|
||||
@ -22,6 +22,8 @@ import LocationPicker from '@/components/ui/location-picker';
|
||||
|
||||
interface EventFormProps {
|
||||
event: CalendarEvent | null;
|
||||
templateData?: Partial<CalendarEvent> | 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<string>(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
|
||||
<SheetContent>
|
||||
<SheetClose onClick={onClose} />
|
||||
<SheetHeader>
|
||||
<SheetTitle>{event ? 'Edit Event' : 'New Event'}</SheetTitle>
|
||||
<SheetTitle>
|
||||
{isEditing
|
||||
? 'Edit Event'
|
||||
: templateName
|
||||
? `Create Event from ${templateName} Template`
|
||||
: 'New Event'}
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<form onSubmit={handleSubmit} className="flex flex-col flex-1 overflow-y-auto">
|
||||
<div className="px-6 py-5 space-y-4 flex-1">
|
||||
@ -429,7 +440,7 @@ export default function EventForm({ event, initialStart, initialEnd, initialAllD
|
||||
</div>
|
||||
|
||||
<SheetFooter>
|
||||
{event && (
|
||||
{isEditing && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
@ -444,7 +455,7 @@ export default function EventForm({ event, initialStart, initialEnd, initialAllD
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={mutation.isPending}>
|
||||
{mutation.isPending ? 'Saving...' : event ? 'Update' : 'Create'}
|
||||
{mutation.isPending ? 'Saving...' : isEditing ? 'Update' : 'Create'}
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
</form>
|
||||
|
||||
@ -25,8 +25,15 @@ export default function LocationPicker({ value, onChange, onSelect, placeholder
|
||||
const debounceRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
const requestIdRef = useRef(0);
|
||||
const containerRef = useRef<HTMLDivElement>(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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user