beatHrRecoilDataEffect.js

PHOTO EMBED

Thu Nov 28 2024 02:28:57 GMT+0000 (Coordinated Universal Time)

Saved by @khainguyenhm

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

/Users/khainguyen/ITRVN_REPO/BTCY/btcy-bioflux-frontend-ai_report_processing/app/LayoutV2/Reports/AiEditReport/BeatHR/recoil/beatHrRecoilDataEffect.js