From 0b72b72fcc1b840570dd0cb855374836944c6469 Mon Sep 17 00:00:00 2001 From: Lixian Zhou Date: Thu, 19 Mar 2026 16:26:10 -0400 Subject: [PATCH] fix --- .../center-calendar/CenterCalendar.js | 89 ++++++++++++------- .../src/components/events/EventsCalendar.js | 77 ++++++++++------ 2 files changed, 106 insertions(+), 60 deletions(-) diff --git a/client/src/components/center-calendar/CenterCalendar.js b/client/src/components/center-calendar/CenterCalendar.js index dcb4677..a8c79a4 100644 --- a/client/src/components/center-calendar/CenterCalendar.js +++ b/client/src/components/center-calendar/CenterCalendar.js @@ -1,4 +1,4 @@ -import React, {useState, useEffect, useRef, useCallback} from "react"; +import React, {useState, useEffect, useMemo, useRef, useCallback} from "react"; import { useNavigate } from "react-router-dom"; import { AuthService, EventsService, CustomerService, ResourceService, VehicleService, EmployeeService } from "../../services"; import moment from 'moment'; @@ -38,9 +38,6 @@ const EventsCalendar = () => { 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([]); @@ -147,6 +144,7 @@ const EventsCalendar = () => { const [initialEventsLoaded, setInitialEventsLoaded] = useState(false); const [isTabTransitionLoading, setIsTabTransitionLoading] = useState(false); const hasMarkedInitialEventsLoadedRef = useRef(false); + const eventsFetchRequestIdRef = useRef(0); const visibleCalendarTabs = calendarTabOrder.filter((tabKey) => AuthService.canViewCalendarTab(tabKey) || AuthService.canEditCalendarTab(tabKey)); const canEditCurrentTab = () => AuthService.canEditCalendarTab(currentTab); @@ -184,6 +182,22 @@ const EventsCalendar = () => { return doctorName ? `${fullName} - ${doctorName}` : fullName; }; + const customerById = useMemo(() => { + const map = new Map(); + (customers || []).forEach((customer) => { + if (customer?.id) map.set(customer.id, customer); + }); + return map; + }, [customers]); + + const resourceById = useMemo(() => { + const map = new Map(); + (resources || []).forEach((resource) => { + if (resource?.id) map.set(resource.id, resource); + }); + return map; + }, [resources]); + // Helper: expand a recurring rule into individual event instances for a date range const expandRecurrence = (rule, rangeFrom, rangeTo) => { const instances = []; @@ -297,15 +311,15 @@ const EventsCalendar = () => { plugins: [eventModalService, eventsServicePlugin, eventRecurrence], callbacks: { onRangeUpdate(range) { - console.log('new calendar range start date', range.start); - console.log('new calendar range end date', range.end); setCurrentRangeStart(range.start); setCurrentRangeEnd(range.end); // Update fromDate/toDate for API fetching based on the range 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)); + const nextFromDate = new Date(startDate.getFullYear(), startDate.getMonth(), 1); + const nextToDate = new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0); + setFromDate((prev) => (prev?.getTime?.() === nextFromDate.getTime() ? prev : nextFromDate)); + setToDate((prev) => (prev?.getTime?.() === nextToDate.getTime() ? prev : nextToDate)); }, onClickDate(date) { if (currentTabRef.current === 'medicalCalendar' || !AuthService.canEditCalendarTab(currentTabRef.current)) return; @@ -331,8 +345,6 @@ const EventsCalendar = () => { // Filter events based on current calendar range const getFilteredEvents = () => { - console.log("CenterCalendar - range:", currentRangeStart, "to", currentRangeEnd, "events:", events.length); - if (!currentRangeStart || !currentRangeEnd) { // If no range set yet, show all events for current month const now = moment(); @@ -429,19 +441,34 @@ const EventsCalendar = () => { }, [currentTab]); useEffect(() => { - 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); - } - }); + const requestId = ++eventsFetchRequestIdRef.current; + const timer = setTimeout(() => { + EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }) + .then((eventsResult) => { + if (requestId !== eventsFetchRequestIdRef.current) return; + setAllEvents(eventsResult?.data || []); + if (!hasMarkedInitialEventsLoadedRef.current) { + hasMarkedInitialEventsLoadedRef.current = true; + setInitialEventsLoaded(true); + } + }).catch(() => { + if (requestId !== eventsFetchRequestIdRef.current) return; + setAllEvents([]); + if (!hasMarkedInitialEventsLoadedRef.current) { + hasMarkedInitialEventsLoadedRef.current = true; + setInitialEventsLoaded(true); + } + }); + }, 180); + return () => clearTimeout(timer); }, [fromDate, toDate]); + useEffect(() => { + EventsService.getAllEventRecurrences() + .then((data) => setAllEventRecurrences(data?.data || [])) + .catch(() => setAllEventRecurrences([])); + }, []); + useEffect(() => { if (!showSpinner) return; const timer = setInterval(() => { @@ -490,14 +517,16 @@ const EventsCalendar = () => { 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 || ''); + const customer = item?.data?.customer ? customerById.get(item?.data?.customer) : null; + const resource = item?.data?.resource ? resourceById.get(item?.data?.resource) : null; + const customerField = customer?.name || item?.data?.client_name || ''; + const doctorField = 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.phone = resource?.phone || item?.data?.resource_phone || ''; + item.contact = resource?.contact || item?.data?.resource_contact || ''; + item.address = resource?.address || item?.data?.resource_address || ''; item.translation = item?.data?.interpreter || ''; item.newPatient = item?.data?.new_patient || ''; item.needId = item?.data?.need_id || ''; @@ -519,11 +548,8 @@ const EventsCalendar = () => { 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 => { const includeDeleted = selectedColorFilters.includes('gray'); @@ -598,7 +624,7 @@ const EventsCalendar = () => { filteredEvents = filteredEvents?.filter(item => { // If it's a customer-related event, check if the customer is active if (item.target_type === 'customer' && item.target_uuid) { - const customer = customers.find(c => c.id === item.target_uuid); + const customer = customerById.get(item.target_uuid); // Only show if customer exists and is active (not discharged/transferred/deceased) return customer && customer.status === 'active' && customer.type !== 'discharged' && @@ -612,13 +638,12 @@ const EventsCalendar = () => { setEvents(filteredEvents); } - }, [customers, resources, timeData, currentTab, allEvents, allEventRecurrences, showDeletedItems, selectedColorFilters]) + }, [customers, resources, timeData, currentTab, allEvents, allEventRecurrences, showDeletedItems, selectedColorFilters, customerById, resourceById]) useEffect(() => { if (events && calendar) { - console.log("CenterCalendar useEffect - events:", events.length, "range:", currentRangeStart, "to", currentRangeEnd); calendar?.eventsService?.set(events); setGroupedEvents(getGroupedEvents()); } diff --git a/client/src/components/events/EventsCalendar.js b/client/src/components/events/EventsCalendar.js index 8b3852c..3822d29 100644 --- a/client/src/components/events/EventsCalendar.js +++ b/client/src/components/events/EventsCalendar.js @@ -1,4 +1,4 @@ -import React, {useState, useEffect, useRef} from "react"; +import React, {useState, useEffect, useMemo, useRef} from "react"; import { useNavigate } from "react-router-dom"; import { AuthService, EventsService, CustomerService, ResourceService } from "../../services"; import moment from 'moment'; @@ -27,9 +27,6 @@ const EventsCalendar = () => { 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 [selectedColorFilters, setSelectedColorFilters] = useState([]); const [timeData, setTimeData] = useState([]); const [showFilterDropdown, setShowFilterDropdown] = useState(false); @@ -54,6 +51,7 @@ const EventsCalendar = () => { const [initialLookupsLoaded, setInitialLookupsLoaded] = useState(false); const [initialEventsLoaded, setInitialEventsLoaded] = useState(false); const hasMarkedInitialEventsLoadedRef = useRef(false); + const eventsFetchRequestIdRef = useRef(0); const [newEventStartDateTime, setNewEventStartDateTime] = useState(new Date()); const [newEventEndDateTime, setNewEventEndDateTime] = useState(new Date()); const [newEventColor, setNewEventColor] = useState(''); @@ -90,6 +88,22 @@ const EventsCalendar = () => { return doctorName ? `${fullName} - ${doctorName}` : fullName; }; + const customerById = useMemo(() => { + const map = new Map(); + (customers || []).forEach((customer) => { + if (customer?.id) map.set(customer.id, customer); + }); + return map; + }, [customers]); + + const resourceById = useMemo(() => { + const map = new Map(); + (resources || []).forEach((resource) => { + if (resource?.id) map.set(resource.id, resource); + }); + return map; + }, [resources]); + const calendar = useCalendarApp({ views: [createViewMonthGrid(), createViewDay(), createViewWeek()], monthGridOptions: { @@ -106,8 +120,10 @@ const EventsCalendar = () => { 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)); + const nextFromDate = new Date(startDate.getFullYear(), startDate.getMonth(), 1); + const nextToDate = new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0); + setFromDate((prev) => (prev?.getTime?.() === nextFromDate.getTime() ? prev : nextFromDate)); + setToDate((prev) => (prev?.getTime?.() === nextToDate.getTime() ? prev : nextToDate)); }, onClickDate(date) { // Disabled: prevent creating new event by tapping calendar date @@ -191,19 +207,25 @@ const EventsCalendar = () => { }, []); useEffect(() => { - 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); - } - }); + const requestId = ++eventsFetchRequestIdRef.current; + const timer = setTimeout(() => { + EventsService.getAllEvents({ from: EventsService.formatDate(fromDate), to: EventsService.formatDate(toDate) }).then(data => { + if (requestId !== eventsFetchRequestIdRef.current) return; + setAllEvents(data?.data || []); + if (!hasMarkedInitialEventsLoadedRef.current) { + hasMarkedInitialEventsLoadedRef.current = true; + setInitialEventsLoaded(true); + } + }).catch(() => { + if (requestId !== eventsFetchRequestIdRef.current) return; + setAllEvents([]); + if (!hasMarkedInitialEventsLoadedRef.current) { + hasMarkedInitialEventsLoadedRef.current = true; + setInitialEventsLoaded(true); + } + }); + }, 180); + return () => clearTimeout(timer); }, [fromDate, toDate]); useEffect(() => { @@ -230,14 +252,16 @@ const EventsCalendar = () => { 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 || ''); + const customer = item?.data?.customer ? customerById.get(item?.data?.customer) : null; + const resource = item?.data?.resource ? resourceById.get(item?.data?.resource) : null; + const customerField = customer?.name || item?.data?.client_name || ''; + const doctorField = 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.phone = resource?.phone || item?.data?.resource_phone || ''; + item.contact = resource?.contact || item?.data?.resource_contact || ''; + item.address = resource?.address || item?.data?.resource_address || ''; item.translation = item?.data?.interpreter || ''; item.newPatient = item?.data?.new_patient || ''; item.needId = item?.data?.need_id || ''; @@ -259,11 +283,8 @@ const EventsCalendar = () => { 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 => { const includeDeleted = selectedColorFilters.includes('gray'); @@ -282,7 +303,7 @@ const EventsCalendar = () => { return false; })); } - }, [customers, resources, timeData, allEvents, selectedColorFilters]) + }, [customers, resources, timeData, allEvents, selectedColorFilters, customerById, resourceById])