fix
All checks were successful
Build And Deploy Main / build-and-deploy (push) Successful in 53s

This commit is contained in:
2026-03-19 15:47:34 -04:00
parent 562395cbc3
commit c6c7faa979
3 changed files with 177 additions and 35 deletions

View File

@@ -2,7 +2,7 @@ import React, {useState, useEffect, useRef, useCallback} from "react";
import { useNavigate } from "react-router-dom";
import { AuthService, EventsService, CustomerService, ResourceService, VehicleService, EmployeeService } from "../../services";
import moment from 'moment';
import { Breadcrumb, Tabs, Tab, Button, Modal, Dropdown, Spinner } from "react-bootstrap";
import { Breadcrumb, Tabs, Tab, Button, Modal, Dropdown, ProgressBar } from "react-bootstrap";
import { useCalendarApp, ScheduleXCalendar } from '@schedule-x/react';
import {
viewMonthGrid,
@@ -45,9 +45,21 @@ const EventsCalendar = () => {
const [selectedColorFilters, setSelectedColorFilters] = useState([]);
const [timeData, setTimeData] = useState([]);
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
const eventsServicePlugin = createEventsServicePlugin();
const eventModalService = createEventModalPlugin();
const eventRecurrence = createEventRecurrencePlugin();
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 eventRecurrenceRef = useRef(null);
if (!eventRecurrenceRef.current) {
eventRecurrenceRef.current = createEventRecurrencePlugin();
}
const eventRecurrence = eventRecurrenceRef.current;
const [groupedEvents, setGroupedEvents] = useState(new Map());
const [currentRangeStart, setCurrentRangeStart] = useState(null);
const [currentRangeEnd, setCurrentRangeEnd] = useState(null);
@@ -130,6 +142,10 @@ const EventsCalendar = () => {
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [deleteTargetId, setDeleteTargetId] = useState(null);
const [showSpinner, setShowSpinner] = useState(false);
const [loadingProgress, setLoadingProgress] = useState(12);
const [initialLookupsLoaded, setInitialLookupsLoaded] = useState(false);
const [initialEventsLoaded, setInitialEventsLoaded] = useState(false);
const hasMarkedInitialEventsLoadedRef = useRef(false);
const visibleCalendarTabs = calendarTabOrder.filter((tabKey) => AuthService.canViewCalendarTab(tabKey) || AuthService.canEditCalendarTab(tabKey));
const canEditCurrentTab = () => AuthService.canEditCalendarTab(currentTab);
@@ -379,6 +395,9 @@ const EventsCalendar = () => {
navigate(`/login`);
}
setShowSpinner(true);
setLoadingProgress(12);
setInitialLookupsLoaded(false);
setInitialEventsLoaded(false);
Promise.all([
VehicleService.getAllActiveVehicles().then((data) => {
setVehicles(data.data)
@@ -396,7 +415,7 @@ const EventsCalendar = () => {
setTimeData(data.data);
})
]).finally(() => {
setShowSpinner(false);
setInitialLookupsLoaded(true);
});
}, []);
@@ -409,10 +428,39 @@ const EventsCalendar = () => {
}, [currentTab]);
useEffect(() => {
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then(data => setAllEvents(data?.data));
EventsService.getAllEventRecurrences().then(data => setAllEventRecurrences(data?.data || []));
Promise.allSettled([
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }),
EventsService.getAllEventRecurrences()
]).then(([eventsResult, recurrencesResult]) => {
setAllEvents(eventsResult?.status === 'fulfilled' ? (eventsResult.value?.data || []) : []);
setAllEventRecurrences(recurrencesResult?.status === 'fulfilled' ? (recurrencesResult.value?.data || []) : []);
if (!hasMarkedInitialEventsLoadedRef.current) {
hasMarkedInitialEventsLoadedRef.current = true;
setInitialEventsLoaded(true);
}
});
}, [fromDate, toDate]);
useEffect(() => {
if (!showSpinner) 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);
}, [showSpinner]);
useEffect(() => {
if (!initialLookupsLoaded || !initialEventsLoaded) return;
setLoadingProgress(100);
const closeTimer = setTimeout(() => {
setShowSpinner(false);
}, 250);
return () => clearTimeout(closeTimer);
}, [initialLookupsLoaded, initialEventsLoaded]);
// Auto-fill repeat start date from event date when repeat option is selected (new modal)
useEffect(() => {
if (newEventRecurring && newEventStartDateTime) {
@@ -466,9 +514,17 @@ const EventsCalendar = () => {
item.totalResource = totalResource;
setCurrentTotalResource(item.totalResource);
return item;
})?.filter(item => (!showDeletedItems && item.status === 'active') || showDeletedItems)
})?.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' || item?.color === 'gray') {
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;
@@ -946,14 +1002,17 @@ const EventsCalendar = () => {
const dismissEventModal = (e) => {
e?.preventDefault?.();
e?.stopPropagation?.();
e?.nativeEvent?.stopImmediatePropagation?.();
try {
eventModalService?.close?.();
} catch (_e) {}
try {
calendar?.config?.plugins?.eventModal?.close();
} catch (e) {
document.querySelectorAll('.sx__event-modal').forEach(el => el.remove());
}
} catch (_e) {}
setTimeout(() => {
document.querySelectorAll('[class*="sx__event-modal"]').forEach(el => el.remove());
document.querySelectorAll('[class*="event-modal"]').forEach(el => el.remove());
}, 0);
};
@@ -965,8 +1024,10 @@ const EventsCalendar = () => {
<button
type="button"
onClick={dismissEventModal}
onMouseDown={dismissEventModal}
aria-label="Close"
title="Close"
data-custom-close="true"
style={{
border: 'none',
background: 'transparent',
@@ -1437,11 +1498,17 @@ const getReminderTitleLabel = (value) => {
return (
<>
{showSpinner && <div className="spinner-overlay">
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
</div>}
<Modal show={showSpinner} centered backdrop="static" keyboard={false}>
<Modal.Header>
<Modal.Title>Loading Calendar</Modal.Title>
</Modal.Header>
<Modal.Body>
<div style={{ marginBottom: '8px', fontSize: '13px', color: '#666' }}>
Fetching and rendering calendar data...
</div>
<ProgressBar now={loadingProgress} animated label={`${Math.round(loadingProgress)}%`} />
</Modal.Body>
</Modal>
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item href="/">General</Breadcrumb.Item>

View File

@@ -122,8 +122,14 @@ const UpdateCustomer = () => {
const [pharmacy, setPharmacy] = useState('');
const [pharmacyId, setPharmacyId] = useState('');
const [providers, setProviders] = useState([]);
const normalizeText = (value) => `${value || ''}`.toLowerCase().replace(/\s+/g, ' ').trim();
const isPcpSpecialty = (provider) => {
const specialty = normalizeText(provider?.specialty || provider?.data?.specialty);
return specialty === 'family medicine (pcp)' || specialty === 'family medicine pcp';
};
const pcpProviders = providers.filter((provider) => {
return `${provider?.specialty || ''}`.trim().toLowerCase() === 'family medicine (pcp)';
const status = normalizeText(provider?.status || 'active');
return status === 'active' && isPcpSpecialty(provider);
});
const [pharmacies, setPharmacies] = useState([]);
@@ -239,9 +245,10 @@ const UpdateCustomer = () => {
setPharmacies(data.data);
}
});
ResourceService.getAll('provider').then(data => {
ResourceService.getAll().then(data => {
if (isMounted) {
setProviders(data.data);
const sortedProviders = [...(data?.data || [])].sort((a, b) => `${a?.name || ''}`.localeCompare(`${b?.name || ''}`));
setProviders(sortedProviders);
}
});
};

View File

@@ -2,7 +2,7 @@ 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 { Breadcrumb, Tabs, Tab, Button, Modal, Dropdown, ProgressBar } from "react-bootstrap";
import { useCalendarApp, ScheduleXCalendar } from '@schedule-x/react';
import {
viewMonthGrid,
@@ -33,14 +33,27 @@ const EventsCalendar = () => {
const [selectedColorFilters, setSelectedColorFilters] = useState([]);
const [timeData, setTimeData] = useState([]);
const [showFilterDropdown, setShowFilterDropdown] = useState(false);
const eventsServicePlugin = createEventsServicePlugin();
const eventModalService = createEventModalPlugin();
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 [newEventStartDateTime, setNewEventStartDateTime] = useState(new Date());
const [newEventEndDateTime, setNewEventEndDateTime] = useState(new Date());
const [newEventColor, setNewEventColor] = useState('');
@@ -158,23 +171,61 @@ const EventsCalendar = () => {
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);
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(() => {
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then(data => setAllEvents(data?.data));
EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then(data => {
setAllEvents(data?.data || []);
if (!hasMarkedInitialEventsLoadedRef.current) {
hasMarkedInitialEventsLoadedRef.current = true;
setInitialEventsLoaded(true);
}
}).catch(() => {
setAllEvents([]);
if (!hasMarkedInitialEventsLoadedRef.current) {
hasMarkedInitialEventsLoadedRef.current = true;
setInitialEventsLoaded(true);
}
});
}, [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];
@@ -216,7 +267,8 @@ const EventsCalendar = () => {
return item;
})?.filter(item => {
const includeDeleted = selectedColorFilters.includes('gray');
if (item.status === 'active') return true;
const isDeletedAppt = item?.status !== 'active' || item?.color === 'gray';
if (!isDeletedAppt) return true;
return includeDeleted;
})
?.filter(item => {
@@ -352,14 +404,17 @@ const EventsCalendar = () => {
const dismissEventModal = (e) => {
e?.preventDefault?.();
e?.stopPropagation?.();
e?.nativeEvent?.stopImmediatePropagation?.();
try {
eventModalService?.close?.();
} catch (_e) {}
try {
calendar?.config?.plugins?.eventModal?.close();
} catch (e) {
document.querySelectorAll('.sx__event-modal').forEach(el => el.remove());
}
} catch (_e) {}
setTimeout(() => {
document.querySelectorAll('[class*="sx__event-modal"]').forEach(el => el.remove());
document.querySelectorAll('[class*="event-modal"]').forEach(el => el.remove());
}, 0);
};
const customComponents = {
@@ -370,8 +425,10 @@ const EventsCalendar = () => {
<button
type="button"
onClick={dismissEventModal}
onMouseDown={dismissEventModal}
aria-label="Close"
title="Close"
data-custom-close="true"
style={{
border: 'none',
background: 'transparent',
@@ -604,6 +661,17 @@ const handleSave = () => {
return (
<>
<Modal show={showLoadingModal} centered backdrop="static" keyboard={false}>
<Modal.Header>
<Modal.Title>Loading Medical Event Calendar</Modal.Title>
</Modal.Header>
<Modal.Body>
<div style={{ marginBottom: '8px', fontSize: '13px', color: '#666' }}>
Fetching and rendering calendar data...
</div>
<ProgressBar now={loadingProgress} animated label={`${Math.round(loadingProgress)}%`} />
</Modal.Body>
</Modal>
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item href="/medical/index">Medical</Breadcrumb.Item>