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


import { createSlice } from '@reduxjs/toolkit';

import { CREATE_NEW_WORKSPACE_ID } from '@configs/appConfig';

import type { PayloadAction } from '@reduxjs/toolkit';
import type { Workspace, WorkspaceState } from 'types/Workspace';

const initialState: WorkspaceState = {
  workspaces: [],
};

type WorkspaceChangePayload<T extends Exclude<keyof Workspace, 'id'>> = Pick<Workspace, 'id' | T>;

const workspacesSlice = createSlice({
  name: 'workspace',
  // `createSlice` will infer the state type from the `initialState` argument
  initialState,
  reducers: {
    addWorkspace: (state, { payload }: PayloadAction<Workspace | Workspace[]>): void => {
      state.workspaces = state.workspaces.filter((workspace: Workspace) => workspace.id !== -1);
      if ((payload as Workspace[]).length && (payload as Workspace[]).length > 0) {
        state.workspaces = [...(payload as Workspace[])];
      } else {
        state.workspaces.push(payload as Workspace);
      }
    },
    removeWorkspace: (state, { payload }: PayloadAction<Workspace['id']>): void => {
      state.workspaces = state.workspaces.filter((workspace: Workspace) => workspace.id !== payload);
    },
    setWorkspaceName: (state, { payload }: PayloadAction<WorkspaceChangePayload<'name'>>) => {
      const changedWorkspaceIndex = state.workspaces.findIndex(({ id }) => payload.id === id);
      state.workspaces[changedWorkspaceIndex].name = payload.name;
    },
    setCurrentWorkspace: (state, { payload }: PayloadAction<Workspace['id']>) => {
      if (payload !== CREATE_NEW_WORKSPACE_ID) {
        state.workspaces = state.workspaces.filter((workspace: Workspace) => workspace.id !== CREATE_NEW_WORKSPACE_ID);
      }
      state.workspaces = state.workspaces.map((item) => ({
        ...item,
        isCurrent: payload === item.id,
      }));
    },
    togglePinWorkspace: (state, { payload }: PayloadAction<WorkspaceChangePayload<never>>) => {
      const changedWorkspace = state.workspaces.find(({ id }) => payload.id === id);
      if (changedWorkspace) {
        changedWorkspace.pinned = !changedWorkspace.pinned;
      }
    },
    setClosedWorkspace: (state, { payload }: PayloadAction<WorkspaceChangePayload<'saved'>>) => {
      const changedWorkspace = state.workspaces.find(({ id }) => payload.id === id);
      if (changedWorkspace) {
        changedWorkspace.saved = payload.saved;
      }
    },
    setWorkspaces: (state, { payload }: PayloadAction<Workspace[]>) => {
      state.workspaces = payload;
    },
    openWorkspace: (state, { payload }: PayloadAction<Workspace['id']>) => {
      const changedWorkspace = state.workspaces.find(({ id }) => payload === id);
      if (changedWorkspace) {
        changedWorkspace.saved = true;
      }
      state.workspaces = state.workspaces.map((item) => ({
        ...item,
        isCurrent: payload === item.id,
      }));
    },
    addToFavorites: (state, { payload }: PayloadAction<Partial<WorkspaceChangePayload<'name'>>>) => {
      const changedWorkspace = state.workspaces.find(({ id }) => id === payload.id);
      if (changedWorkspace) {
        changedWorkspace.favorite = true;
        if (payload.name) {
          changedWorkspace.name = payload.name;
        }
      }
    },
    removeFromFavorites: (state, { payload }: PayloadAction<Workspace['id']>) => {
      const changedWorkspace = state.workspaces.find(({ id }) => payload === id);
      if (changedWorkspace) {
        changedWorkspace.favorite = false;
      }
    },
    updateWorkspace: (state, { payload }: PayloadAction<Workspace>) => {
      const workspaceIndex = state.workspaces.findIndex((w) => w.id === payload.id);
      if (workspaceIndex !== -1) {
        state.workspaces[workspaceIndex] = payload;
      }
    },
    changeCurrentWorkspace: (state, { payload }: PayloadAction<Workspace['id']>) => {
      state.workspaces = state.workspaces.map((item) => ({
        ...item,
        isCurrent: payload === item.id,
      }));
      const workspace = state.workspaces.find((w) => payload === w.id);
      if (workspace) {
        workspace.saved = true;
      }
    },
    updateLayout: (
      state,
      { payload: { layout, id: payloadId } }: PayloadAction<Partial<WorkspaceChangePayload<'layout'>>>,
    ) => {
      const changedWorkspace = state.workspaces.find(({ id }) => id === payloadId);
      if (changedWorkspace && layout) {
        changedWorkspace.layout = layout;
      }
    },
  },
});

export const {
  addWorkspace,
  removeWorkspace,
  setCurrentWorkspace,
  setWorkspaceName,
  togglePinWorkspace,
  setClosedWorkspace,
  setWorkspaces,
  openWorkspace,
  addToFavorites,
  removeFromFavorites,
  updateWorkspace,
  changeCurrentWorkspace,
  updateLayout,
} = workspacesSlice.actions;

export default workspacesSlice.reducer;


















import { createSlice } from '@reduxjs/toolkit';

import { TSelectedTableType } from '@components/VirtualTable';
import { WIDGETS_CAN_BE_MULTIPLE_MASTERS, Z_INDEX } from '@configs/appConfig';
import { bind, isWidgetHasMaster, pinch, unbind } from '@utils/bind';
import { OrderbookWidgetConfig, Widget } from 'types/Widgets';

import type { PayloadAction } from '@reduxjs/toolkit';

export interface WidgetsState {
  widgets: Widget[];
  widgetsConfig: {
    glass: OrderbookWidgetConfig;
  };
}

const initialState: WidgetsState = {
  widgets: [],
  widgetsConfig: {
    glass: {
      view: 'four-col-3',
      showYield: false,
      showSpread: false,
      choosenInstrument: '',
    },
  },
};

export const columnKeys = {
  1: 'firstColumns',
  2: 'secondColumns',
  3: 'thirdColumns',
  4: 'forthColumns',
};

export type WidgetChangePayload<T extends Exclude<keyof Widget, 'id'>> = Pick<Widget, 'id' | T> & {
  withoutSend?: boolean;
} & { widgetTableColSizes?: Record<string, unknown> } & {
  moved?: boolean;
};

const getWidgetIndex = (widgetId: Widget['id'], widgets: Widget[]): number =>
  widgets.findIndex(({ id }) => id === widgetId);

const widgetsSlice = createSlice({
  name: 'workspace',
  // `createSlice` will infer the state type from the `initialState` argument
  initialState,
  reducers: {
    addWidget: (state, { payload }: PayloadAction<Widget | Widget[]>) => {
      if (Array.isArray(payload)) {
        if (payload.length > 0) {
          state.widgets = [...state.widgets, ...payload];
        }
      } else {
        state.widgets = [...state.widgets, payload];
      }
    },
    removeWidget: (state, { payload }: PayloadAction<Widget['id'] | Widget['id'][]>) => {
      const widgetsIdList = Array.isArray(payload) ? payload : [payload];

      // Поиск виджетов с удалённым мастером
      state.widgets = state.widgets.filter(({ id }) => !widgetsIdList.includes(id));

      // Поиск и удаление связи с удалённым мастер виджетом
      const slaveWidgets = state.widgets.filter((w) =>
        widgetsIdList.some((widgetId) => isWidgetHasMaster(w, widgetId)),
      );
      slaveWidgets.forEach((slaveWidget) => {
        widgetsIdList.forEach((masterId) => unbind(slaveWidget, masterId));
      });
    },
    setZIndexForWidget: (state, { payload }: PayloadAction<WidgetChangePayload<'zIndex'>>) => {
      const changedWidgetIndex = getWidgetIndex(payload.id, state.widgets);
      if (changedWidgetIndex === -1) {
        return;
      }
      state.widgets[changedWidgetIndex].zIndex = payload.zIndex;
      state.widgets[changedWidgetIndex].withoutSend = false;
    },
    setSizesForWidget: (state, { payload }: PayloadAction<WidgetChangePayload<'sizes'>>) => {
      const changedWidgetIndex = getWidgetIndex(payload.id, state.widgets);
      state.widgets[changedWidgetIndex].withoutSend = !!payload.withoutSend;
      state.widgets[changedWidgetIndex].sizes = payload.sizes;
    },
    setWidgetSizeLimits: (state, { payload }: PayloadAction<WidgetChangePayload<'sizeLimits'>>) => {
      const changedWidgetIndex = getWidgetIndex(payload.id, state.widgets);
      if (!state.widgets[changedWidgetIndex]?.sizeLimits) {
        return;
      }
      state.widgets[changedWidgetIndex].sizeLimits = payload.sizeLimits;
    },
    setPositionForWidget: (state, { payload }: PayloadAction<WidgetChangePayload<'position'>>) => {
      const changedWidgetIndex = getWidgetIndex(payload.id, state.widgets);
      state.widgets[changedWidgetIndex].withoutSend = !!payload.withoutSend;
      state.widgets[changedWidgetIndex].position = {
        ...state.widgets[changedWidgetIndex].position,
        x: payload.position.x,
        y: payload.position.y,
      };
      state.widgets[changedWidgetIndex].moved = !!payload.moved;
    },
    addContentPropsToWidget: (state, { payload }: PayloadAction<WidgetChangePayload<'widgetContentProps'>>) => {
      const changedWidgetIndex = getWidgetIndex(payload.id, state.widgets);
      if (changedWidgetIndex !== -1) {
        if (!payload.withoutSend) {
          // делаем запрос после обновления стора
          state.widgets[changedWidgetIndex].withoutSend = false;
        }
        state.widgets[changedWidgetIndex].widgetContentProps = {
          ...state.widgets[changedWidgetIndex].widgetContentProps,
          ...payload.widgetContentProps,
        };
      }
    },
    updateWidgetContentProps: (
      state,
      {
        payload,
      }: PayloadAction<{
        id: number;
        withoutSend?: boolean;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any -- конкретный тип задается при dispatch
        updater: (draft: any) => void;
      }>,
    ) => {
      const changedWidgetIndex = getWidgetIndex(payload.id, state.widgets);
      if (changedWidgetIndex !== -1) {
        if (!payload.withoutSend) {
          // делаем запрос после обновления стора
          state.widgets[changedWidgetIndex].withoutSend = false;
        }
        payload.updater(state.widgets[changedWidgetIndex].widgetContentProps ?? {});
      }
    },
    setColumnsWidthToWidget: (
      state,
      {
        payload,
      }: PayloadAction<{
        widgetId: number;
        widgetTableColSizes: {
          1?: string[];
          2?: string[];
          3?: string[];
          4?: string[];
        };
        selectedTableType: TSelectedTableType;
      }>,
    ) => {
      const changedWidgetIndex = getWidgetIndex(payload.widgetId, state.widgets);

      const columnKey = columnKeys[payload.selectedTableType];
      const columns = state.widgets[changedWidgetIndex].widgetContentProps.state[columnKey].map(
        (column: Record<string, unknown>) => ({
          ...column,
        }),
      );
      let index = 0;
      const { selectedTableType } = payload;
      const tableColSizes = payload.widgetTableColSizes[selectedTableType];

      if (tableColSizes) {
        columns.forEach((column: Record<string, unknown>) => {
          if (!column.hidden && tableColSizes[index]) {
            // eslint-disable-next-line no-param-reassign -- в редьюсере можно
            column.width = tableColSizes[index];
            index += 1;
          }
        });
      }

      state.widgets[changedWidgetIndex].widgetContentProps.state[columnKey] = columns;
    },

    setWidgetName: (state, { payload }: PayloadAction<WidgetChangePayload<'name'>>) => {
      const changedWidgetIndex = getWidgetIndex(payload.id, state.widgets);
      if (changedWidgetIndex > -1) {
        state.widgets[changedWidgetIndex].name = payload.name;
      }
    },
    setWidgetExpand: (
      state,
      { payload }: PayloadAction<WidgetChangePayload<'isExpand' | 'beforeExpandParams' | 'position' | 'sizes'>>,
    ) => {
      const changedWidgetIndex = getWidgetIndex(payload.id, state.widgets);
      if (changedWidgetIndex > -1) {
        state.widgets[changedWidgetIndex].isExpand = payload.isExpand;
        state.widgets[changedWidgetIndex].beforeExpandParams = payload.beforeExpandParams;
        state.widgets[changedWidgetIndex].position = payload.position;
        state.widgets[changedWidgetIndex].sizes = payload.sizes;
      }
    },
    setWidgetParentWorkspaceId: (
      state,
      { payload }: PayloadAction<WidgetChangePayload<'workspaceId'> | WidgetChangePayload<'workspaceId'>[]>,
    ) => {
      if ((payload as WidgetChangePayload<'workspaceId'>[]).length > 0) {
        (payload as WidgetChangePayload<'workspaceId'>[]).forEach(({ id, workspaceId }) => {
          const changedWidgetIndex = getWidgetIndex(id, state.widgets);
          if (changedWidgetIndex > -1) {
            state.widgets[changedWidgetIndex].workspaceId = workspaceId;
          }
        });
      } else {
        const { id, workspaceId } = payload as WidgetChangePayload<'workspaceId'>;
        const changedWidgetIndex = getWidgetIndex(id, state.widgets);
        if (changedWidgetIndex > -1) {
          state.widgets[changedWidgetIndex].workspaceId = workspaceId;
        }
      }
    },
    addWidgetBinding: (
      state,
      {
        payload,
      }: PayloadAction<{
        masterId: Widget['id'];
        idList: Widget['id'][];
      }>,
    ) => {
      // предотвращает бесконечный цикл подписок
      const masterWidget = state.widgets.find((w) => w.id === payload.masterId);
      if (masterWidget && masterWidget?.master && payload.idList.includes(masterWidget?.master)) {
        unbind(masterWidget, masterWidget?.master);
      }

      state.widgets
        .filter((w) => !payload.idList.includes(w.id) && isWidgetHasMaster(w, payload.masterId))
        .forEach((w) => unbind(w, payload.masterId));

      const canAllMastersBeMultipleMasters = (current: Widget) => {
        const canAllAlreadyMastersBeMultipleMasters = state.widgets
          .filter((w) => current.master === w.id || current.masters?.includes(w.id))
          .every(({ type }) => WIDGETS_CAN_BE_MULTIPLE_MASTERS.includes(type));

        const canCurrentMasterBeMultipleMaster =
          !!masterWidget?.type && WIDGETS_CAN_BE_MULTIPLE_MASTERS.includes(masterWidget.type);

        return canAllAlreadyMastersBeMultipleMasters && canCurrentMasterBeMultipleMaster;
      };
      state.widgets
        .filter((w) => payload.idList.includes(w.id))
        .forEach((w) => bind(w, payload.masterId, canAllMastersBeMultipleMasters(w)));
    },
    addWidgetBindingProperties: (
      state,
      {
        payload,
      }: PayloadAction<{
        widgetId: Widget['id'];
        masterWidgetId: Widget['id'];
        externalProperties: Record<string, string>;
      }>,
    ) => {
      const changedWidgetIndex = getWidgetIndex(payload.widgetId, state.widgets);
      if (changedWidgetIndex === -1) {
        return;
      }
      if (payload.masterWidgetId) {
        if (state.widgets[changedWidgetIndex].externalProperties) {
          state.widgets[changedWidgetIndex].externalProperties = [payload.externalProperties];
        } else {
          state.widgets[changedWidgetIndex].externalProperties = [payload.externalProperties];
        }
      } else {
        state.widgets[changedWidgetIndex].externalProperties = null;
      }
    },
    // Фактически не используется
    setBinding: (state, { payload }: PayloadAction<{ widgetId: number; master: number | null; masters: number[] }>) => {
      const changedWidgetIndex = getWidgetIndex(payload.widgetId, state.widgets);
      if (changedWidgetIndex > -1) {
        state.widgets[changedWidgetIndex].master = payload.master;
        state.widgets[changedWidgetIndex].masters = payload.masters;
      }
    },
    unbindWidgets: (state, { payload }: PayloadAction<{ widgetId: Widget['id'] }>) => {
      if (payload.widgetId) {
        const changedWidgetIndex = getWidgetIndex(payload.widgetId, state.widgets);
        if (changedWidgetIndex > -1) {
          state.widgets[changedWidgetIndex].master = null;
          state.widgets[changedWidgetIndex].masters = [];
        }
      } else {
        state.widgets.forEach((item) => {
          // eslint-disable-next-line no-param-reassign -- в редьюсере можно
          item.master = null;
          // eslint-disable-next-line no-param-reassign -- в редьюсере можно
          item.masters = [];
        });
      }
    },
    markAsActiveMaster: (state, { payload }: PayloadAction<{ widgetId: Widget['id'] }>) => {
      state.widgets.filter((w) => isWidgetHasMaster(w, payload.widgetId)).forEach((w) => pinch(w, payload.widgetId));
    },
    setWidgetFocus: (state, { payload }: PayloadAction<{ widgetId: Widget['id']; inFocus: boolean }>) => {
      const changedWidgetIndex = getWidgetIndex(payload.widgetId, state.widgets);
      if (changedWidgetIndex > -1) {
        state.widgets[changedWidgetIndex].inFocus = payload.inFocus;
      }
    },

    optimizeWidgetsZIndexes: (
      state,
      { payload }: PayloadAction<{ workspaceId: number; widgetIdWithMaxZIndex: Widget['id']; basicIndex?: number }>,
    ) => {
      const { workspaceId, widgetIdWithMaxZIndex, basicIndex = Z_INDEX.WIDGET_BASE } = payload;

      const widgetWithMaxZIndex = state.widgets.find(
        (widget) => widget.workspaceId === workspaceId && widget.id === widgetIdWithMaxZIndex,
      );
      if (!widgetWithMaxZIndex) {
        return;
      }

      const changedWidgets = state.widgets
        .filter((widget) => widget.workspaceId === workspaceId && widget.id !== widgetWithMaxZIndex.id)
        .sort((a, b) => a.zIndex - b.zIndex)
        .concat(widgetWithMaxZIndex);

      changedWidgets.forEach((widget, index) => {
        // eslint-disable-next-line no-param-reassign -- в редьюсере можно
        widget.zIndex = index + basicIndex;
        // eslint-disable-next-line no-param-reassign -- в редьюсере можно
        widget.withoutSend = false;
      });
    },
  },
});

export const {
  addWidget,
  removeWidget,
  setZIndexForWidget,
  setPositionForWidget,
  setSizesForWidget,
  setWidgetSizeLimits,
  addContentPropsToWidget,
  setColumnsWidthToWidget,
  setWidgetName,
  setWidgetExpand,
  setWidgetParentWorkspaceId,

  addWidgetBinding,
  addWidgetBindingProperties,

  unbindWidgets,

  markAsActiveMaster,
  setWidgetFocus,
  updateWidgetContentProps,
  optimizeWidgetsZIndexes,
} = widgetsSlice.actions;

export { initialState as widgetsInitialState };
export default widgetsSlice.reducer;



















import { createSlice, PayloadAction } from '@reduxjs/toolkit';

export interface PublicContextItem {
  id: number;
  color?: string | null;
  publicProperties?: { name: string | null; value: string | number | null }[];
  name?: string | null;
  workspaceId?: number;
  isRemoval?: boolean;
}

interface publicContextState {
  publicContext: PublicContextItem[];
}

const initialState: publicContextState = {
  publicContext: [],
};

const publicContextSlice = createSlice({
  name: 'publicContext',
  initialState,
  reducers: {
    addValuesToPublicContext(state, { payload }: PayloadAction<PublicContextItem>) {
      const index = state.publicContext.findIndex((item) => item.id === payload.id);

      if (index === -1) {
        state.publicContext.push(payload);
      } else {
        state.publicContext[index] = { ...state.publicContext[index], ...payload };
      }
    },

    createValuesToPublicContext: (state, { payload }: PayloadAction<PublicContextItem[]>) => {
      if (payload.length > 0) {
        state.publicContext = [...payload];
      } else {
        state.publicContext = [];
      }
    },
  },
});

export const { addValuesToPublicContext, createValuesToPublicContext } = publicContextSlice.actions;

export default publicContextSlice.reducer;


















import type { StorageDataType } from './StorageData';
import type { SplitLayoutState } from '@terminal/desktop/workspaces/mesh/types';

export type SystemWorkspaceConfig =
  | 'currencyMarket'
  | 'negotiations'
  | 'bonds'
  | 'monitor'
  | 'monitor2'
  | 'stocks'
  | 'NTBStock';

export type WorkspaceType = 'Bonds' | 'Currency' | 'Talks' | 'Stocks' | 'NTBStock';

export interface Workspace {
  id: number;
  name: string;
  isCurrent: boolean;
  position: number;
  isAddWidgets: boolean;
  saved?: boolean;
  pinned?: boolean;
  isNew?: boolean;
  code?: string;
  favorite?: boolean;
  closedAt?: string;
  layout?: SplitLayoutState;
}

export interface WorkspaceState {
  workspaces: Workspace[];
}

export enum WorkspaceGroups {
  Currency = 'currency',
  Ntb = 'ntb',
  Spfi = 'spfi',
  Stocks = 'stocks',
  Bonds = 'bonds',
  Market = 'market',
  Trading = 'trading',
  Cci = 'cci',
  DepositCcp = 'depositCcp',
}

export type LibraryWorkspace = {
  id: LibraryWorkspaceType;
  name: string;
  groups: WorkspaceGroups[];
};

/** Все типы столов, используемых в библиотеке шаблонов */
export type LibraryWorkspaceType =
  | WorkspaceType
  | 'corporateInformationCenter'
  | 'spfiScreen'
  | 'spfiView'
  | 'spfiBroker'
  | 'spfiScreenExtended'
  | 'bondsLargeScreen'
  | 'bondsComparison'
  | 'bondsMonitoringLargeScreen'
  | 'municipalBonds'
  | 'corporateBonds'
  | 'govermentBonds'
  | 'currencyBonds'
  | 'repoMidSizeScreen'
  | 'ntb'
  | 'ntbIndexes'
  | 'ntbLogistic'
  | 'dealingFunctionality'
  | 'marketOverview'
  | 'instrumentListsByAllMarkets'
  | 'stocksAndIndexes'
  | 'currencyMarket'
  | 'depositsCcp';

export type WorkspaceAutoCreateConfig = {
  checkPermission: (permissions: string[]) => boolean;
  workspaceJsonName: LibraryWorkspaceType;
  storageKey: keyof StorageDataType;
  featureFlag: boolean;
};

















import type { Workspace } from './Workspace';
import type { ViewType } from '@widgets/Orderbook/types';
import type { Position } from 'react-rnd';

export interface WidgetPosition {
  x: number;
  y: number;
  widgetNumber?: number;
  maxX?: number;
  maxY?: number;
}

export enum WidgetContentType {
  testIFrame = 'testIFrame',
  graphic = 'graphic',
  news = 'news',
  newsIss = 'newsIss',
  corporateActions = 'corporateActions',
  glass = 'glass',
  instruments = 'instruments',
  noTradeChat = 'noTradeChat',
  HHI = 'HHI',
  contacts = 'contacts',
  aboutInstruments = 'aboutInstruments',
  fixings = 'fixings',
  FUTOI = 'FUTOI',
  curves = 'curves',
  // Виджет Карта рынка облигаций
  // для совестимости с предыдущей версией названия из  /tb/api/v1/users/current -> widgetTypes
  profitabilityAndDuration = 'profitabilityAndDuration',
  widgetTurnovers = 'widgetTurnovers',
  tradingResult = 'tradingResult',
  quotesNTPro = 'quotesNTPro',
  instruction = 'instruction',
  macroData = 'macroData',
  otcTurnover = 'otcTurnover',
  techSupport = 'techSupport',
  screener = 'screener',
  review = 'review',
  helpCenter = 'helpCenter',
  ordersJournal = 'ordersJournal',
  indicativeQuotes = 'indicativeQuotes',
  swapCalculator = 'swapCalculator',
  spfiPrices = 'spfiPrices',
  issuerCard = 'issuerCard',
  ntbOrdersAndDeals = 'ntbOrdersAndDeals',
  ntbPositions = 'ntbPositions',
  ntbLogisticAuto = 'ntbLogisticAuto',
  ntbLogisticFreight = 'ntbLogisticFreight',
  ntbLogisticContainers = 'ntbLogisticContainers',
  ntbAnaliticsCommodities = 'ntbAnaliticsCommodities',
  ntbAnalyticsContracts = 'ntbAnalyticsContracts',

  tradeJournal = 'tradeJournal',
  draftBrokerSpfi = 'draftBrokerSpfi',
  tradeJournalDetails = 'tradeJournalDetails',
  ntbIndexes = 'ntbIndexes',
  depositCcpOrderTables = 'depositCcpOrderTables',
  depositCcpTradeTables = 'depositCcpTradeTables',
  depositCcpReferenceTables = 'depositCcpReferenceTables',
  depositCcpRiskTables = 'depositCcpRiskTables',
  userAdministration = 'userAdministration',
  mxtAccountPositions = 'mxtAccountPositions',
}

export interface WidgetSizes {
  /** 'px' */
  minHeight?: string;
  /** '%' */
  height: string;
  /** 'px' */
  minWidth?: string;
  /** '%' */
  width: string;
  /** '%' */
  restWidth?: number;
}

export interface WidgetSizeLimits {
  minHeight: number; // px
  minWidth: number; // px
  maxWidth?: number; // px
  maxHeight?: number; // px
}

export interface BeforeWidgetExpandParams {
  position: Position;
  sizes: WidgetSizes;
}

export interface ICreateWidgetByDragProps {
  sizes?: WidgetSizes;
  position?: WidgetPosition;
}

export interface Widget {
  name: string;
  sizes: WidgetSizes;
  sizeLimits?: WidgetSizeLimits;
  zIndex: number;
  id: number;
  workspaceId: Workspace['id'];
  position: WidgetPosition;
  type: WidgetContentType;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: типипзировать
  widgetContentProps: Record<string, any>;
  isExpand: boolean;
  beforeExpandParams: BeforeWidgetExpandParams | null;
  /** @deprecated Для множественной связки используется более универсальное поле masters */
  master?: number | null;
  /** Для определения мастеров при связке (binding) виджетов. */
  masters?: number[] | null;
  externalProperties?: { [key: string]: string | number }[] | null;
  withoutSend?: boolean;
  moved?: boolean;
  inFocus?: boolean;
  /**
   *
   */
  isStacked?: boolean;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: типипзировать
export interface WidgetContentBasicProps<T = any> {
  widgetId: Widget['id'];
  widgetContentProps?: T;
  onWidgetContentClick?: () => void;
  refWrapper?: React.MutableRefObject<HTMLDivElement | null>;
  /** undefined - не отображаем, null - дизейблим */
  handlerSaveAsExcel?: (() => void) | null;
  isMobileView?: boolean;
  style?: Record<string, string>;
  className?: string;
  // максимальное число toast-ов в рамках виджета
  maxToastsCount?: number;
}

export interface PackedPropsProperties {
  key: string;
  value: string | number | boolean | undefined | null;
}

export interface WidgetPackedProps {
  widgetId: number;
  properties: PackedPropsProperties[];
  isRemoval?: boolean;
}

export interface OrderbookWidgetConfig {
  view: ViewType;
  showYield: boolean;
  showSpread: boolean;
  choosenInstrument: string;
}
















import { AxiosResponse } from 'axios';
import { call, put } from 'typed-redux-saga';

import { storageController } from '@api/controllers/customStorage';
import { isShowMarketProfileModal, openMarketProfileModalAndUpdateStorage } from '@components/MarketProfileModal/utils';
import { TOURS_DATA } from '@modules/tours';

import { addStorageData } from '@store/slices/cashedData';
import { StorageDataType } from 'types/StorageData';

import type { SagaGenerator } from 'typed-redux-saga';

export function* initStorageData(): SagaGenerator<void> {
  try {
    const response: AxiosResponse<StorageDataType> = yield* call(() => storageController.getAllCustomFields());
    const res = response.data;

    yield* put(addStorageData(res));

    if (isShowMarketProfileModal(res)) {
      yield* call(openMarketProfileModalAndUpdateStorage);
    } else {
      const introTour = TOURS_DATA?.find((el) => el.id === 'intro-tour');

      if (introTour?.onStart) {
        yield* call([introTour, introTour.onStart], res);
      }
    }
  } catch {
    // Ошибка при получении данных в storage — не блокирует
  }
}


















import { createSelector } from '@reduxjs/toolkit';

import { RootState } from '@store/store';
import { createFieldSelector } from '@store/utils/createFieldSelector';
import { Permissions } from 'types/User';

export const userPersonalDataSelector = createFieldSelector((state) => state.userSlice.personalData);

export const userInfoSelector = createFieldSelector((state) => state.userSlice.info);

export const userPrivacySettingsSelector = createFieldSelector((state) => state.userSlice.privacySettings);

export const permissionsSelector = (state: RootState) => state.userSlice.permissions;

export const isSPFITraderSelector = (state: RootState) => permissionsSelector(state).includes(Permissions.SPFI_USER);

export const isSPFIBrokerSelector = (state: RootState) => permissionsSelector(state).includes(Permissions.SPFI_BROCKER);

// TODO: Сейчас есть 2 правила. Нужно будет разобраться
export const isTraderSelector = (state: RootState) => permissionsSelector(state).includes(Permissions.TRADER);

export const checkMxtTrading = (state: RootState) => permissionsSelector(state).includes(Permissions.MXT_TRADING);

export const isAnalyticSelector = (state: RootState) => state.userSlice.info?.bookbuilderRoles.includes('Аналитик');
export const isTraderRoleSelector = (state: RootState) => state.userSlice.info?.bookbuilderRoles.includes('Трейдер');

export const isContributorSelector = (state: RootState) => permissionsSelector(state).includes(Permissions.CONTRIBUTOR);

export const isAgroTraderSelector = (state: RootState) => permissionsSelector(state).includes(Permissions.AGRO_TRADER);

const isAgroViewerSelector = (state: RootState) => permissionsSelector(state).includes(Permissions.AGRO_VIEWER);

export const isNtbUserSelector = (state: RootState) => {
  const permissions = permissionsSelector(state);
  return permissions.includes(Permissions.AGRO_VIEWER) || permissions.includes(Permissions.AGRO_TRADER);
};

export const profileEditModeSelector = (state: RootState) => state.userSlice.editMode;

export const userAvatarSelector = (state: RootState) => state.userSlice.avatarUrl;

export const userEmailSelector = (state: RootState): string => state.userSlice.info?.email || '';

export const userFullNameSelector = (state: RootState): string => state.userSlice.info?.fullName || '';

export const userTRIdSelector = (state: RootState): string | null => state.userSlice.info?.trId || null;

/** deprecated, По возможности не использовать этот селектор, а обращаться к ролевой модели */
const isTradePermission = (state: RootState) => state.userSlice.info?.isTradePermission;

export const formOptionsSelector = createFieldSelector((state) => state.userSlice.formOptions);

export const userTradingAccessesSelector = (state: RootState) => state.userSlice.userTradingAccesses;

export const tradeTimePermissionsSelector = (state: RootState) =>
  state.userSlice.userTradingAccesses?.tradeTimePermissions;

/** Map, где ключ - key инструмента, а значение - информация о торгах (статус, время) */
export const tradeTimePermissionsMapSelector = createSelector(
  [tradeTimePermissionsSelector],
  (permissions) => new Map(permissions?.map((p) => [p.key, p])),
);
















import { createSelector } from '@reduxjs/toolkit';
import { shallowEqual } from 'react-redux';

import { RootState } from '@store/store';
import { createFieldSelector } from '@store/utils/createFieldSelector';
import { WidgetContentType } from 'types/Widgets';

export const widgetsSelector = (state: RootState) => state.widgets.widgets;

export const widgetSizesSelector = (widgetId: number) => (state: RootState) =>
  state.widgets.widgets.find(({ id }) => id === widgetId)?.sizes;

export const createWidgetsSelectorByWorkspaceId = (workspaceId: number) =>
  createSelector([(state: RootState) => state.widgets.widgets], (widgets) =>
    widgets.filter((w) => w.workspaceId === workspaceId),
  );

export const widgetByIdSelector = (id: number) => (state: RootState) =>
  state.widgets.widgets.find((item) => item.id === id);

export const masterWidgetSelector = (widgetId: number) => (state: RootState) => {
  const masterId = widgetByIdSelector(widgetId)(state)?.master;
  return masterId ? widgetByIdSelector(masterId)(state) : undefined;
};

export const widgetsByWorkspaceIdAndTypeSelector = (workspaceId: number, type: WidgetContentType) =>
  createSelector([widgetsSelector], (widgets) =>
    widgets.filter((item) => item.workspaceId === workspaceId && item.type === type),
  );

export const getSlaveWidgetsByIdSelector = (widgetId: number) =>
  createSelector(
    [widgetsSelector],
    (widgets) =>
      widgets.filter((elem) => elem?.master === widgetId || elem?.masters?.includes(widgetId)).map((elem) => elem.id),
    {
      memoizeOptions: {
        maxSize: 10,
        resultEqualityCheck: shallowEqual,
      },
    },
  );

















import { createSelector } from '@reduxjs/toolkit';
import isEqual from 'lodash/isEqual';

import { RootState } from '@store/store';
import { createFieldSelector } from '@store/utils/createFieldSelector';
import { getTheNewestWidget } from '@utils/getTheNewestWidget';
import { WidgetContentType } from 'types/Widgets';
import { Workspace } from 'types/Workspace';

import { widgetsSelector } from './widgets';

export const workspacesSelector = (state: RootState) => state.workspaces.workspaces;

export const currentWorkspaceIdSelector = (state: RootState) =>
  state.workspaces.workspaces.find(({ isCurrent }) => isCurrent)?.id;

export const workspaceWidgetsSelector = createSelector(
  [widgetsSelector, currentWorkspaceIdSelector],
  (wgts, currentWorkspaceId) => wgts.filter(({ workspaceId }) => workspaceId === currentWorkspaceId),
  {
    memoizeOptions: {
      maxSize: 10,
      resultEqualityCheck: isEqual,
    },
  },
);

export const savedWorkspacesSelector = createSelector(workspacesSelector, (workspaces) =>
  workspaces.filter((item) => item.saved),
);

export const closedWorkspacesSelector = createSelector(workspacesSelector, (workspaces) =>
  workspaces.filter((item) => !item.saved),
);

export function workspaceByIdSelector(id: number): (state: RootState) => Workspace | undefined;
export function workspaceByIdSelector<K extends keyof Workspace>(
  id: number,
  field: K,
): (state: RootState) => Workspace[K] | undefined;
export function workspaceByIdSelector(id: number, field?: keyof Workspace) {
  return (state: RootState) => {
    const workspace = state.workspaces.workspaces.find((w) => w.id === id);
    if (field !== undefined) {
      return workspace?.[field];
    }
    return workspace;
  };
}

export const selectIsCurrentWorkspaceNew = (state: RootState) =>
  state.workspaces.workspaces.find(({ isCurrent }) => isCurrent)?.isNew ?? false;

export const currentWorkspaceSelector = (state: RootState) =>
  state.workspaces.workspaces.find(({ isCurrent }) => isCurrent);

export const currentWorkspaceLayoutSelector = createSelector(
  currentWorkspaceSelector,
  (workspace) => workspace?.layout,
);
export const workspaceByNameSelector = (name: string) =>
  createFieldSelector((state: RootState) => workspacesSelector(state).find((w) => w.name === name));

export const workspacesNamesSelector = createSelector(workspacesSelector, (workspaces) =>
  workspaces.map((w) => w.name),
);

export const newestWidgetIdSelector = createSelector(
  [widgetsSelector, (_: RootState, type: WidgetContentType) => type],
  (widgets, type) => getTheNewestWidget(widgets, type)?.id,
);



















/* eslint-disable no-continue */
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Widget, WidgetPackedProps } from 'types/Widgets';

export function packWidgetProps(value: Record<string, any>, propertyPrefix?: string): WidgetPackedProps {
  const result = Object.keys(value)
    .map((key) => {
      const val = value[key];
      const isSaveAsString = typeof val === 'string' && !Number.isNaN(Number(val)) && val !== '';
      // если есть префикс, значит функция вызвана в рекурсии и нужно
      // сосласться на предыдущее проперти, таким образом "зашифровав"
      // объект

      const propertyPrefixKey = isSaveAsString ? `${propertyPrefix}_${key}_asString` : `${propertyPrefix}_${key}`;
      const newKey = propertyPrefix ? propertyPrefixKey : key;

      // если значение - объект(объектом в том числе является и массив)
      // то уходим в рекурсию
      const newValue =
        typeof val === 'object' && val !== undefined && val !== null ? packWidgetProps(val, newKey) : val;
      // если значение получено из рекурсии надо сделать массив
      // объектом
      if (typeof newValue === 'object' && newValue !== undefined && newValue !== null) {
        return Object.keys(newValue)
          .map((k) => ({ ...newValue[k] }))
          .reduce(
            (acc, curr) => ({
              ...acc,
              ...curr,
            }),
            {},
          );
      }
      // бэк не умеет работать с null значением нпрямую, поэтому с фронта
      // передаем его строкой
      return {
        [newKey]: newValue !== null && newValue !== undefined ? newValue : 'null',
      };
    })
    .flat(Infinity);
  // если нет префикса, значит возвращаемся из первого вызова, и нужно свернуть
  // массив для передачи на бэк, в противном случае возвращаем массив
  return !propertyPrefix
    ? result.reduce(
        (acc, curr) => {
          Object.entries(curr).forEach(([key, val]) => {
            if (key === 'id') {
              acc.widgetId = val;
            } else {
              acc.properties.push({ key, value: val });
            }
          });
          return acc;
        },
        { widgetId: undefined, properties: [] },
      )
    : result;
}

function convertTypesFromString(value: WidgetPackedProps['properties']): WidgetPackedProps['properties'] {
  return Object.entries(value).reduce(
    (acc, curr) => {
      const [, v] = curr;
      const { key } = v;
      const val = v.value;
      if (typeof val !== 'string') {
        return { ...acc, [key as string]: val };
      }
      if (val === 'false') {
        return { ...acc, [key as string]: false };
      }
      if (val === 'true') {
        return { ...acc, [key as string]: true };
      }
      if (val === 'undefined') {
        return { ...acc, [key as string]: undefined };
      }
      if (val === 'null') {
        return { ...acc, [key as string]: null };
      }

      // не используем тут parseFloat или parseInt, так как он преобразует
      // строковые значения в числа, если строки содержат в себе любое число
      const numberVal = Number(val);

      if (!Number.isNaN(numberVal) && val !== '' && !key.includes('asString')) {
        return { ...acc, [key as string]: numberVal };
      }

      return { ...acc, [key as string]: val };
    },
    [] as WidgetPackedProps['properties'],
  );
}

function unpackObject(value: Record<string, any> | any[]): any {
  const result = Array.isArray(value) ? [] : ({} as any);
  const valuesEntries = Object.entries(value).map(([key, val]) => [key.split('_'), val]);
  valuesEntries.forEach(([keys, val]) => {
    const [firstKey] = keys as string[];
    // если ключей больше 1, значит перед нами объект и мы должны
    // найти все ключи которые содержат в себе firstKey, объединить
    // их в объект и уйти в рекурсию
    const isArray = !Number.isNaN(Number(keys[1]));
    if (keys.length > 1 && result[firstKey] === undefined) {
      const middler = valuesEntries
        .filter(([k]) => k[0] === firstKey)
        .reduce(
          (acc, curr) => {
            const [k, v] = curr;
            const newKey = k.join('_').replace(`${firstKey}_`, '');
            acc[newKey] = v;
            return acc;
          },
          isArray ? [] : ({} as any),
        );
      result[firstKey] = unpackObject(middler);
    } else if (result[firstKey] === undefined) {
      result[firstKey] = val;
    }
  });

  return result;
}

export function unpackWidgetProps(v: WidgetPackedProps): Widget {
  const { widgetId, properties } = v;
  // бэк отдает нам все значение в виде строк, поэтом нам нужно
  // преобразовать их в исходные типы данных
  const convertedProperties = Object.entries(convertTypesFromString(properties as any)).reduce((acc, curr) => {
    const [key, value] = curr;
    acc[key.replace('_asString', '')] = value;
    return acc;
  }, {} as any);
  /*
  Так как бэк не понимает, что некоторые проперти, которые в одном случае могли бы быть 
  объектам, могут быть null(например beforeExpandParams), то нам необходимо отфильтровать
  пустые значения если есть не пустые ключи со схожим началом в ключах это и проделывается
  в блоке кода на строках 130-164
  */
  const propertiesKeys = Object.keys(convertedProperties) as (keyof typeof convertedProperties)[];

  const cleanProperties = {} as typeof convertedProperties;

  for (let i = 0; i < propertiesKeys.length; i += 1) {
    const key = propertiesKeys[i] as string;
    const val = convertedProperties[key];

    if (val === null) {
      const spitted = (key as string).split('_');
      const sameKeys = propertiesKeys.filter((k) => (k as string).includes(key));
      // если нет схожик ключей - значит параметр уникален
      // и не имеет более глубоких дублеров
      if (sameKeys.length === 0) {
        cleanProperties[key] = val;
        continue;
      }

      let flag = true;

      for (let j = 0; j < sameKeys.length; j += 1) {
        // если все части исходного ключа содержатся в дублере,
        // то исходный ключ не должен идти в конечный результат
        if (spitted.every((item) => sameKeys.includes(item))) {
          flag = false;
          break;
        }
      }

      if (flag) {
        cleanProperties[key] = val;
      }
    } else {
      cleanProperties[key] = val;
    }
  }
  const unpackedProperties = unpackObject(cleanProperties);
  return {
    id: widgetId,
    ...unpackedProperties,
  };
}