This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user