/* 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;