import React, {useState, useEffect} from "react"; import { useNavigate } from "react-router-dom"; import { AuthService, EventsService, CustomerService, DriverService, ResourceService } from "../../services"; import DatePicker from "react-datepicker"; import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Button, Modal } from "react-bootstrap"; import { Columns, Download, Filter, PencilSquare, PersonSquare, Plus } from "react-bootstrap-icons"; import TimePicker from 'react-time-picker'; import 'react-time-picker/dist/TimePicker.css'; import { ManageTable, Export } from "../../shared/components"; const EventsList = () => { const navigate = useNavigate(); const [events, setEvents] = useState([]); const [customers, setCustomers] = useState([]); const [resources, setResources] = useState([]); const [selectedDate, setSelectedDate] = useState(new Date()); // Multi-column sorting: array of {key, order} objects // First item has highest priority, subsequent items are secondary sorts const [sortingRules, setSortingRules] = useState([]); const [selectedItems, setSelectedItems] = useState([]); const [showTransportationModal, setShowTransportationModal] = useState(false); const [driver, setDriver] = useState(null); const [driverOptions, setDriverOptions] = useState([]); const [transportStartTime, setTransportStartTime] = useState(null); const [transportOptionsList, setTransportationOptionsList] = useState([]); const [transportSelected, setTransportSelected] = useState(null); const [showDeletedItems, setShowDeletedItems] = useState(false); const [columns, setColumns] = useState([ { key: 'customer', label:'Customer Name', show: true }, { key: 'member_type', label: 'Customer Type', show: true }, { key: 'translation', label: 'Language Support', show: true }, { key: 'transMethod', label: 'Transportation Support', show: true }, { key: 'transportation', label: 'Driver', show: true }, { key: 'startTime', label: 'Appointment Time', show: true }, { key: 'eyes_on', label: 'Eyes-On', show: true }, { key: 'newPatient', label: 'New Patient', show: true }, { key: 'needId', label: 'ID Needed', show: true }, { key: 'doctor', label: 'Provider', show: true }, { key: 'phone', label: 'Provider Phone Number', show: true }, { key: 'address', label: 'Provider Address', show: true }, { key: 'fasting', label: 'Fasting Required', show: true } ]); const checkDisability = (customers, event) => { const currentCustomer = customers?.find(c => c?.id === event?.data?.customer || c?.name === event?.data?.client_name || c?.name === event?.target_name); return currentCustomer?.disability || event?.data?.disability?.toLowerCase() === 'yes' || false; }; // Default sort: 1) driver name ascending, 2) start time early to late, 3) address ascending, 4) language (empty first) const applyDefaultSort = (eventsArray) => { return [...eventsArray].sort((a, b) => { // 1. First sort by driver (transportation) name ascending const driverA = (a.transportation || '').toLowerCase(); const driverB = (b.transportation || '').toLowerCase(); if (driverA !== driverB) { return driverA.localeCompare(driverB); } // 2. Then sort by start time (early to late) const timeA = a.start_time ? new Date(a.start_time).getTime() : 0; const timeB = b.start_time ? new Date(b.start_time).getTime() : 0; if (timeA !== timeB) { return timeA - timeB; } // 3. Then sort by resource address ascending const addressA = (a.address || '').toLowerCase(); const addressB = (b.address || '').toLowerCase(); if (addressA !== addressB) { return addressA.localeCompare(addressB); } // 4. Finally sort by language (empty values first, then values with content) const langA = (a.translation || '').trim(); const langB = (b.translation || '').trim(); if (langA === '' && langB !== '') return -1; // Empty comes first if (langA !== '' && langB === '') return 1; // Empty comes first return langA.localeCompare(langB); // Both empty or both have values - sort alphabetically }); }; 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.getAllCustomers().then((data) => { setCustomers(data.data); }) DriverService.getAllActiveDrivers().then((data) => { console.log('drivers', data.data); setDriverOptions(data.data); }) ResourceService.getAll().then(data => { setResources(data.data); }) }, []); useEffect(() => { if (customers?.length > 0 && resources?.length>0) { EventsService.getAllEvents({ date: EventsService.formatDate(selectedDate) }).then((data) => { const processedEvents = data.data.filter((item) => { item.customer = item?.data?.customer ? (customers.find(c=>c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || ''); item.doctor = item?.data?.resource ? (resources.find(r=> r.id === item?.data?.resource)?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || ''); item.phone = item?.data?.resource ? (resources.find(r=> r.id === item?.data?.resource)?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || ''); 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.member_type = item?.data?.customer ? (customers.find(c => c.id === item?.data?.customer)?.type || '') : ''; item.eyes_on = checkDisability(customers, item) ? 'Yes' : 'No'; item.startTime = item?.start_time? `${new Date(item?.start_time).toLocaleDateString()} ${new Date(item?.start_time).toLocaleTimeString()}` : '' ; item.fasting = item?.data?.fasting || ''; item.chinese_name = item?.data?.customer ? customers.find(c => c.id === item?.data?.customer)?.name_cn : (customers?.find(c=> c?.name === item?.data?.client_name || c?.name === item?.target_name )?.name_cn || ''); item.transportation = item?.link_event_name || ''; item.dob = item?.data?.customer ? customers.find(c => c.id === item?.data?.customer)?.birth_date : (item?.data?.client_birth_date || ''); item.transMethod = item?.data?.trans_method; return item; }).filter(item => item.type === 'medical' && item.confirmed); // Apply default sort (driver name → start time → address) const sortedEvents = applyDefaultSort(processedEvents); setEvents(sortedEvents); setTransportationOptionsList(data.data.filter((item) => item.type === 'transportation' && item.status === 'active')) }) } }, [selectedDate, resources, customers]); // Apply multi-column sorting // After all custom sorting rules, always sort by language (empty first) const applyMultiColumnSort = (eventsArray, rules) => { if (rules.length === 0) { return applyDefaultSort(eventsArray); } return [...eventsArray].sort((a, b) => { // First apply all custom sorting rules for (const rule of rules) { const valA = (a[rule.key] || '').toString().toLowerCase(); const valB = (b[rule.key] || '').toString().toLowerCase(); const comparison = valA.localeCompare(valB); if (comparison !== 0) { return rule.order === 'desc' ? -comparison : comparison; } } // After all rules, always sort by language (empty values first, then values with content) const langA = (a.translation || '').trim(); const langB = (b.translation || '').trim(); if (langA === '' && langB !== '') return -1; // Empty comes first if (langA !== '' && langB === '') return 1; // Empty comes first return langA.localeCompare(langB); // Both empty or both have values - sort alphabetically }); }; useEffect(() => { if (events.length === 0) return; const sortedEvents = applyMultiColumnSort(events, sortingRules); setEvents(sortedEvents); }, [sortingRules]); const redirectToAdmin = () => { navigate(`/medical`) } const goToEdit = (id) => { if (!AuthService.canEditMedicalEvents()) return; navigate(`/medical/events/edit/${id}`) } const goToCreateNew = () => { if (!AuthService.canEditMedicalEvents()) return; navigate(`/medical/events`) } const goToEventsCalendar = () => { navigate(`/medical/events/calendar`) } const goToMultipleList = () => { navigate(`/medical/events/multiple-list`) } const goToResourceList = () => { navigate(`/medical/resources/list`) } const goToView = (id) => { navigate(`/medical/events/${id}`) } const goToNextDay = () => { setSelectedDate(new Date(selectedDate.setDate(selectedDate.getDate() + 1))); } const goToPreviousDay = () => { setSelectedDate(new Date(selectedDate.setDate(selectedDate.getDate() - 1))); } const showDeleted = (value) => { setShowDeletedItems(value === 'archivedEvents'); // Recover all filters // setKeyword(''); setSortingRules([]); setSelectedItems([]); } // Get the sort indicator for a column (shows priority number if multi-column) const getSortingImg = (key) => { const ruleIndex = sortingRules.findIndex(rule => rule.key === key); if (ruleIndex === -1) { return 'default'; } return sortingRules[ruleIndex].order === 'asc' ? 'up_arrow' : 'down_arrow'; } // Get the sort priority number for display (1, 2, 3, etc.) const getSortPriority = (key) => { const ruleIndex = sortingRules.findIndex(rule => rule.key === key); return ruleIndex === -1 ? null : ruleIndex + 1; } // Multi-column sort: clicking a column adds it to sort rules or toggles its order // Behavior: asc → desc → remove from sorting const sortTableWithField = (key) => { const existingIndex = sortingRules.findIndex(rule => rule.key === key); if (existingIndex === -1) { // Column not in sort rules - add it with 'asc' order setSortingRules([...sortingRules, { key, order: 'asc' }]); } else { const existingRule = sortingRules[existingIndex]; if (existingRule.order === 'asc') { // Toggle to desc const newRules = [...sortingRules]; newRules[existingIndex] = { key, order: 'desc' }; setSortingRules(newRules); } else { // Already desc - remove from sorting rules const newRules = sortingRules.filter((_, index) => index !== existingIndex); setSortingRules(newRules); } } } const toggleSelectedAllItems = () => { if (selectedItems.length !== events.length || selectedItems.length === 0) { const newSelectedItems = [...events].map((event) => event.id); setSelectedItems(newSelectedItems); } else { setSelectedItems([]); } } const toggleItem = (id) => { if (selectedItems.includes(id)) { const newSelectedItems = [...selectedItems].filter((item) => item !== id); setSelectedItems(newSelectedItems); } else { const newSelectedItems = [...selectedItems, id]; setSelectedItems(newSelectedItems); } } const checkSelectAll = () => { return selectedItems.length === events.length && selectedItems.length > 0; } const disableAssignTransportationButton = () => { return (!transportSelected || transportSelected === '') || ((transportSelected === 'create_new') && (!driver || !transportStartTime || driver === '' || transportStartTime === '')); } const closePanel = () => { setShowTransportationModal(false); setTransportStartTime(null); setDriver(null); setTransportSelected(null); } const assignDriver = () => { // if select create new event, then create a new transportation event first if (transportSelected === 'create_new') { const dateString = new Date().toLocaleDateString(); const startDateTime = new Date(`${dateString} ${transportStartTime}`); const transportationParameter = { title: `${driverOptions.find((item) => item.id === driver)?.name} ${startDateTime.toLocaleTimeString()}`, description: 'transportatoin for med events', type: 'transportation', source_type: 'resource', source_uuid: '', target_type: 'staff', target_uuid: driver, start_time: startDateTime, stop_time: startDateTime, status: 'active', create_by: JSON.parse(localStorage.getItem('user'))?.name, create_date: new Date(), edit_by: JSON.parse(localStorage.getItem('user'))?.name, edit_date: new Date(), edit_history: [{ employee: JSON.parse(localStorage.getItem('user'))?.name, date: new Date() }] }; EventsService.createNewEvent(transportationParameter).then(data => { const trans = data.data; EventsService.assignTransportationToEvents({ transportationId: trans.id, transportationName: trans.title, eventIds: selectedItems }).then(() => { setSelectedItems([]); EventsService.getAllEvents({ date: EventsService.formatDate(selectedDate) }).then((data) => { const results = [...data.data]; const eventsResults = results.filter((item) => { item.customer = item?.data?.customer ? (customers.find(c=>c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || ''); item.doctor = item?.data?.resource ? (resources.find(r=> r.id === item?.data?.resource)?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || ''); item.phone = item?.data?.resource ? (resources.find(r=> r.id === item?.data?.resource)?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || ''); 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.member_type = item?.data?.customer ? (customers.find(c => c.id === item?.data?.customer)?.type || '') : ''; item.eyes_on = checkDisability(customers, item) ? 'Yes' : 'No'; item.startTime = item?.start_time? `${new Date(item?.start_time).toLocaleDateString()} ${new Date(item?.start_time).toLocaleTimeString()}` : '' ; item.fasting = item?.data?.fasting || ''; item.chinese_name = item?.data?.customer ? customers.find(c => c.id === item?.data?.customer)?.name_cn : (customers?.find(c=> c?.name === item?.data?.client_name || c?.name === item?.target_name )?.name_cn || ''); item.transportation = item?.link_event_name || ''; item.dob = item?.data?.customer ? customers.find(c => c.id === item?.data?.customer)?.birth_date : (item?.data?.client_birth_date || ''); item.transMethod = item?.data?.trans_method; return item; }).filter(item => item.type === 'medical' && item.confirmed); // Apply current sorting rules (or default if none) const sortedEvents = applyMultiColumnSort(eventsResults, sortingRules); setEvents(sortedEvents); setTransportationOptionsList(data.data.filter((item) => item.type === 'transportation' && item.status === 'active')); closePanel(); }) }); }).catch((err) => console.log('Transportation Event Creation failed')) } else { if (transportSelected && transportSelected !== '') { EventsService.assignTransportationToEvents({ transportationId: transportSelected, transportationName: transportOptionsList.find((item) => item.id === transportSelected)?.title, eventIds: selectedItems }).then(() => { setSelectedItems([]); EventsService.getAllEvents({ date: EventsService.formatDate(selectedDate) }).then((data) => { const results = [...data.data]; const eventsResults = results.filter((item) => { item.customer = item?.data?.customer ? (customers.find(c=>c.id === item?.data?.customer)?.name || item?.data?.client_name || '') : (item?.data?.client_name || ''); item.doctor = item?.data?.resource ? (resources.find(r=> r.id === item?.data?.resource)?.name || item?.data?.resource_name || '') : (item?.data?.resource_name || ''); item.phone = item?.data?.resource ? (resources.find(r=> r.id === item?.data?.resource)?.phone || item?.data?.resource_phone || '') : (item?.data?.resource_phone || ''); 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.member_type = item?.data?.customer ? (customers.find(c => c.id === item?.data?.customer)?.type || '') : ''; item.eyes_on = checkDisability(customers, item) ? 'Yes' : 'No'; item.startTime = item?.start_time? `${EventsService.formatDate(new Date(item?.start_time))} ${new Date(item?.start_time).toLocaleTimeString('en-US', { hour: '2-digit', minute: 'numeric', hour12: true })}` : '' ; item.fasting = item?.data?.fasting || ''; item.chinese_name = item?.data?.customer ? customers.find(c => c.id === item?.data?.customer)?.name_cn : (customers?.find(c=> c?.name === item?.data?.client_name || c?.name === item?.target_name )?.name_cn || ''); item.transportation = item?.link_event_name || ''; item.dob = item?.data?.customer ? customers.find(c => c.id === item?.data?.customer)?.birth_date : (item?.data?.client_birth_date || ''); item.transMethod = item?.data?.trans_method; return item; }).filter(item => item.type === 'medical' && item.confirmed); // Apply current sorting rules (or default if none) const sortedEvents = applyMultiColumnSort(eventsResults, sortingRules); setEvents(sortedEvents); // setTransportationOptionsList(data.data.filter((item) => item.type === 'transportation' && item.status === 'active')); closePanel(); }) }); } }; }; const table = (statusParam) =>
| toggleSelectedAllItems()}> | No. | { columns.filter(col => col.show).map((column, index) =>
{column.label}
sortTableWithField(column.key)} style={{ cursor: 'pointer' }}>
{getSortPriority(column.key) && {getSortPriority(column.key)}}
| )
}
||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| toggleItem(medicalEvent?.id)}/> | {index + 1} | {columns.find(col => col.key === 'customer')?.show &&{`${medicalEvent?.customer || ''}${medicalEvent?.chinese_name ? ` (${medicalEvent.chinese_name})` : ''}`} | } {columns.find(col => col.key === 'member_type')?.show &&{medicalEvent?.member_type} | } {columns.find(col => col.key === 'translation')?.show &&{medicalEvent?.translation} | } {columns.find(col => col.key === 'transMethod')?.show &&{medicalEvent?.transMethod} | } {columns.find(col => col.key === 'transportation')?.show &&{medicalEvent?.transportation} | } {columns.find(col => col.key === 'startTime')?.show &&{medicalEvent?.startTime} | } {columns.find(col => col.key === 'eyes_on')?.show &&{medicalEvent?.eyes_on} | } {columns.find(col => col.key === 'newPatient')?.show &&{medicalEvent?.newPatient} | } {columns.find(col => col.key === 'fasting')?.show &&{medicalEvent?.fasting} | } {columns.find(col => col.key === 'needId')?.show &&{medicalEvent?.needId} | } {columns.find(col => col.key === 'doctor')?.show &&{medicalEvent?.doctor} | } {columns.find(col => col.key === 'phone')?.show &&{medicalEvent?.phone} | } {columns.find(col => col.key === 'address')?.show &&{medicalEvent?.address} | }