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


import { useEffect, useMemo } from 'react';

import { useAppSelect } from '@hooks/useAppSelector';

import { Contract, useContracts } from '@modules/contracts';
import { filterByUniqIssKey } from '@utils/filterByUniqIssKey';
import { DEFAULT_SYMBOL } from '@widgets/Chart/const';
import { Widget } from 'types/Widgets';

import type { SymbolInfoInput } from 'moex-chart';

interface UseChartPublicContextArg {
  setCurrInstrument: (symbolInfo: SymbolInfoInput) => void;
  widgetId: Widget['id'];
  getMasterInstrumentFromPublicContext: () => string | number | null | undefined;
}

interface UseChartPublicContextReturn {
  issKey: Contract['issKey'] | undefined;
}

export function useChartPublicContext({
  widgetId,
  setCurrInstrument,
  getMasterInstrumentFromPublicContext,
}: UseChartPublicContextArg): UseChartPublicContextReturn {
  const publicContext = useAppSelect((state) => state.publicContext.publicContext);
  const widget = useAppSelect((state) => state.widgets.widgets.find(({ id }) => id === widgetId));

  const { contracts } = useContracts();

  const instruments = useMemo(
    () => (contracts ? filterByUniqIssKey(contracts, ['issKey']) : []),
    [contracts],
  );

  const issKey = useMemo(() => {
    const fieldValue = getMasterInstrumentFromPublicContext();

    return instruments.find((instrument) => instrument.issKey === fieldValue)?.issKey;
    // eslint-disable-next-line react-hooks/exhaustive-deps -- Посмотреть этот момент.
  }, [publicContext, instruments]);

  useEffect(() => {
    const fieldValue = getMasterInstrumentFromPublicContext();

    if (!fieldValue && widget?.master) {
      const defaultInstrument = instruments.find((instrument) => instrument.issKey === DEFAULT_SYMBOL);

      setCurrInstrument({
        symbolId: DEFAULT_SYMBOL,
        symbol: defaultInstrument?.symbol,
        symbolName: defaultInstrument?.displayName,
      });

      return;
    }

    const instrument = instruments.find((item) => item.issKey === fieldValue);

    if (instrument) {
      setCurrInstrument({
        symbolId: instrument.issKey,
        symbol: instrument.symbol,
        symbolName: instrument.displayName,
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps -- Посмотреть этот момент.
  }, [publicContext, instruments, widget?.externalProperties, setCurrInstrument]);

  return {
    issKey,
  };
}




import { useCallback, useEffect, useRef, 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 { WidgetProperties } from '../properties/types';
import type { ChartContainerProps, SelectedInstrument } from '../types';

import type { SymbolInfoInput } from 'moex-chart';
import type { Dispatch, SetStateAction } from 'react';
import type { Widget } from 'types/Widgets';

interface UseChartComponentFacadeReturn {
  dropDownOpen: boolean;
  setDropdownOpen: Dispatch<SetStateAction<boolean>>;
  currentSymbolInfo: SymbolInfoInput;
  isWidgetHeaderContextMenuOpen: boolean;
  setIsWidgetHeaderContextMenuOpen: Dispatch<SetStateAction<boolean>>;
  onDropInstrument: (symbolId: string, withUpdate?: boolean) => void;
  addInstrumentFromModal: (instruments: SelectedInstrument[]) => 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 currentSymbolInfoRef = useRef<SymbolInfoInput>({
    symbolId: widgetProperties?.chartState?.savedInstrument ?? DEFAULT_SYMBOL,
    symbol: widgetProperties?.chartState?.savedSymbol,
    symbolName: widgetProperties?.chartState?.savedSymbolName,
  });

  const [currentSymbolInfo, setCurrentSymbolInfo] = useState<SymbolInfoInput>(
    currentSymbolInfoRef.current,
  );

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

  const triggerRelatedWidgetsToUpdateRef = useRef(triggerRelatedWidgetsToUpdate);

  useEffect(() => {
    triggerRelatedWidgetsToUpdateRef.current = triggerRelatedWidgetsToUpdate;
  }, [triggerRelatedWidgetsToUpdate]);

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

      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,
              ...chartStateValue,
            },
            // при смене инструмента очищаем индикативные данные, переданные из виджета Индикативные котировки
            indicativeData: cleanIndicativeData ? undefined : oldProps.indicativeData,
          },
        }),
      );
    },
    [dispatch, widgetId],
  );

  const onInstrumentChange = useCallback(
    (
      symbolInfo: SymbolInfoInput,
      withUpdate?: boolean,
      unbind = true,
    ): void => {
      const symbolId = symbolInfo.symbolId.trim();

      if (!symbolId) {
        return;
      }

      setIsWidgetHeaderContextMenuOpen(false);

      const currentValue = currentSymbolInfoRef.current;
      const symbolIdChanged = currentValue.symbolId !== symbolId;

      const symbol = symbolInfo.symbol?.trim() || (symbolIdChanged ? undefined : currentValue.symbol);
      const symbolName =
        symbolInfo.symbolName?.trim() || (symbolIdChanged ? undefined : currentValue.symbolName);

      const nextSymbolInfo: SymbolInfoInput = {
        symbolId,
        symbol,
        symbolName,
      };

      const symbolChanged = currentValue.symbol !== symbol;
      const symbolNameChanged = currentValue.symbolName !== symbolName;

      if (!symbolIdChanged && !symbolChanged && !symbolNameChanged) {
        return;
      }

      currentSymbolInfoRef.current = nextSymbolInfo;
      setCurrentSymbolInfo(nextSymbolInfo);

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

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

      if (symbolIdChanged) {
        triggerRelatedWidgetsToUpdateRef.current(symbolId);
      }
    },
    [dispatch, saveContentProps, widgetId],
  );

  const addInstrumentFromModal = useCallback(
    (instruments: SelectedInstrument[]): void => {
      const instrument = instruments[0];

      if (!instrument?.issKey) {
        return;
      }

      onInstrumentChange({
        symbolId: instrument.issKey,
        symbol: instrument.symbol,
        symbolName: instrument.displayName,
      });
    },
    [onInstrumentChange],
  );

  const onInstrumentChangeFromBind = useCallback(
    (symbolInfo: SymbolInfoInput): void => {
      onInstrumentChange(symbolInfo, true, false);
    },
    [onInstrumentChange],
  );

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

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

  useEffect(() => {
    triggerRelatedWidgetsToUpdateRef.current(currentSymbolInfoRef.current.symbolId);
  }, []);

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

        if (symbolId) {
          onInstrumentChange({ symbolId });
        }
      },
    );

    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();
    };
  }, [onInstrumentChange, widgetId]);

  return {
    dropDownOpen,
    setDropdownOpen,
    currentSymbolInfo,
    isWidgetHeaderContextMenuOpen,
    setIsWidgetHeaderContextMenuOpen,
    onDropInstrument,
    addInstrumentFromModal,
    isOver,
  };
}