import React, {useState, useEffect, useMemo, useRef} from "react"; import { useNavigate } from "react-router-dom"; import { AuthService, EventsService, CustomerService, ResourceService } from "../../services"; import moment from 'moment'; import { Breadcrumb, Tabs, Tab, Button, Modal, Dropdown, ProgressBar } from "react-bootstrap"; import { useCalendarApp, ScheduleXCalendar } from '@schedule-x/react'; import { viewMonthGrid, createViewDay, createViewWeek, createViewMonthGrid } from '@schedule-x/calendar'; import { createEventsServicePlugin } from '@schedule-x/events-service'; import { createEventModalPlugin} from '@schedule-x/event-modal'; import '@schedule-x/theme-default/dist/calendar.css'; import { Archive, Filter, PencilSquare, X } from "react-bootstrap-icons"; import DatePicker from "react-datepicker"; const EventsCalendar = () => { const navigate = useNavigate(); const [events, setEvents] = useState([]); const calendarColumnRef = useRef(null); const [listHeight, setListHeight] = useState(null); const [allEvents, setAllEvents] = useState([]); const [customers, setCustomers] = useState([]); const [resources, setResources] = useState([]); const [fromDate, setFromDate] = useState(new Date(new Date().getFullYear(), new Date().getMonth(), 1)); const [toDate, setToDate] = useState(new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0)); const [selectedColorFilters, setSelectedColorFilters] = useState([]); const [timeData, setTimeData] = useState([]); const [showFilterDropdown, setShowFilterDropdown] = useState(false); const eventsServicePluginRef = useRef(null); if (!eventsServicePluginRef.current) { eventsServicePluginRef.current = createEventsServicePlugin(); } const eventsServicePlugin = eventsServicePluginRef.current; const eventModalServiceRef = useRef(null); if (!eventModalServiceRef.current) { eventModalServiceRef.current = createEventModalPlugin(); } const eventModalService = eventModalServiceRef.current; const [groupedEvents, setGroupedEvents] = useState(new Map()); const [currentRangeStart, setCurrentRangeStart] = useState(null); const [currentRangeEnd, setCurrentRangeEnd] = useState(null); const [showCreationModal, setShowCreationModal] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [deleteTargetId, setDeleteTargetId] = useState(null); const [showLoadingModal, setShowLoadingModal] = useState(true); const [loadingProgress, setLoadingProgress] = useState(12); const [initialLookupsLoaded, setInitialLookupsLoaded] = useState(false); const [initialEventsLoaded, setInitialEventsLoaded] = useState(false); const hasMarkedInitialEventsLoadedRef = useRef(false); const eventsFetchRequestIdRef = useRef(0); const [newEventStartDateTime, setNewEventStartDateTime] = useState(new Date()); const [newEventEndDateTime, setNewEventEndDateTime] = useState(new Date()); const [newEventColor, setNewEventColor] = useState(''); // Medical appointment specific fields const [newEventCustomer, setNewEventCustomer] = useState(''); const [newEventResource, setNewEventResource] = useState(''); const [newEventInterpreter, setNewEventInterpreter] = useState(''); const [newEventFasting, setNewEventFasting] = useState(''); const [newEventNeedId, setNewEventNeedId] = useState(''); const [newEventNewPatient, setNewEventNewPatient] = useState(''); const [newEventDisability, setNewEventDisability] = useState(''); const [newEventTransMethod, setNewEventTransMethod] = useState(''); // Helper function to format name from "lastname, firstname" to "firstname lastname" const formatFullName = (name) => { if (!name) return ''; if (name.includes(',')) { const parts = name.split(',').map(part => part.trim()); return `${parts[1]} ${parts[0]}`; // firstname lastname } return name; }; // Helper function to format event title - now uses full name, CSS handles overflow const formatEventTitle = (customerName, startTime) => { const fullName = formatFullName(customerName); return fullName; }; // Get full name for description/tooltip const getEventDescription = (customerName, doctorName) => { const fullName = formatFullName(customerName); return doctorName ? `${fullName} - ${doctorName}` : fullName; }; const customerById = useMemo(() => { const map = new Map(); (customers || []).forEach((customer) => { if (customer?.id) map.set(customer.id, customer); }); return map; }, [customers]); const resourceById = useMemo(() => { const map = new Map(); (resources || []).forEach((resource) => { if (resource?.id) map.set(resource.id, resource); }); return map; }, [resources]); const calendar = useCalendarApp({ views: [createViewMonthGrid(), createViewDay(), createViewWeek()], monthGridOptions: { nEventsPerDay: 50, }, defaultView: viewMonthGrid.name, skipValidation: true, selectedDate: moment(new Date()).format('YYYY-MM-DD HH:mm'), events: events, plugins: [eventModalService, eventsServicePlugin], callbacks: { onRangeUpdate(range) { setCurrentRangeStart(range.start); setCurrentRangeEnd(range.end); const startDate = new Date(range.start); const endDate = new Date(range.end); const nextFromDate = new Date(startDate.getFullYear(), startDate.getMonth(), 1); const nextToDate = new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0); setFromDate((prev) => (prev?.getTime?.() === nextFromDate.getTime() ? prev : nextFromDate)); setToDate((prev) => (prev?.getTime?.() === nextToDate.getTime() ? prev : nextToDate)); }, onClickDate(date) { // Disabled: prevent creating new event by tapping calendar date return; }, onClickDateTime(dateTime) { // Disabled: prevent creating new event by tapping calendar time slot return; } } }); // Filter events based on current calendar range const getFilteredEvents = () => { if (!currentRangeStart || !currentRangeEnd) { const now = moment(); return events.filter(event => { const eventDate = moment(event.start_time); return eventDate.isSame(now, 'month'); }); } const rangeStart = moment(currentRangeStart); const rangeEnd = moment(currentRangeEnd); return events.filter(event => { const eventDate = moment(event.start_time); return eventDate.isBetween(rangeStart, rangeEnd, 'day', '[]'); }); }; const getGroupedEvents = () => { const eventsDateMap = new Map(); const filteredEvents = getFilteredEvents(); for (const eventItem of filteredEvents) { const dateString = moment(eventItem.start_time).format('MMM Do, YYYY'); if (eventsDateMap.has(dateString)) { eventsDateMap.set(dateString, [...eventsDateMap.get(dateString), eventItem]); } else { const value = []; value.push(eventItem); eventsDateMap.set(dateString, value); } } // Sort each day by earliest start time first eventsDateMap.forEach((items, key) => { const sortedItems = [...items].sort((a, b) => { const aTime = moment(a?.start_time).valueOf(); const bTime = moment(b?.start_time).valueOf(); return aTime - bTime; }); eventsDateMap.set(key, sortedItems); }); return eventsDateMap; }; useEffect(() => { if (!AuthService.canViewMedicalEvents()) { window.alert('You haven\'t login yet OR this user does not have access to this page. Please change an admin account to login.') AuthService.logout(); navigate(`/login`); } setShowLoadingModal(true); setLoadingProgress(12); setInitialLookupsLoaded(false); setInitialEventsLoaded(false); Promise.all([ CustomerService.getAllActiveCustomers(), ResourceService.getAll(), EventsService.getTimeData() ]).then(([customersData, resourcesData, timeDataResult]) => { setCustomers(customersData?.data || []); setResources(resourcesData?.data || []); setTimeData(timeDataResult?.data || []); }).finally(() => { setInitialLookupsLoaded(true); }); // Hide deleted appointments by default on page load. setSelectedColorFilters([]); }, []); useEffect(() => { const requestId = ++eventsFetchRequestIdRef.current; const timer = setTimeout(() => { EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then(data => { if (requestId !== eventsFetchRequestIdRef.current) return; setAllEvents(data?.data || []); if (!hasMarkedInitialEventsLoadedRef.current) { hasMarkedInitialEventsLoadedRef.current = true; setInitialEventsLoaded(true); } }).catch(() => { if (requestId !== eventsFetchRequestIdRef.current) return; setAllEvents([]); if (!hasMarkedInitialEventsLoadedRef.current) { hasMarkedInitialEventsLoadedRef.current = true; setInitialEventsLoaded(true); } }); }, 180); return () => clearTimeout(timer); }, [fromDate, toDate]); useEffect(() => { if (!showLoadingModal) return; const timer = setInterval(() => { setLoadingProgress((prev) => { if (prev >= 90) return prev; return prev + Math.max(1, Math.ceil((90 - prev) / 8)); }); }, 220); return () => clearInterval(timer); }, [showLoadingModal]); useEffect(() => { if (!initialLookupsLoaded || !initialEventsLoaded) return; setLoadingProgress(100); const closeTimer = setTimeout(() => { setShowLoadingModal(false); }, 250); return () => clearTimeout(closeTimer); }, [initialLookupsLoaded, initialEventsLoaded]); useEffect(() => { if (customers?.length > 0 && resources.length > 0) { const orignialEvents = [...allEvents]; setEvents(orignialEvents?.filter(item => item.type === 'medical')?.map((item) => { const customer = item?.data?.customer ? customerById.get(item?.data?.customer) : null; const resource = item?.data?.resource ? resourceById.get(item?.data?.resource) : null; const customerField = customer?.name || item?.data?.client_name || ''; const doctorField = resource?.name || item?.data?.resource_name || ''; item.event_id = item.id; item.customer = customerField; item.doctor = doctorField; item.phone = resource?.phone || item?.data?.resource_phone || ''; item.contact = resource?.contact || item?.data?.resource_contact || ''; item.address = resource?.address || item?.data?.resource_address || ''; item.translation = item?.data?.interpreter || ''; item.newPatient = item?.data?.new_patient || ''; item.needId = item?.data?.need_id || ''; item.disability = item?.data?.disability || ''; item.startTime = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : '' ; item.endTime = item?.start_time? `${moment(new Date(item?.end_time)).format('YYYY-MM-DD HH:mm')}` : '' ; item.fasting = item?.data?.fasting || ''; item.transportation = item?.link_event_name || ''; item.title = formatEventTitle(customerField, item?.start_time); item.description = getEventDescription(customerField, doctorField); item.start = item?.start_time? `${moment(new Date(item?.start_time)).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`; item.end = item?.stop_time? `${moment(new Date(item?.stop_time)).format('YYYY-MM-DD HH:mm')}` : (item?.start_time? `${moment(item?.start_time).format('YYYY-MM-DD HH:mm')}` : `${moment().format('YYYY-MM-DD HH:mm')}`); const transportationInfo = EventsService.getTransportationInfo(allEvents, item, timeData); const { isFutureEvent, maxTranslate1, maxTranslate2, maxResource, totalTranslate1, totalTranslate2, totalResource} = transportationInfo; item.color = item?.color; item._options = { additionalClasses: [`event-${item?.color || 'primary'}`]}; item.showWarnings = isFutureEvent; item.maxTranslate1 = maxTranslate1; item.maxTranslate2 = maxTranslate2; item.maxResource = maxResource; item.totalTranslate1 = totalTranslate1; item.totalTranslate2 = totalTranslate2; item.totalResource = totalResource; return item; })?.filter(item => { const includeDeleted = selectedColorFilters.includes('gray'); const isDeletedAppt = item?.status !== 'active' || item?.color === 'gray'; if (!isDeletedAppt) return true; return includeDeleted; }) ?.filter(item => { if (selectedColorFilters.length === 0) return true; if (item.status !== 'active') { return selectedColorFilters.includes('gray'); } if (selectedColorFilters.includes(item.color)) return true; // When "Drop-Off Only" (purple) is selected, also show events with no label if (selectedColorFilters.includes('purple') && !item.color) return true; return false; })); } }, [customers, resources, timeData, allEvents, selectedColorFilters, customerById, resourceById]) useEffect(() => { if (events && calendar) { calendar?.eventsService?.set(events); setGroupedEvents(getGroupedEvents()); } }, [events, currentRangeStart, currentRangeEnd]); // Fallback close handler: ensure ScheduleX modal can always be dismissed via X button. useEffect(() => { const onDocumentClickCapture = (event) => { const target = event.target; if (!(target instanceof HTMLElement)) return; if (!target.closest('.sx__event-modal')) return; const closeControl = target.closest('button,[role="button"]'); if (!closeControl) return; const closeAriaLabel = `${closeControl.getAttribute('aria-label') || ''}`.toLowerCase(); const closeText = `${closeControl.textContent || ''}`.trim().toLowerCase(); const isCloseControl = closeAriaLabel.includes('close') || closeText === '\u00d7' || closeText === 'x' || closeText === '\u2715'; if (!isCloseControl) return; event.preventDefault(); event.stopPropagation(); eventModalService?.close?.(); try { calendar?.config?.plugins?.eventModal?.close(); } catch (_) { // no-op fallback } setTimeout(() => { document.querySelectorAll('.sx__event-modal').forEach(el => el.remove()); }, 0); }; document.addEventListener('click', onDocumentClickCapture, true); return () => { document.removeEventListener('click', onDocumentClickCapture, true); }; }, [calendar, eventModalService]); // Sync list column height with calendar column height useEffect(() => { const updateListHeight = () => { if (calendarColumnRef.current) { const calendarHeight = calendarColumnRef.current.offsetHeight; setListHeight(calendarHeight); } }; const timer = setTimeout(updateListHeight, 100); window.addEventListener('resize', updateListHeight); return () => { clearTimeout(timer); window.removeEventListener('resize', updateListHeight); }; }, [events]); const redirectToAdmin = () => { navigate(`/medical`) } const goToCreateNew = () => { navigate(`/medical/events`) } const goToList = () => { navigate(`/medical/events/list`) } const goToMultipleList = () => { navigate(`/medical/events/multiple-list`) } const goToView = (id) => { navigate(`/medical/events/${id}`) } const goToEdit = (id) => { if (!AuthService.canEditMedicalEvents()) return; navigate(`/medical/events/edit/${id}?from=calendar`) } const disableEvent = (id) => { if (!AuthService.canEditMedicalEvents()) return; const currentEvent = events.find(item => item.id === id); EventsService.disableEvent(id, { status: 'inactive', edit_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, edit_date: new Date(), edit_history: currentEvent?.edit_history ? [...currentEvent.edit_history, { employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }] : [{ employee: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }] }).then(() => { EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => { setAllEvents(data?.data); }) }); } const toggleColorFilter = (colorValue) => { setSelectedColorFilters(prev => prev.includes(colorValue) ? prev.filter(c => c !== colorValue) : [...prev, colorValue] ); }; const FilterAndClose = () => { setShowFilterDropdown(false); } const cleanFilterAndClose = () => { setShowFilterDropdown(false); setSelectedColorFilters([]); } const dismissEventModal = (e) => { e?.preventDefault?.(); e?.stopPropagation?.(); e?.nativeEvent?.stopImmediatePropagation?.(); try { eventModalService?.close?.(); } catch (_e) {} try { calendar?.config?.plugins?.eventModal?.close(); } catch (_e) {} setTimeout(() => { document.querySelectorAll('.sx__event-modal').forEach((el) => { const dialogContainer = el.closest('[role="dialog"]'); if (dialogContainer) { dialogContainer.remove(); } else { el.remove(); } }); }, 0); }; const customComponents = { eventModal: ({calendarEvent}) => { return <>
{calendarEvent?.customer}
{calendarEvent?.doctor &&
{`${calendarEvent?.doctor}`}
}
{`${calendarEvent?.start}`}
{AuthService.canEditMedicalEvents() &&
goToEdit(calendarEvent?.id)} style={{ cursor: 'pointer' }} title="Edit" /> { setDeleteTargetId(calendarEvent?.id); setShowDeleteConfirm(true); }} style={{ cursor: 'pointer' }} title="Delete" />
} } }; const customMenu = React.forwardRef( ({ children, style, className, 'aria-labelledby': labeledBy }, ref) => { return (
Filter By

Filter by Type
{EventsService.labelOptions.map((item) => (
toggleColorFilter(item.value)}> toggleColorFilter(item.value)} style={{ marginRight: '8px', marginLeft: '0' }} onClick={(e) => e.stopPropagation()} /> {item.label}
))} {selectedColorFilters.length > 0 && (
setSelectedColorFilters([])} > Clear all
)}
); }, ); const calendarView =
{calendar && } {/* Legend */}
Legend:
{EventsService.labelOptions?.map((item) => (
{item.label}
))}
List
{(!groupedEvents || groupedEvents.size === 0) && (
No events for this period
)} { Array.from(groupedEvents?.keys())?.map((key) => { return
{key}
{ groupedEvents.get(key).map(eventItem =>
goToView(eventItem.id)} style={{ cursor: 'pointer', padding: '8px 12px', borderRadius: '4px' }} >
{formatFullName(eventItem.customer)} {moment(eventItem?.start_time).format('HH:mm')} - {moment(eventItem?.stop_time || eventItem?.start_time).format('HH:mm')}
Provider: {eventItem?.doctor || '-'}
{AuthService.canEditMedicalEvents() &&
e.stopPropagation()}> goToEdit(eventItem?.id)} style={{ cursor: 'pointer' }} title="Edit" /> { setDeleteTargetId(eventItem?.id); setShowDeleteConfirm(true); }} style={{ cursor: 'pointer' }} title="Delete" />
}
) }
}) }
const handleClose = () => { setShowCreationModal(false); setNewEventStartDateTime(undefined); setNewEventEndDateTime(undefined); setNewEventColor(''); setNewEventCustomer(''); setNewEventResource(''); setNewEventInterpreter(''); setNewEventFasting(''); setNewEventNeedId(''); setNewEventNewPatient(''); setNewEventDisability(''); setNewEventTransMethod(''); } const handleSave = () => { if (!AuthService.canEditMedicalEvents()) return; const userName = localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name; const selectedCustomer = customers.find(c => c.id === newEventCustomer); const selectedResource = resources.find(r => r.id === newEventResource); const data = { type: 'medical', status: 'active', create_by: userName, edit_by: userName, edit_date: new Date(), create_date: new Date(), edit_history: [{ employee: userName, date: new Date() }], title: selectedCustomer ? `${selectedCustomer.name} - Medical Appointment` : 'Medical Appointment', start_time: newEventStartDateTime, stop_time: newEventStartDateTime, color: newEventColor, confirmed: true, data: { customer: newEventCustomer, client_name: selectedCustomer?.name || '', resource: newEventResource, resource_name: selectedResource?.name || '', resource_phone: selectedResource?.phone || '', resource_address: selectedResource?.address || '', interpreter: newEventInterpreter, fasting: newEventFasting, need_id: newEventNeedId, new_patient: newEventNewPatient, disability: newEventDisability, trans_method: newEventTransMethod, } }; EventsService.createNewEvent(data).then(() => { EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => { setAllEvents(data.data); handleClose(); }) }); } return ( <> Loading Medical Event Calendar
Fetching and rendering calendar data...
Medical Medical Event Calendar

Medical Event Calendar

{ calendarView }
setShowFilterDropdown(!showFilterDropdown)} autoClose={false} > Filter
New Medical Appointment
Customer*
Provider*
Appointment Time*
Language Support
Transportation Support
Label
{ setShowDeleteConfirm(false); setDeleteTargetId(null); }} size="sm" centered> Confirm Delete Are you sure you want to delete this event?
) }; export default EventsCalendar;