import React, {useState, useEffect, 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 } 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 } 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 [currentTotalTranslate1, setCurrentTotalTranslate1] = useState(0); const [currentTotalTranslate2, setCurrentTotalTranslate2] = useState(0); const [currentTotalResource, setCurrentTotalResource] = useState(0); const [showDeletedItems, setShowDeletedItems] = useState(false); const [selectedColorFilters, setSelectedColorFilters] = useState([]); const [timeData, setTimeData] = useState([]); const [showFilterDropdown, setShowFilterDropdown] = useState(false); const eventsServicePlugin = createEventsServicePlugin(); const eventModalService = createEventModalPlugin(); 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 [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 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); setFromDate(new Date(startDate.getFullYear(), startDate.getMonth(), 1)); setToDate(new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0)); }, 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`); } CustomerService.getAllActiveCustomers().then((data) => { setCustomers(data.data); }); ResourceService.getAll().then((data) => { setResources(data.data); }); EventsService.getTimeData().then(data => { setTimeData(data.data); }); }, []); useEffect(() => { EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then(data => setAllEvents(data?.data)); }, [fromDate, toDate]); useEffect(() => { if (customers?.length > 0 && resources.length > 0) { const orignialEvents = [...allEvents]; setEvents(orignialEvents?.filter(item => item.type === 'medical')?.map((item) => { const customerField = item?.data?.customer ? (customers?.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || ''); const doctorField = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || ''); item.event_id = item.id; item.customer = customerField; item.doctor = doctorField; item.phone = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || ''); item.contact = item?.data?.resource? ((resources?.find(r => r.id === item?.data?.resource))?.contact || item?.data?.resource_contact || '') : (item?.data?.resource_contact || '') item.address = item?.data?.resource ? ((resources?.find(r => r.id === item?.data?.resource))?.address || item?.data?.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; setCurrentTotalTranslate1(item.totalTranslate1); item.totalTranslate2 = totalTranslate2; setCurrentTotalTranslate2(item.totalTranslate2); item.totalResource = totalResource; setCurrentTotalResource(item.totalResource); return item; })?.filter(item => (!showDeletedItems && item.status === 'active') || showDeletedItems) ?.filter(item => { if (selectedColorFilters.length === 0) return true; 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, showDeletedItems, selectedColorFilters]) useEffect(() => { if (events && calendar) { calendar?.eventsService?.set(events); setGroupedEvents(getGroupedEvents()); } }, [events, currentRangeStart, currentRangeEnd]); // 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); setShowDeletedItems(false); setSelectedColorFilters([]); } 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
Show Deleted Events
setShowDeletedItems(!showDeletedItems)} />

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 ( <>
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;