diff --git a/client/src/components/events/EventsList.js b/client/src/components/events/EventsList.js index d9bc9f8..1b00a29 100644 --- a/client/src/components/events/EventsList.js +++ b/client/src/components/events/EventsList.js @@ -103,36 +103,20 @@ const EventsList = () => { return roles.includes('driver') || permissions.includes('isdriver'); }; - // Default sort: 1) driver name ascending, 2) start time early to late, 3) address ascending, 4) language (empty first) + // Default sort: appointment time early to late 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) + // 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 + + // Stable fallback sort by customer name + const customerA = (a.customer || '').toLowerCase(); + const customerB = (b.customer || '').toLowerCase(); + return customerA.localeCompare(customerB); }); }; @@ -180,7 +164,7 @@ const EventsList = () => { return item; }).filter(item => item.type === 'medical' && item.confirmed); - // Apply default sort (driver name → start time → address) + // Apply default sort (appointment time early first) const sortedEvents = applyDefaultSort(processedEvents); setEvents(sortedEvents); setTransportationOptionsList(data.data.filter((item) => item.type === 'transportation' && item.status === 'active')) @@ -191,7 +175,6 @@ const EventsList = () => { // 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); @@ -209,12 +192,10 @@ const EventsList = () => { } } - // 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 + // Stable fallback sort by appointment time ascending + const timeA = a.start_time ? new Date(a.start_time).getTime() : 0; + const timeB = b.start_time ? new Date(b.start_time).getTime() : 0; + return timeA - timeB; }); }; diff --git a/client/src/components/events/EventsMultipleList.js b/client/src/components/events/EventsMultipleList.js index c700daf..931f557 100644 --- a/client/src/components/events/EventsMultipleList.js +++ b/client/src/components/events/EventsMultipleList.js @@ -12,7 +12,7 @@ const EventsMultipleList = () => { const navigate = useNavigate(); const [events, setEvents] = useState([]); const [customers, setCustomers] = useState([]); - const [sorting, setSorting] = useState({key: '', order: ''}); + const [sorting, setSorting] = useState({key: 'appointment_time', order: 'asc'}); const [selectedItems, setSelectedItems] = useState([]); const [resources, setResources] = useState([]); const [fromDate, setFromDate] = useState(new Date()); @@ -170,6 +170,21 @@ const EventsMultipleList = () => { return currentCustomer?.disability || event?.data?.disability?.toLowerCase() === 'yes' || false; }; + const sortEventsByKey = (eventsList = [], key = '', order = 'asc') => { + if (!key) return [...eventsList]; + const sorted = [...eventsList].sort((a, b) => { + if (key === 'appointment_time') { + const aTime = a?.start_time ? new Date(a.start_time).getTime() : 0; + const bTime = b?.start_time ? new Date(b.start_time).getTime() : 0; + return aTime - bTime; + } + const aValue = `${a?.[key] || ''}`.toLowerCase(); + const bValue = `${b?.[key] || ''}`.toLowerCase(); + return aValue.localeCompare(bValue); + }); + return order === 'desc' ? sorted.reverse() : sorted; + }; + 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.') @@ -187,7 +202,7 @@ const EventsMultipleList = () => { useEffect(() => { if (fromDate && toDate && customers?.length > 0 && resources?.length > 0) { EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => { - setEvents(data.data.filter((item) => { + const nextEvents = data.data.filter((item) => { const currentCustomer = customers.find(c => c.id === item?.data?.customer) || customers?.find(c => c?.name === item?.data?.client_name || c?.name === item?.target_name); const currentResource = resources.find(r => r.id === item?.data?.resource); const customerName = item?.data?.customer ? (currentCustomer?.name || item?.data?.client_name || '') : (item?.data?.client_name || ''); @@ -231,7 +246,8 @@ const EventsMultipleList = () => { item.eyes_on = checkDisability(customers, item) ? 'Yes' : 'No'; item.dob = item?.data?.customer ? customers.find(c => c.id === item?.data?.customer)?.birth_date : (item?.data?.client_birth_date || '') return item; - }).filter(item => item.type === 'medical')); + }).filter(item => item.type === 'medical'); + setEvents(sortEventsByKey(nextEvents, sorting?.key || 'appointment_time', sorting?.order || 'asc')); }) } }, [fromDate, toDate, customers, resources]); @@ -270,20 +286,14 @@ const EventsMultipleList = () => { useEffect(() => { - const newEvents = [...events]; - const sortedEvents = sorting.key === '' ? newEvents : newEvents.sort((a, b) => { - return a[sorting.key]?.localeCompare(b[sorting.key]); - }); - setEvents( - sorting.order === 'asc' ? sortedEvents : sortedEvents.reverse() - ) + setEvents((prevEvents) => sortEventsByKey(prevEvents, sorting.key, sorting.order || 'asc')); }, [sorting]); const confirmEvent = (id) => { if (!AuthService.canEditMedicalEvents()) return; EventsService.updateEvent(id, {confirmed: true}).then(() => { EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then((data) => { - setEvents(data.data.filter((item) => { + const nextEvents = data.data.filter((item) => { const currentCustomer = customers.find(c => c.id === item?.data?.customer) || customers?.find(c => c?.name === item?.data?.client_name || c?.name === item?.target_name); const currentResource = resources.find(r => r.id === item?.data?.resource); const customerName = item?.data?.customer ? (currentCustomer?.name || item?.data?.client_name || '') : (item?.data?.client_name || ''); @@ -327,7 +337,8 @@ const EventsMultipleList = () => { 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.dob = item?.data?.customer ? customers.find(c => c.id === item?.data?.customer)?.birth_date : (item?.data?.client_birth_date || '') return item; - }).filter(item => item.type === 'medical')); + }).filter(item => item.type === 'medical'); + setEvents(sortEventsByKey(nextEvents, sorting?.key || 'appointment_time', sorting?.order || 'asc')); }) }); }