Загрузка данных


import { useCallback, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';

import { communicator } from '@core/comm';
import { useAppSelect } from '@hooks/useAppSelector';
import { CORPACTIONS_OPEN_EXESTED_WIDGET_EVENT, HIGHLIGHT_WIDGET_EVENT } from '@modules/widgets/shared';
import { addContentPropsToWidget, unbindWidgets } from '@store/slices/widgets';
import { getState } from '@store/store';
import { useWidgetsBind } from '@utils/hooks/useWidgetsBind';

import { DEFAULT_SYMBOL } from '../const';

import { useChartPublicContext } from './useChartPublicContext';

import type { Contract } from '@modules/contracts';
import type { Dispatch, SetStateAction } from 'react';
import type { Widget } from 'types/Widgets';

import type { WidgetProperties } from '../properties/types';
import type { ChartContainerProps } from '../types';

interface UseChartComponentFacadeReturn {
  dropDownOpen: boolean;
  setDropdownOpen: Dispatch<SetStateAction<boolean>>;
  currentInstrument: string;
  isWidgetHeaderContextMenuOpen: boolean;
  setIsWidgetHeaderContextMenuOpen: Dispatch<SetStateAction<boolean>>;
  onDropInstruments: (val: string, withUpdate?: boolean) => void;
  addInstrumentFromModal: (instruments: Pick<Contract, 'issKey'>[]) => void;
  isOver: boolean;
}

type SaveContentPropsOptions = Partial<WidgetProperties['chartState']> & {
  withUpdate?: boolean;
  cleanIndicativeData?: boolean;
};

export default function useChartComponentFacade(
  props: ChartContainerProps,
): UseChartComponentFacadeReturn {
  const { widgetId } = props;

  const dispatch = useDispatch();

  const [isOver, setIsOver] = useState(false);
  const [dropDownOpen, setDropdownOpen] = useState(false);
  const [isWidgetHeaderContextMenuOpen, setIsWidgetHeaderContextMenuOpen] = useState(false);

  const widgetProperties = useAppSelect(
    (state) => state.widgets.widgets.find(({ id }) => id === widgetId)?.widgetContentProps,
  ) as WidgetProperties | undefined;

  const [currentInstrument, setCurrentInstrument] = useState(
    widgetProperties?.chartState?.savedInstrument ?? DEFAULT_SYMBOL,
  );

  const { triggerRelatedWidgetsToUpdate, getMasterInstrumentFromPublicContext } = useWidgetsBind({
    widgetId,
  });

  const saveContentProps = useCallback(
    (val: SaveContentPropsOptions): void => {
      const { withUpdate, cleanIndicativeData, ...chartStateVal } = val;

      // Используем прямой доступ к стору, чтобы получить актуальные свойства виджета
      const oldProps = (getState().widgets.widgets.find(({ id }) => id === widgetId) as Widget | undefined)
        ?.widgetContentProps;

      if (!oldProps) {
        return;
      }

      dispatch(
        addContentPropsToWidget({
          id: widgetId,
          withoutSend: !withUpdate,
          widgetContentProps: {
            chartState: {
              ...oldProps.chartState,
              ...chartStateVal,
            },
            // при смене инструмента очищаем индикативные данные, переданные из виджета Индикативные котировки
            indicativeData: cleanIndicativeData ? undefined : oldProps.indicativeData,
          },
        }),
      );
    },
    [dispatch, widgetId],
  );

  useEffect(() => {
    triggerRelatedWidgetsToUpdate(currentInstrument);
  }, [currentInstrument, triggerRelatedWidgetsToUpdate]);

  const onInstrumentChange = useCallback(
    (newVal: string, withUpdate?: boolean, unbind = true): void => {
      setCurrentInstrument(newVal);

      if (unbind) {
        dispatch(unbindWidgets({ widgetId }));
      }

      // при смене инструмента очищаем индикативные данные виджета график
      // т.к. логика для графика индикатива построена на наличии в widgetContentProps данных indicativeData
      saveContentProps({
        savedInstrument: newVal,
        withUpdate,
        cleanIndicativeData: true,
      });

      setIsWidgetHeaderContextMenuOpen(false);
    },
    [dispatch, saveContentProps, widgetId],
  );

  const addInstrumentFromModal = useCallback(
    (instruments: Pick<Contract, 'issKey'>[]) => {
      const issKey = instruments[0]?.issKey;

      if (!issKey) {
        return;
      }

      onInstrumentChange(issKey);
    },
    [onInstrumentChange],
  );

  const onInstrumentChangeFromBind = useCallback(
    (instrumentId: string) => {
      onInstrumentChange(instrumentId, true, false);
    },
    [onInstrumentChange],
  );

  const onDropInstruments = useCallback(
    (val: string, withUpdate?: boolean): void => {
      onInstrumentChange(val, withUpdate);
    },
    [onInstrumentChange],
  );

  useChartPublicContext({
    setCurrInstrument: onInstrumentChangeFromBind,
    widgetId,
    getMasterInstrumentFromPublicContext,
  });

  useEffect(() => {
    const unsubscribe = communicator.listen(
      {
        messageType: CORPACTIONS_OPEN_EXESTED_WIDGET_EVENT,
      },
      (message) => {
        const issKey = (message as Record<number, string>)[widgetId];

        if (issKey) {
          addInstrumentFromModal([{ issKey }]);
        }
      },
    );

    const unsubscribeHighlighter = communicator.listen(
      {
        messageType: HIGHLIGHT_WIDGET_EVENT,
      },
      (message) => {
        const typedMessage = message as Record<number, boolean>;

        if (Object.keys(typedMessage).includes(String(widgetId))) {
          setIsOver(typedMessage[widgetId]);
        }
      },
    );

    return () => {
      unsubscribe();
      unsubscribeHighlighter();
    };
  }, [addInstrumentFromModal, widgetId]);

  return {
    dropDownOpen,
    setDropdownOpen,
    currentInstrument,
    isWidgetHeaderContextMenuOpen,
    setIsWidgetHeaderContextMenuOpen,
    onDropInstruments,
    addInstrumentFromModal,
    isOver,
  };
}