/* eslint-disable no-promise-executor-return */ import dayjs from 'dayjs'; import _ from 'lodash'; import { useEffect, useRef, useState, } from 'react'; import { useRecoilState, useRecoilValue, useSetRecoilState, } from 'recoil'; import { t } from 'ttag'; import { beatOptionsState, dailySummariesCombineState, dataHeatmapChartState, dataHeatmapChartStatusState, generateDefaultHeatmapState, listOriginReviewedCellsState, listReviewedCellsState, listSelectedCellsState, listUnReviewedCellsState, resetAppliedBulkChangesState, rrHeatMapCellChangesState, rrOverviewOriginState, rrOverviewState, totalStripState, updatedDailySummariesState, } from '.'; import fetchHolterBeatEventsCount from '../../../../../Apollo/Functions/fetchHolterBeatEventsCount'; import fetchHolterEventIndex from '../../../../../Apollo/Functions/fetchHolterEventIndex'; import fetchHolterEvents from '../../../../../Apollo/Functions/fetchHolterEvents'; import fetchHolterEventsDailyCount from '../../../../../Apollo/Functions/fetchHolterEventsDailyCount'; import fetchHolterRhythmEventsCount from '../../../../../Apollo/Functions/fetchHolterRhythmEventsCount'; import fetchHolterRrHeatMapCount from '../../../../../Apollo/Functions/fetchHolterRrHeatMapCount'; import fetchHolterRrHeatMapOverview from '../../../../../Apollo/Functions/fetchHolterRrHeatMapOverview'; import handleCheckSavingAiProcess from '../../../../../Apollo/Functions/handleCheckSavingAiProcess'; import { clearApolloCache } from '../../../../../Apollo/apolloClient'; import { EMITTER_CONSTANTS } from '../../../../../ConstantsV2'; import { AI_COMMAND, EVENT_COUNT_CONVERT, KEY_CANCEL, KEY_RECOIL, STRIP_AFIB_THUMBNAIL_INFO, STRIP_ARTIFACT_THUMBNAIL_INFO, TYPE_RHYTHM_EVENT_ENUM, typesPersistEventV2, } from '../../../../../ConstantsV2/aiConstants'; import { resetHistoryStateRecoil } from '../../../../../Store/dbHistoryStateRecoil'; import { getDataStateRecoil } from '../../../../../Store/dbStateRecoil'; import { convertToDayJS } from '../../../../../UtilsV2/aiUtils'; import { useEmitter, useGetRecoilValue, } from '../../../../../UtilsV2/customHooks'; import emitter from '../../../../../UtilsV2/eventEmitter'; import { toastrError } from '../../../../../UtilsV2/toastNotification'; import { activeTabState, beatChangesState, dailyArtifactCountState, dailyCountState, ecgDataMapState, eventChangesState, eventFilterState, eventNewsState, eventOptionsState, eventOthersState, groupEventChangesState, isActiveTabState, isNotReadyAfterSaveState, isRenderViewerEcgState, originalDailySummariesState, pageIndexState, profileAfibAvgHrState, reloadEcgViewerState, reloadHrChartState, reportInfoState, selectedDateValueState, selectedStripState, } from '../../Recoil'; import { isAppliedFilterEvent } from '../../RhythmEvents/handler'; import { beatEventCountState, rhythmEventCountState } from '../../RhythmEvents/recoil'; import { logError } from '../../handler'; import { formatDataChart } from '../rrInterval/heatMapChart/handler'; import { DATA_HEATMAP_STATUS } from './model'; import { clearCachesBeatHourly } from '../../../../../Store/caches'; import { removeSavingData } from '../helper'; const BeatHrRecoilDataEffect = () => { const keyRecoil = KEY_RECOIL.TAB_1; const activeButton = useRecoilValue(activeTabState(keyRecoil)); const getActiveButton = useGetRecoilValue(activeTabState(keyRecoil)); const getSelectedStrip = useGetRecoilValue(selectedStripState(keyRecoil)); const selectedDateValue = useRecoilValue(selectedDateValueState(keyRecoil)); const getSelectedDateValue = useGetRecoilValue(selectedDateValueState(keyRecoil)); const setBeatChanges = useSetRecoilState(beatChangesState(keyRecoil)); const setEventChanges = useSetRecoilState(eventChangesState(keyRecoil)); const setTotalStrip = useSetRecoilState(totalStripState); const setDailyCount = useSetRecoilState(dailyCountState(keyRecoil)); const setArtifactDailyCount = useSetRecoilState(dailyArtifactCountState(keyRecoil)); const setDailySummariesCombine = useSetRecoilState(dailySummariesCombineState); const setRrOverview = useSetRecoilState(rrOverviewState); const getRrOverview = useGetRecoilValue(rrOverviewState); const setRrOverviewOrigin = useSetRecoilState(rrOverviewOriginState); const getPageIndex = useGetRecoilValue(pageIndexState(keyRecoil)); const setPageIndex = useSetRecoilState(pageIndexState(keyRecoil)); const [updatedDailySummaries, setUpdatedDailySummaries] = useRecoilState(updatedDailySummariesState); const setReloadEcgViewer = useSetRecoilState(reloadEcgViewerState(keyRecoil)); const setReloadHrChart = useSetRecoilState(reloadHrChartState(keyRecoil)); const getDataHeatmapChart = useGetRecoilValue(dataHeatmapChartState); const getDataHeatmapChartStatus = useGetRecoilValue(dataHeatmapChartStatusState); const getListReviewedCells = useGetRecoilValue(listReviewedCellsState); const getListUnReviewedCells = useGetRecoilValue(listUnReviewedCellsState); const setListReviewedCells = useSetRecoilState(listReviewedCellsState); const setListUnReviewedCells = useSetRecoilState(listUnReviewedCellsState); const setDataHeatmapChart = useSetRecoilState(dataHeatmapChartState); const setDataHeatmapChartStatus = useSetRecoilState(dataHeatmapChartStatusState); const setListOriginReviewedCells = useSetRecoilState(listOriginReviewedCellsState); const setGroupEventChanges = useSetRecoilState(groupEventChangesState(keyRecoil)); const getEventFilter = useGetRecoilValue(eventFilterState(keyRecoil)); const isActiveTab = useRecoilValue(isActiveTabState(keyRecoil)); const setIsNotReadyAfterSaveTab1 = useSetRecoilState(isNotReadyAfterSaveState(KEY_RECOIL.TAB_1)); const setIsNotReadyAfterSaveTab2 = useSetRecoilState(isNotReadyAfterSaveState(KEY_RECOIL.TAB_2)); const setIsNotReadyAfterSaveTab3 = useSetRecoilState(isNotReadyAfterSaveState(KEY_RECOIL.TAB_3)); const getHolterRhythmEventsCount = useGetRecoilValue(rhythmEventCountState); const getListSelectedCells = useGetRecoilValue(listSelectedCellsState); const setListSelectedCells = useSetRecoilState(listSelectedCellsState); const setEventOthers = useSetRecoilState(eventOthersState(keyRecoil)); const setEventNews = useSetRecoilState(eventNewsState(keyRecoil)); const setEventOptions = useSetRecoilState(eventOptionsState(keyRecoil)); const setHolterRhythmEventsCount = useSetRecoilState(rhythmEventCountState); const setHolterBeatEventsCount = useSetRecoilState(beatEventCountState); const setSelectedStrip = useSetRecoilState(selectedStripState(keyRecoil)); const setIsRenderViewerEcg = useSetRecoilState(isRenderViewerEcgState); const setRrHeatMapCellChanges = useSetRecoilState(rrHeatMapCellChangesState); const setResetAppliedBulkChanges = useSetRecoilState(resetAppliedBulkChangesState); const setBeatOptions = useSetRecoilState(beatOptionsState(keyRecoil)); const { studyId, profileId, timezoneOffset, } = useRecoilValue(reportInfoState); const [originalDailySummaries, setOriginalDailySummaries] = useRecoilState(originalDailySummariesState); const setEcgDataMap = useSetRecoilState(ecgDataMapState); const getEcgDataMap = useGetRecoilValue(ecgDataMapState); const setProfileAfibAvgHr = useSetRecoilState(profileAfibAvgHrState); const commandPendingQueue = useRef([]); const updateDataMessageQueue = useRef([]); const promiseUpdateData = useRef([]); const isExcutedRef = useRef(false); const [doneReset, setDoneReset] = useState(false); const [doneSetListOriginReviewedCells, setDoneSetListOriginReviewedCells] = useState(true); const formatRrOverview = (rrOverview) => { const result = generateDefaultHeatmapState({ reviewed: 1, total: 1, }); _.forEach(rrOverview, (x) => { result[x.rrHeatMapType] = x.total === 0 ? { reviewed: 1, total: 1, } : { reviewed: x.reviewed, total: x.total, }; }); return result; }; const handleFetchRrOverview = async (isFistFetch = false) => { // check data from indexdb, if true, no need to fetch data from server const rrOver = await getDataStateRecoil('rrOverviewState'); if (isFistFetch && rrOver) { return {}; } try { const rrOverview = await fetchHolterRrHeatMapOverview({ studyId, profileId, }, null, false); const formattedRrOverview = formatRrOverview(rrOverview); setRrOverview(formattedRrOverview); setRrOverviewOrigin(formattedRrOverview); } catch (error) { logError('Failed to fetch rr histogram overview', error); } return {}; }; const handleFetchStripEventsArtifact = async (page) => { if (!selectedDateValue) { return undefined; } try { const validatePage = page <= 0 ? 0 : page; const filterHolterEvents = { studyId, profileId, types: [TYPE_RHYTHM_EVENT_ENUM.ARTIFACT], startRange: { start: selectedDateValue, stop: dayjs(selectedDateValue).add(1, 'days').toISOString(), }, skip: validatePage * STRIP_ARTIFACT_THUMBNAIL_INFO.stripDisplayLimit, }; const { events } = await fetchHolterEvents( filterHolterEvents, STRIP_ARTIFACT_THUMBNAIL_INFO.stripDisplayLimit, true, KEY_CANCEL.API_HOLTER_AI, ); return events; } catch (error) { return undefined; } }; const handleFetchStripEventsAfib = async ({ page, additionalFilter = {} }) => { try { const type = getActiveButton(); const validatePage = page <= 0 ? 0 : page; const filterHolterEvents = { studyId, profileId, types: [type], skip: validatePage * STRIP_AFIB_THUMBNAIL_INFO.stripDisplayLimit, ...additionalFilter, }; const promises = [fetchHolterEvents( filterHolterEvents, STRIP_AFIB_THUMBNAIL_INFO.stripDisplayLimit, true, KEY_CANCEL.API_HOLTER_AI, )]; if (!_.isEmpty(additionalFilter)) { const holterEventsCountFilter = { studyId, profileId, types: [type], ...additionalFilter, }; promises.push(fetchHolterRhythmEventsCount(holterEventsCountFilter, true, KEY_CANCEL.API_HOLTER_AI)); } const [{ events }, eventsCount] = await Promise.all(promises); if (!_.isEmpty(additionalFilter)) { return { events, eventsCount }; } return { events, eventsCount: getHolterRhythmEventsCount() }; } catch (error) { return { events: undefined, eventsCount: 0 }; } }; const handleReloadAfibV2 = async () => { try { const type = getActiveButton(); const selectedDateValue = getSelectedDateValue(); const startSearchDate = selectedDateValue; const stopSearchDate = selectedDateValue ? dayjs(selectedDateValue).add(1, 'days').toISOString() : null; const additionalFilter = {}; const eventFilter = getEventFilter(); if (isAppliedFilterEvent(eventFilter, type)) { _.assign(additionalFilter, { ...(eventFilter.isHideReviewed && { isReviewed: false }), ...(eventFilter.isCapture && { isCaptured: true }), }); } const promiseArr = [ selectedDateValue ? fetchHolterEvents({ studyId, profileId, types: typesPersistEventV2, startSearchDate, stopSearchDate, }, 0, true, KEY_CANCEL.API_HOLTER_AI) : undefined, fetchHolterEventsDailyCount({ studyId, profileId, types: [type], ...additionalFilter, }, null, false), ]; const selectedStrip = getSelectedStrip(); if (selectedStrip?.idEvent) { const filterHolterEventIndex = { studyId, profileId, types: [type], includedEventId: selectedStrip.idEvent, ...additionalFilter, }; promiseArr.push(fetchHolterEventIndex(filterHolterEventIndex, false, KEY_CANCEL.API_HOLTER_AI)); } else { promiseArr.push(new Promise((resolve) => resolve(undefined))); } const [resultHolterEventsPersistAllDay, resultDailyCount, resultEventIndex] = await Promise.allSettled(promiseArr); const dailyCount = resultDailyCount?.status === 'rejected' ? undefined : resultDailyCount.value; if (resultEventIndex?.status === 'fulfilled') { const eventIndex = resultEventIndex.value; if (!_.isNil(eventIndex) && eventIndex >= 0) { const page = Math.floor(eventIndex / STRIP_AFIB_THUMBNAIL_INFO.stripDisplayLimit); await handleFetchStripEventsAfib({ page, additionalFilter }); promiseUpdateData.current.push(() => setPageIndex({ index: page < 0 ? 0 : page })); } else { const pageIndex = getPageIndex(); const { events, eventsCount } = await handleFetchStripEventsAfib({ page: pageIndex.index, additionalFilter }); if (_.isEmpty(events)) { const totalEvent = eventsCount[EVENT_COUNT_CONVERT[getActiveButton()]]?.count || 0; const pageTemp = Math.floor(totalEvent / STRIP_AFIB_THUMBNAIL_INFO.stripDisplayLimit); const page = (totalEvent % STRIP_AFIB_THUMBNAIL_INFO.stripDisplayLimit) === 0 ? pageTemp - 1 : pageTemp; promiseUpdateData.current.push(() => setPageIndex({ index: page < 0 ? 0 : page })); } else { promiseUpdateData.current.push(() => setPageIndex({ index: getPageIndex().index })); } } } if (!_.isEmpty(dailyCount)) { if (getActiveButton() === TYPE_RHYTHM_EVENT_ENUM.ARTIFACT) { promiseUpdateData.current.push(() => setArtifactDailyCount(dailyCount)); } else { promiseUpdateData.current.push(() => setDailyCount(dailyCount)); } } promiseUpdateData.current.push(() => setReloadHrChart((prev) => prev + _.round(Math.random() * 100))); } catch (error) { logError('Failed to fetch reload data: ', error); toastrError(error.message, t`Error`); } }; const handleReloadAfibHrChartV2 = async () => { try { const selectedDateValue = getSelectedDateValue(); const startSearchDate = selectedDateValue; const stopSearchDate = selectedDateValue ? dayjs(selectedDateValue).add(1, 'days').toISOString() : null; const promiseArr = [ selectedDateValue ? fetchHolterEvents({ studyId, profileId, types: typesPersistEventV2, startSearchDate, stopSearchDate, }, 0, true, KEY_CANCEL.API_HOLTER_AI) : undefined, ]; await Promise.allSettled(promiseArr); promiseUpdateData.current.push(() => setReloadHrChart((prev) => prev + _.round(Math.random() * 100))); } catch (error) { logError('Failed to fetch reload data: ', error); toastrError(error.message, t`Error`); } }; const handleReloadArtifact = async (reloadAll = false) => { try { // Thumbnail const filterDailyCount = { studyId, profileId, types: [TYPE_RHYTHM_EVENT_ENUM.ARTIFACT], }; const promiseArr = [ fetchHolterEventsDailyCount(filterDailyCount, null, false), ]; const selectedDateValue = getSelectedDateValue(); if (reloadAll) { // Strip event const selectedStrip = getSelectedStrip(); if (selectedStrip?.idEvent && selectedDateValue) { const filterHolterEventIndex = { studyId, profileId, types: [TYPE_RHYTHM_EVENT_ENUM.ARTIFACT], startRange: { start: selectedDateValue, stop: dayjs(selectedDateValue).add(1, 'days').toISOString(), }, includedEventId: selectedStrip.idEvent, }; promiseArr.push(fetchHolterEventIndex(filterHolterEventIndex, false, KEY_CANCEL.API_HOLTER_AI)); } else { promiseArr.push(new Promise((resolve) => resolve(undefined))); } if (selectedDateValue) { // Hr chart: fetch to cache only const filterHolterEvents = { studyId, profileId, types: [TYPE_RHYTHM_EVENT_ENUM.ARTIFACT], startSearchDate: selectedDateValue, stopSearchDate: dayjs(selectedDateValue).add(1, 'days').toISOString(), }; promiseArr.push(fetchHolterEvents(filterHolterEvents, 0, true, KEY_CANCEL.API_HOLTER_AI)); } } const [resultDailyCount, resultEventIndex, resultHolterEventsAllDay] = await Promise.allSettled(promiseArr); const dailyCount = resultDailyCount?.status === 'rejected' ? undefined : resultDailyCount.value; if (resultEventIndex?.status === 'fulfilled' && reloadAll) { const eventIndex = resultEventIndex.value; if (!_.isNil(eventIndex) && eventIndex >= 0) { const page = Math.floor(eventIndex / STRIP_ARTIFACT_THUMBNAIL_INFO.stripDisplayLimit); await handleFetchStripEventsArtifact(page); promiseUpdateData.current.push(() => { if (getActiveButton() === TYPE_RHYTHM_EVENT_ENUM.ARTIFACT) { setPageIndex({ index: page < 0 ? 0 : page }); } }); } else { const pageIndex = getPageIndex(); const events = await handleFetchStripEventsArtifact(pageIndex.index); if (_.isEmpty(events)) { const currentSelectedDateValue = getSelectedDateValue(); const totalEvent = _.find(dailyCount, (x) => x.date === currentSelectedDateValue)?.count || 0; const pageTemp = Math.floor(totalEvent / STRIP_ARTIFACT_THUMBNAIL_INFO.stripDisplayLimit); const page = (totalEvent % STRIP_ARTIFACT_THUMBNAIL_INFO.stripDisplayLimit) === 0 ? pageTemp - 1 : pageTemp; promiseUpdateData.current.push(() => { if (getActiveButton() === TYPE_RHYTHM_EVENT_ENUM.ARTIFACT) { setPageIndex({ index: page < 0 ? 0 : page }); } }); } else { promiseUpdateData.current.push(() => { if (getActiveButton() === TYPE_RHYTHM_EVENT_ENUM.ARTIFACT) { setPageIndex({ index: getPageIndex().index }); } }); } } } if (!_.isEmpty(dailyCount)) { const currentSelectedDateValue = getSelectedDateValue(); const foundDailyCount = _.find(dailyCount, (x) => x.date === currentSelectedDateValue)?.count; promiseUpdateData.current.push(() => { if (getActiveButton() === TYPE_RHYTHM_EVENT_ENUM.ARTIFACT) { setTotalStrip(foundDailyCount ?? 0); setArtifactDailyCount(dailyCount); } else { setDailyCount(dailyCount); } }); } promiseUpdateData.current.push(() => setReloadHrChart((prev) => prev + _.round(Math.random() * 100))); } catch (error) { logError('Error: ', error); toastrError(error.message, t`Error`); } return true; }; const handleReloadEvent = async (msg) => { if (isActiveTab) { setIsRenderViewerEcg(false); } try { const { newEvents, updatedEvents, deletedEvents, deletedEventTypes, } = msg; // Reload if user in artifact areas and event updated is in selected date const selectedDateValue = getSelectedDateValue(); const selectedDateString = selectedDateValue ? convertToDayJS(selectedDateValue, timezoneOffset).format('DD-MM-YYYY') : null; const types = [TYPE_RHYTHM_EVENT_ENUM.AFIB, TYPE_RHYTHM_EVENT_ENUM.ARTIFACT]; let needUpdateArtifact; let needUpdateAfib; if (deletedEventTypes?.length && !(needUpdateArtifact || needUpdateAfib)) { deletedEventTypes.forEach((type) => { if (types.includes(type) && activeButton === TYPE_RHYTHM_EVENT_ENUM.AFIB) { needUpdateAfib = true; } else if (type === TYPE_RHYTHM_EVENT_ENUM.ARTIFACT && activeButton === TYPE_RHYTHM_EVENT_ENUM.ARTIFACT) { needUpdateArtifact = true; } }); } if (deletedEvents?.length && !(needUpdateArtifact || needUpdateAfib)) { deletedEvents.forEach((deletedEvent) => { if (deletedEvent.type === activeButton && activeButton === TYPE_RHYTHM_EVENT_ENUM.AFIB) { needUpdateAfib = true; } else { const isSameDate = selectedDateString === convertToDayJS(deletedEvent.start, timezoneOffset).format('DD-MM-YYYY'); if (deletedEvent.type === activeButton && isSameDate) { needUpdateArtifact = true; } } }); } if (updatedEvents?.length && !(needUpdateArtifact || needUpdateAfib)) { updatedEvents.forEach((updatedEvent) => { if (updatedEvent.type === activeButton && activeButton === TYPE_RHYTHM_EVENT_ENUM.AFIB) { needUpdateAfib = true; } else { const isSameDate = selectedDateString === convertToDayJS(updatedEvent.start, timezoneOffset).format('DD-MM-YYYY'); if (updatedEvent.type === activeButton && isSameDate) { needUpdateArtifact = true; } } }); } if (newEvents?.length && !(needUpdateArtifact || needUpdateAfib)) { newEvents.forEach((newEvent) => { if (newEvent.type === activeButton && activeButton === TYPE_RHYTHM_EVENT_ENUM.AFIB) { needUpdateAfib = true; } else { const isSameDate = selectedDateString === convertToDayJS(newEvent.start, timezoneOffset).format('DD-MM-YYYY'); if (newEvent.type === activeButton && isSameDate) { needUpdateArtifact = true; } } }); } if (needUpdateArtifact) { await handleReloadArtifact(true); } else if (needUpdateAfib) { await handleReloadAfibV2(); } else if (activeButton === TYPE_RHYTHM_EVENT_ENUM.ARTIFACT) { await handleReloadArtifact(); } else if (activeButton === TYPE_RHYTHM_EVENT_ENUM.AFIB) { await handleReloadAfibHrChartV2(); } console.log('[beatHrRecoilDataEffect]-RELOAD-EVENT-BEATHR', needUpdateArtifact, activeButton); promiseUpdateData.current.push(() => { setGroupEventChanges((prev) => { if (prev.length === 0) { return prev; } return []; }); let eventChanges = []; let eventNews = []; let eventOthers = []; setEventChanges((prev) => { let count = 0; const filterResult = prev.filter((x) => { if (x.isSaving) { count += 1; return false; } return true; }); if (count === 0) { return prev; } eventChanges.push(...filterResult); return filterResult; }); setEventOthers((prev) => { let count = 0; const filterResult = prev.filter((x) => { if (x.isSaving) { count += 1; return false; } return true; }); if (count === 0) { return prev; } eventOthers.push(...filterResult); return filterResult; }); setEventNews((prev) => { let count = 0; const filterResult = prev.filter((x) => { if (x.isSaving) { count += 1; return false; } return true; }); if (count === 0) { return prev; } eventNews.push(...filterResult); return filterResult; }); setEventOptions({ eventNewsState: eventNews, eventChangesState: eventChanges, eventOthersState: eventOthers, }); setTimeout(() => { resetHistoryStateRecoil(); }, 200); const selectedStrip = getSelectedStrip(); if (selectedStrip?.isNew) { setSelectedStrip(null); } eventChanges = []; eventNews = []; eventOthers = []; }); promiseUpdateData.current.push(() => setReloadEcgViewer((prev) => prev + _.round(Math.random() * 100))); } catch (error) { logError('Error: ', error); } }; const updateCellsStatus = async (listDataHeatmapChart) => { const resultListReviewedCells = {}; const resultListUnReviewedCells = {}; const resultListOriginReviewedCells = {}; const listUnReviewedCells = getListUnReviewedCells(); const dataHeatmapChart = getDataHeatmapChart(); const listReviewedCells = getListReviewedCells(); const listSelectedCells = _.filter(getListSelectedCells(), (x) => !_.isNaN(Number(x.id))); const newListSelectedCells = []; let isSetListSelectedCells = false; const activeButton = getActiveButton(); _.forEach(listDataHeatmapChart, (newDataHeatmapChart, key) => { const { listReviewedCells: currentListReviewedCells, listOriginReviewedCells, } = formatDataChart({ currentDataHeatmapChart: newDataHeatmapChart, type: key, }); resultListOriginReviewedCells[key] = listOriginReviewedCells; const oldDataHeatmapChart = dataHeatmapChart[key]; const oldListReviewedCells = listReviewedCells[key]; const studyId2 = window.location.pathname.split('/')[2]; const defaultHeatmapState = JSON.parse(localStorage.getItem(`defaultValueHeatmap-${Number(studyId2)}`)); if (defaultHeatmapState) { const resultRrOverviewCells = getRrOverview(); defaultHeatmapState[key] = { newValue: { listReviewedCells: listReviewedCells[key], listUnReviewedCells: [], listSelectedCells: [], activeButton: key, markCells: 'initialize', resetData: true, rrOverview: resultRrOverviewCells, }, }; localStorage.setItem(`defaultValueHeatmap-${Number(studyId2)}`, JSON.stringify(defaultHeatmapState)); } const newListReviewedCells = []; /* Điều kiện của list review mới: - Có trong list review hiện tại (list review được update trong thời gian saving) và không bị mất đi trong list data mới - Không nằm trong list data hiện tại nhưng có trong list data mới và có cờ isRevewed = true */ let isSetListReviewedCells = false; _.forEach(currentListReviewedCells, (cells) => { // check trong list review const isInListOldReviewed = _.some(oldListReviewedCells, { id: cells.id }); if (isInListOldReviewed) { // Có trong list review hiện tại (list review được update trong thời gian saving) và không bị mất đi trong list data mới newListReviewedCells.push(cells); return; } // check trong list data heatmap const isInListOldData = _.some(oldDataHeatmapChart, { id: cells.id }); if (!isInListOldData) { // Không nằm trong list data hiện tại nhưng có trong list data mới và có cờ isRevewed = true isSetListReviewedCells = true; newListReviewedCells.push(cells); } }); _.forEach(oldListReviewedCells, (cells) => { const isInListNewData = _.some(newDataHeatmapChart, { id: cells.id }); if (!isInListNewData) { isSetListReviewedCells = true; return; } const isInCurrentList = _.some(newListReviewedCells, { id: cells.id }); if (!isInCurrentList) { // Có trong list review hiện tại (list review được update trong thời gian saving) và không bị mất đi trong list data mới newListReviewedCells.push(cells); } }); if (isSetListReviewedCells) { resultListReviewedCells[key] = newListReviewedCells; } // reset list unreview if (listUnReviewedCells[key]?.length > 0) { resultListUnReviewedCells[key] = []; } if (key === activeButton) { // Nếu có item không nằm trong list hiện tại (sau khi save bị xoá cell) hoặc nằm trong nhưng value thay đổi thì set reload _.forEach(listSelectedCells, (cell) => { let newDataValue; const isInNewData = _.some(newDataHeatmapChart, (item) => { if (item.id === cell.id) { newDataValue = item.count; return true; } return false; }); if (isInNewData) { const newCell = { ...cell, value: newDataValue }; if (cell.value !== newDataValue) { isSetListSelectedCells = true; } newListSelectedCells.push(newCell); return; } isSetListSelectedCells = true; }); } }); if (!_.isEmpty(resultListReviewedCells)) { setListReviewedCells((prev) => ({ ...prev, ...resultListReviewedCells, })); } if (!_.isEmpty(resultListUnReviewedCells)) { setListUnReviewedCells((prev) => ({ ...prev, ...resultListUnReviewedCells, })); } setListOriginReviewedCells(resultListOriginReviewedCells); if (isSetListSelectedCells) { emitter.emit(EMITTER_CONSTANTS.UPDATE_RELOAD_BEAT, newListSelectedCells); setListSelectedCells(newListSelectedCells); } else { emitter.emit(EMITTER_CONSTANTS.UPDATE_RELOAD_BEAT); } }; const fetchDataHeatmapChart = async (key) => { try { const filter = { studyId, profileId, rrHeatMapType: key, }; const newDataHeatmapChart = await fetchHolterRrHeatMapCount(filter, null, false); return { [key]: newDataHeatmapChart, }; } catch (error) { logError('Failed to fetch rr heatmap data', error); toastrError(error.message); return {}; } }; const handleReloadRrHeatMap = async () => { await clearApolloCache(); const promiseArr = [handleFetchRrOverview()]; _.forEach(getDataHeatmapChartStatus(), (value, key) => { if (value === DATA_HEATMAP_STATUS.SUCCESS || value === DATA_HEATMAP_STATUS.EMPTY) { promiseArr.push(fetchDataHeatmapChart(key)); } }); const data = await Promise.all(promiseArr); const newDataHeatmapChart = {}; const newDataHeatMapChartStatus = {}; _.forEach(data, (value) => { if (!_.isEmpty(value)) { _.assign(newDataHeatmapChart, value); } }); Object.keys(newDataHeatmapChart).forEach((key) => { if (_.isEmpty(newDataHeatmapChart[key])) { newDataHeatMapChartStatus[key] = DATA_HEATMAP_STATUS.EMPTY; } else { newDataHeatMapChartStatus[key] = DATA_HEATMAP_STATUS.SUCCESS; } }); setDataHeatmapChart((prev) => ({ ...prev, ...newDataHeatmapChart, })); setDataHeatmapChartStatus((prev) => ({ ...prev, ...newDataHeatMapChartStatus, })); // use setTimeOut in order to waiting all setState in recoil updateCellsStatus(newDataHeatmapChart); setTimeout(() => { emitter.emit(EMITTER_CONSTANTS.UPDATE_HEATMAP_CHART_DATA); }, 0); }; const handleUpdateBeats = (msg) => { const prevEcgDataMap = getEcgDataMap(); const cloneEcgDataMap = { ...prevEcgDataMap }; if ((msg.summaries?.length || msg.beatsUpdated?.summaries?.length) && !_.isEmpty(prevEcgDataMap?.data)) { _.forEach(msg.summaries || msg.beatsUpdated?.summaries, (summary) => { const foundData = _.find(cloneEcgDataMap.data, (x) => x.id === summary.id); if (foundData) { _.assign(foundData, { avgHrs: summary?.avgHrs, maxHrs: summary?.maxHrs, minHrs: summary?.minHrs, beatFinalPath: summary?.beatFinalPath, latestBeatPrefix: summary?.latestBeatPrefix, }); } }); const summaryIds = _.map(msg.summaries || msg.beatsUpdated?.summaries, 'id'); const socketUpdateEcgDataMap = { ...cloneEcgDataMap, data: _.filter(cloneEcgDataMap.data, (item) => summaryIds.includes(item.id)), }; if (isActiveTab) { emitter.emit(EMITTER_CONSTANTS.UPDATE_SOCKET_BEAT_DATA_MAP, socketUpdateEcgDataMap); } } return cloneEcgDataMap; }; const handleReloadBeat = async (msg) => { try { const cloneEcgDataMap = handleUpdateBeats(msg); promiseUpdateData.current.push(() => { let rrHeatMapCellChanges = []; const resetAppliedBulkChanges = []; let beatsChanges = []; setRrHeatMapCellChanges((prev) => { if (_.isEmpty(prev)) return prev; rrHeatMapCellChanges = removeSavingData(prev); return rrHeatMapCellChanges; }); setResetAppliedBulkChanges((prev) => { if (_.isEmpty(prev)) return prev; resetAppliedBulkChanges.push(...removeSavingData(prev)); return resetAppliedBulkChanges; }); setBeatChanges((prev) => { if (_.isEmpty(prev)) return prev; const beats = removeSavingData(prev); beatsChanges = beats; return beats; }); setBeatOptions((prev) => ({ ...prev, rrHeatMapCellChangesState: rrHeatMapCellChanges, resetAppliedBulkChangesState: resetAppliedBulkChanges, beatChangesState: beatsChanges, })); setEcgDataMap((prev) => { if (_.isEqual(prev, cloneEcgDataMap)) { return prev; } return cloneEcgDataMap; }); setReloadEcgViewer((prev) => prev + _.round(Math.random() * 100)); }); } catch (error) { logError('Failed to reload beat', error); } }; const fetchEventsCount = async () => { try { const eventsCountFilter = { studyId, profileId, }; const promises = [ fetchHolterBeatEventsCount(eventsCountFilter, false, null, false), fetchHolterRhythmEventsCount(eventsCountFilter, false, null, false), ]; const [holterBeatEventsCount, holterRhythmEventsCount] = await Promise.all(promises); setHolterBeatEventsCount(holterBeatEventsCount); setHolterRhythmEventsCount(holterRhythmEventsCount); } catch (error) { logError('Failed to fetch holter rhythm events count: ', error); } }; const batchUpdateData = async () => { try { await clearApolloCache(); await clearCachesBeatHourly(); if (isActiveTab) { setIsRenderViewerEcg(false); } const promise = []; updateDataMessageQueue.current.forEach((x) => { if (x.type === AI_COMMAND.EVENT) { promise.push(handleReloadEvent(x.msg)); promise.push(fetchEventsCount()); } }); const beatPromises = []; updateDataMessageQueue.current.forEach((x) => { if (x.type === AI_COMMAND.BEAT) { beatPromises.push(handleReloadBeat(x.msg)); } }); await Promise.all(beatPromises); // Wait for all beat promises to resolve await Promise.all(promise); // Wait for all promises to resolve promiseUpdateData.current.forEach((update) => update()); } catch (error) { logError('Error: ', error); } finally { console.log('[beatHrRecoilDataEffect]-COMMANDEXECUTED-2', { updateDataMessageQueue: updateDataMessageQueue.current, promiseUpdateData: promiseUpdateData.current, }); updateDataMessageQueue.current.length = 0; promiseUpdateData.current.length = 0; commandPendingQueue.current.length = 0; emitter.emit(EMITTER_CONSTANTS.RELOAD_EVENT_STRIP); if (isActiveTab) { setDoneReset(true); } } }; const updateEventOutOfPending = _.debounce(async () => { await clearApolloCache(); setIsRenderViewerEcg(false); setTimeout(() => { setIsRenderViewerEcg(true); }, 500); }, 2000); useEffect(() => { handleFetchRrOverview(true); }, []); useEffect(() => { if (originalDailySummaries?.length) { const dailySummariesCombine = []; for (let i = 0; i < originalDailySummaries.length; i += 1) { dailySummariesCombine.push({ ...originalDailySummaries[i], ...(updatedDailySummaries.find((x) => x.date === originalDailySummaries[i].date)), }); } setDailySummariesCombine(dailySummariesCombine); } }, [originalDailySummaries, updatedDailySummaries]); useEffect(() => { if (doneReset && doneSetListOriginReviewedCells) { setTimeout(() => { emitter.emit(EMITTER_CONSTANTS.AI_LOADING, { tab: 'beatHr', isLoading: false, isExcuted: isExcutedRef.current, }); setDoneReset(false); if (isExcutedRef.current) { isExcutedRef.current = false; } }, 2000); } }, [doneReset, doneSetListOriginReviewedCells]); useEmitter(EMITTER_CONSTANTS.UPDATE_SET_LIST_ORIGIN_REVIEWED_CELLS_STATUS, (msg) => { setDoneSetListOriginReviewedCells(msg); }, []); useEmitter(EMITTER_CONSTANTS.RR_CELL_REVIEWED, () => { handleFetchRrOverview(); }, []); useEmitter(EMITTER_CONSTANTS.RR_HEATMAP_UPDATED, () => { handleReloadRrHeatMap(); }, [ activeButton, studyId, profileId, ]); useEmitter(EMITTER_CONSTANTS.EVENTSUPDATED_EVENT, (msg) => { const foundMsg = updateDataMessageQueue.current.find((x) => x.type === AI_COMMAND.EVENT); if (foundMsg) { if (!_.isEmpty(msg)) { Object.keys(msg).forEach((key) => { if (foundMsg.msg[key]) { foundMsg.msg[key] = foundMsg.msg[key].concat(msg[key]); } else { foundMsg.msg[key] = msg[key]; } }); } } else { updateDataMessageQueue.current.push({ type: AI_COMMAND.EVENT, msg: _.cloneDeep(msg), }); } if (commandPendingQueue.current.length === 0) { // update out of pending command updateEventOutOfPending(); } }, []); useEmitter(EMITTER_CONSTANTS.BEATSUPDATED_EVENT, (msg) => { const foundMsg = updateDataMessageQueue.current.find((x) => x.type === AI_COMMAND.BEAT); if (foundMsg) { if (!_.isEmpty(msg)) { _.assign(foundMsg, { msg }); } } else { updateDataMessageQueue.current.push({ type: AI_COMMAND.BEAT, msg, }); } }, []); useEmitter(EMITTER_CONSTANTS.DAILY_SUMMARY_UPDATED, (msg) => { setOriginalDailySummaries((prev) => { if (msg) { const cloneDailySummaries = _.cloneDeep(prev); _.forEach(msg, (dailySummary) => { const foundDailySummary = _.find(cloneDailySummaries, (x) => x.date === dailySummary.date); if (foundDailySummary) { _.assign(foundDailySummary, dailySummary); } }); return cloneDailySummaries; } return prev; }); setUpdatedDailySummaries([]); }, []); useEmitter(EMITTER_CONSTANTS.AFIB_HR_SUMMARY_UPDATED, (msg) => { const { afibAvgHr } = msg; setProfileAfibAvgHr(afibAvgHr); }, []); useEmitter(EMITTER_CONSTANTS.AI_PROCESS_SAVED, async (msg) => { await batchUpdateData(); setIsNotReadyAfterSaveTab1(false); }, [selectedDateValue, activeButton, studyId, profileId, isActiveTab]); useEmitter(EMITTER_CONSTANTS.COMMAND_PENDING, (msg) => { // *: Khi 1 user save, BE gửi command pending về tuỳ theo số lượng api gọi lúc save, push command vào mảng và khi // có socket command execute thì remove => khi mảng về 0 thì get hết data nhận đc trong mảng updateDataMessage => update UI if (msg.messageId !== profileId) { return; } emitter.emit(EMITTER_CONSTANTS.AI_LOADING, { tab: 'beatHr', isLoading: true, }); commandPendingQueue.current.push(msg.command); }, [profileId]); useEmitter(EMITTER_CONSTANTS.COMMAND_EXECUTED, async (msg) => { // msg.command: update-events, update-beats, updateHolterHrDailySummary,reviewRrIntervals,get-single-events // updateHolterProfile, generate-report-comments if (msg.messageId !== profileId) return; if (msg.command === 'updateHolterProfile' || msg.command === 'generate-report-comments') return; console.log('[beatHrRecoilDataEffect]-COMMANDEXECUTED-BEFORE', { msg, commandPendingQueue: commandPendingQueue.current, updateDataMessageQueue: updateDataMessageQueue.current, }); const foundIndexCommand = commandPendingQueue.current.findIndex((command) => command === msg.command); if (foundIndexCommand !== -1) { commandPendingQueue.current.splice(foundIndexCommand, 1); } // *: Finish all command if (commandPendingQueue.current.length === 0) { isExcutedRef.current = true; if (isActiveTab) { if (updateDataMessageQueue.current.length !== 0) { // *: Announce this tab have data to update emitter.emit(EMITTER_CONSTANTS.AI_UPDATE_TAB, '1'); const { events, strips } = await handleCheckSavingAiProcess({ studyId, profileId, }); if (!events) { setIsNotReadyAfterSaveTab2(true); } if (!strips) { setIsNotReadyAfterSaveTab3(true); } await batchUpdateData(); } else { setDoneReset(true); } } else { await batchUpdateData(); } } }, [selectedDateValue, activeButton, isActiveTab, studyId, profileId]); return null; }; export default BeatHrRecoilDataEffect;