diff --git a/app/epd/agenda/components/agenda-calendar.tsx b/app/epd/agenda/components/agenda-calendar.tsx index 37bc9ae..a9a8d1b 100644 --- a/app/epd/agenda/components/agenda-calendar.tsx +++ b/app/epd/agenda/components/agenda-calendar.tsx @@ -6,7 +6,7 @@ * FullCalendar wrapper with day/week/workweek views. */ -import { useCallback, useRef } from 'react'; +import { useCallback, useRef, useEffect } from 'react'; import FullCalendar from '@fullcalendar/react'; import dayGridPlugin from '@fullcalendar/daygrid'; import timeGridPlugin from '@fullcalendar/timegrid'; @@ -19,6 +19,7 @@ import type { CalendarEvent, CalendarView } from '../types'; interface AgendaCalendarProps { events: CalendarEvent[]; initialView?: CalendarView; + currentView?: CalendarView; onEventClick?: (event: CalendarEvent) => void; onDateSelect?: (start: Date, end: Date) => void; onEventDrop?: (eventId: string, newStart: Date, newEnd: Date | null) => void; @@ -29,6 +30,7 @@ interface AgendaCalendarProps { export function AgendaCalendar({ events, initialView = 'timeGridWeek', + currentView, onEventClick, onDateSelect, onEventDrop, @@ -36,6 +38,22 @@ export function AgendaCalendar({ calendarRef: externalRef, }: AgendaCalendarProps) { const internalRef = useRef(null); + const isChangingViewRef = useRef(false); + + // Sync view when currentView prop changes + useEffect(() => { + if (currentView && internalRef.current) { + const api = internalRef.current.getApi(); + if (api.view.type !== currentView) { + isChangingViewRef.current = true; + api.changeView(currentView); + // Reset flag after a short delay to allow the view change to complete + setTimeout(() => { + isChangingViewRef.current = false; + }, 100); + } + } + }, [currentView]); const handleEventClick = useCallback((info: EventClickArg) => { if (onEventClick) { @@ -70,6 +88,10 @@ export function AgendaCalendar({ }, [onEventDrop]); const handleDatesSet = useCallback((dateInfo: { start: Date; end: Date }) => { + // Skip if we're in the middle of a programmatic view change + if (isChangingViewRef.current) { + return; + } if (onDateChange) { onDateChange(dateInfo.start, dateInfo.end); } diff --git a/app/epd/agenda/components/agenda-view.tsx b/app/epd/agenda/components/agenda-view.tsx index 32f610e..9890439 100644 --- a/app/epd/agenda/components/agenda-view.tsx +++ b/app/epd/agenda/components/agenda-view.tsx @@ -14,6 +14,7 @@ import { AgendaCalendar } from './agenda-calendar'; import { AgendaToolbar } from './agenda-toolbar'; import { AppointmentModal } from './appointment-modal'; import { RescheduleDialog } from './reschedule-dialog'; +import { MiniCalendar } from './mini-calendar'; import { getEncounters, rescheduleEncounter } from '../actions'; import type { CalendarEvent, CalendarView } from '../types'; @@ -190,6 +191,21 @@ export function AgendaView({ initialEvents, initialDate, highlightEncounterId }: setIsModalOpen(true); }, [currentDate]); + // Handle mini calendar date selection + const handleMiniCalendarDateSelect = useCallback((date: Date) => { + setCurrentDate(date); + + // Calculate date range based on current view + const start = currentView === 'timeGridDay' + ? date + : startOfWeek(date, { weekStartsOn: 1 }); + const end = currentView === 'timeGridDay' + ? addDays(date, 1) + : endOfWeek(date, { weekStartsOn: 1 }); + + fetchEvents(start, end); + }, [currentView, fetchEvents]); + // Handle modal success (refresh events) const handleModalSuccess = useCallback(() => { const start = currentView === 'timeGridDay' @@ -211,20 +227,32 @@ export function AgendaView({ initialEvents, initialDate, highlightEncounterId }: onNewAppointment={handleNewAppointment} /> -
- {isPending && ( -
-
-
- )} - +
+ {/* Mini calendar sidebar */} +
+ +
+ + {/* Main calendar */} +
+ {isPending && ( +
+
+
+ )} + +
{/* Appointment Modal */} diff --git a/app/epd/agenda/components/mini-calendar.tsx b/app/epd/agenda/components/mini-calendar.tsx new file mode 100644 index 0000000..d66b97e --- /dev/null +++ b/app/epd/agenda/components/mini-calendar.tsx @@ -0,0 +1,195 @@ +'use client'; + +/** + * Mini Calendar Component + * + * Compact month view calendar for quick date navigation. + * Uses FullCalendar daygrid plugin for consistency with the main calendar. + */ + +import { useCallback, useRef, useEffect } from 'react'; +import FullCalendar from '@fullcalendar/react'; +import dayGridPlugin from '@fullcalendar/daygrid'; +import interactionPlugin from '@fullcalendar/interaction'; +import nlLocale from '@fullcalendar/core/locales/nl'; +import type { DateClickArg } from '@fullcalendar/interaction'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { format } from 'date-fns'; +import { nl } from 'date-fns/locale'; + +interface MiniCalendarProps { + selectedDate: Date; + onDateSelect: (date: Date) => void; +} + +export function MiniCalendar({ selectedDate, onDateSelect }: MiniCalendarProps) { + const calendarRef = useRef(null); + + // Navigate to month containing selected date + useEffect(() => { + if (calendarRef.current) { + const api = calendarRef.current.getApi(); + api.gotoDate(selectedDate); + } + }, [selectedDate]); + + const handleDateClick = useCallback((info: DateClickArg) => { + onDateSelect(info.date); + }, [onDateSelect]); + + const handlePrevMonth = useCallback(() => { + if (calendarRef.current) { + calendarRef.current.getApi().prev(); + } + }, []); + + const handleNextMonth = useCallback(() => { + if (calendarRef.current) { + calendarRef.current.getApi().next(); + } + }, []); + + const handleToday = useCallback(() => { + if (calendarRef.current) { + calendarRef.current.getApi().today(); + } + onDateSelect(new Date()); + }, [onDateSelect]); + + return ( +
+ {/* Custom header with month/year navigation */} +
+ + + +
+ + {/* Mini calendar */} +
+ { + const isSelected = arg.date.toDateString() === selectedDate.toDateString(); + return isSelected ? 'mini-cal-selected' : ''; + }} + /> +
+ + +
+ ); +}