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


import {
  ArrowLeft,
  ArrowRightLeft,
  Ban,
  Bell,
  Bookmark,
  Calculator,
  Calendar,
  CalendarCheck,
  ChartColumnDecreasing,
  ChartLine,
  ChartNoAxesColumnIncreasing,
  ChartNoAxesCombined,
  ChartPie,
  Check,
  ChevronDown,
  ChevronLeft,
  ChevronRight,
  ChevronUp,
  CircleAlert,
  CircleArrowRight,
  CircleCheck,
  CircleChevronLeft,
  CircleChevronRight,
  CircleMinus,
  CircleQuestionMark,
  CircleX,
  Columns2,
  Columns3,
  Copy,
  Copyright,
  EllipsisVertical,
  Expand,
  Eye,
  EyeClosed,
  File,
  FilePlusCorner,
  FileUp,
  FileX,
  FolderPlus,
  Forward,
  Fullscreen,
  Headset,
  Image,
  ImagePlus,
  Inbox,
  Info,
  LaptopMinimal,
  Link,
  List,
  ListFilter,
  ListSortAscending,
  ListSortDescending,
  LoaderCircle,
  Lock,
  LockOpen,
  LogOut,
  Mail,
  Megaphone,
  Menu,
  MessageSquare,
  MessageSquareText,
  MessagesSquare,
  Minus,
  MonitorDown,
  MoveDown,
  MoveUp,
  Paperclip,
  Pencil,
  Phone,
  Pin,
  Plus,
  Printer,
  RefreshCw,
  Reply,
  RotateCcw,
  RotateCw,
  Save,
  Search,
  SendHorizontal,
  Settings,
  Share,
  ShieldCheck,
  Shrink,
  Square,
  SquareSplitHorizontal,
  Table2,
  Trash2,
  TriangleAlert,
  Underline,
  UserPlus,
  UserRound,
  Users,
  X,
  Zap,
} from 'lucide-react';

import { ReactComponent as InfoOutlined } from '../../../public/project-icons/info/info-outlined.svg';

import { ReactComponent as BlockRounded } from './icons/migration/block/block-rounded.svg';
import { ReactComponent as Calculate } from './icons/migration/calculate/calculate.svg';
import { ReactComponent as ChevronLeftOutlined } from './icons/migration/chevron-left/chevron-left-outlined.svg';
import { ReactComponent as CompareArrowsRounded } from './icons/migration/compare-arrows/compare-arrows-rounded.svg';
import { ReactComponent as EmailOutLined } from './icons/migration/email/email-outlined.svg';
import { ReactComponent as FileCopyOutlined } from './icons/migration/file-copy/file-copy-outlined.svg';
import { ReactComponent as ForumOutlined } from './icons/migration/forum/forum-outlined.svg';
import { ReactComponent as HeadsetMicOutlined } from './icons/migration/headset-mic/headset-mic-outlined.svg';
import { ReactComponent as LinkRounded } from './icons/migration/link/link-rounded.svg';
import { ReactComponent as MoveToInboxRounded } from './icons/migration/move-to-inbox/move-to-inbox-rounded.svg';
import { ReactComponent as MyMsgStateDlvrd } from './icons/migration/my-message-state/my-message-state-delivered.svg';
import { ReactComponent as MyMessageStateSent } from './icons/migration/my-message-state/my-message-state-sent.svg';
import { ReactComponent as PersonAddOutlined } from './icons/migration/person-add/person-add-outlined.svg';
import { ReactComponent as PhoneOutlined } from './icons/migration/phone/phone-outlined.svg';
import { ReactComponent as PostAddOutlined } from './icons/migration/post-add/post-add-outlined.svg';
import { ReactComponent as PrintOutlined } from './icons/migration/print/print-outlined.svg';
import { ReactComponent as SendRounded } from './icons/migration/send/send-rounded.svg';

import { ReactComponent as UserGroupPlus } from './icons/user-group-plus.svg';

import { IconVariant, IconVariants } from './types';

export const ICON_MAP: Record<IconVariant, React.FC<React.SVGProps<SVGSVGElement>>> = {
  'chart-column-decreasing': ChartColumnDecreasing,
  bell: Bell,
  ban: Ban,
  bookmark: Bookmark,
  calculator: Calculator,
  'calendar-check-2': CalendarCheck,
  calendar: Calendar,
  'chart-line': ChartLine,
  'chart-pie': ChartPie,
  check: Check,
  'chevron-down': ChevronDown,
  'chevron-up': ChevronUp,
  'chevron-left': ChevronLeft,
  'chevron-right': ChevronRight,
  'circle-minus': CircleMinus,
  'circle-question-mark': CircleQuestionMark,
  'circle-x': CircleX,
  'columns-2': Columns2,
  'columns-3': Columns3,
  copy: Copy,
  copyright: Copyright,
  'ellipsis-vertical': EllipsisVertical,
  expand: Expand,
  'eye-closed': EyeClosed,
  eye: Eye,
  'file-plus-corner': FilePlusCorner,
  'file-up': FileUp,
  'file-x': FileX,
  file: File,
  'folder-plus': FolderPlus,
  forward: Forward,
  fullscreen: Fullscreen,
  headset: Headset,
  image: Image,
  inbox: Inbox,
  info: Info,
  'laptop-minimal': LaptopMinimal,
  'link-2': Link,
  list: List,
  'lock-open': LockOpen,
  lock: Lock,
  'log-out': LogOut,
  mail: Mail,
  megaphone: Megaphone,
  'message-square-text': MessageSquareText,
  'message-square': MessageSquare,
  'messages-square': MessagesSquare,
  'monitor-down': MonitorDown,
  pencil: Pencil,
  phone: Phone,
  pin: Pin,
  plus: Plus,
  printer: Printer,
  'refresh-cw': RefreshCw,
  reply: Reply,
  'rotate-ccw': RotateCcw,
  'rotate-cw': RotateCw,
  search: Search,
  'send-horizontal': SendHorizontal,
  settings: Settings,
  share: Share,
  'shield-check': ShieldCheck,
  shrink: Shrink,
  'square-split-horizontal': SquareSplitHorizontal,
  square: Square,
  'table-2': Table2,
  'trash-2': Trash2,
  'triangle-alert': TriangleAlert,
  underline: Underline,
  'user-group-plus': UserGroupPlus,
  'user-plus': UserPlus,
  'user-round': UserRound,
  users: Users,
  x: X,
  zap: Zap,
  'arrow-right-left': ArrowRightLeft,

  'arrow-left': ArrowLeft,
  'chart-no-axes-column-increasing': ChartNoAxesColumnIncreasing,
  'chart-no-axes-combined': ChartNoAxesCombined,
  'circle-alert': CircleAlert,
  'circle-arrow-right': CircleArrowRight,
  'circle-check': CircleCheck,
  'circle-chevron-left': CircleChevronLeft,
  'circle-chevron-right': CircleChevronRight,
  'image-plus': ImagePlus,
  'list-filter': ListFilter,
  'list-sort-ascending': ListSortAscending,
  'list-sort-descending': ListSortDescending,
  'loader-circle': LoaderCircle,
  menu: Menu,
  minus: Minus,
  'move-down': MoveDown,
  'move-up': MoveUp,
  paperclip: Paperclip,
  save: Save,
};

export const ICON_MAP_LEGACY: Record<IconVariants, React.FC<React.SVGProps<SVGSVGElement>>> = {
  [IconVariants.EMAIL_OUTLINED]: EmailOutLined,
  [IconVariants.FORUM_OUTLINED]: ForumOutlined,
  [IconVariants.FILE_COPY_OUTLINED]: FileCopyOutlined,
  [IconVariants.COMPARE_ARROWS_ROUNDED]: CompareArrowsRounded,
  [IconVariants.PHONE_OUTLINED]: PhoneOutlined,
  [IconVariants.HEADSET_MIC_OUTLINED]: HeadsetMicOutlined,
  [IconVariants.POST_ADD_OUTLINED]: PostAddOutlined,
  [IconVariants.SEND_ROUNDED]: SendRounded,
  [IconVariants.MOVE_TO_INBOX_ROUNDED]: MoveToInboxRounded,
  [IconVariants.PRINT_OUTLINED]: PrintOutlined,
  [IconVariants.BLOCK_ROUNDED]: BlockRounded,
  [IconVariants.LINK_ROUNDED]: LinkRounded,
  [IconVariants.PERSON_ADD_OUTLINED]: PersonAddOutlined,
  [IconVariants.CALCULATE]: Calculate,
  [IconVariants.CHEVRON_LEFT_OUTLINED]: ChevronLeftOutlined,
  [IconVariants.MY_MESSAGE_STATE_SENT]: MyMessageStateSent,
  [IconVariants.MY_MESSAGE_STATE_DELIVERED]: MyMsgStateDlvrd,
  [IconVariants.INFO_OUTLINED]: InfoOutlined,
};

import React from 'react';

import { ICON_MAP } from './const';
import { IconProps } from './types';

export const Icon: React.FC<IconProps> = ({ variant, className = '', style, size = 16, ...restProps }) => {
  const IconComponent = ICON_MAP[variant];

  const sizeProps = size === 'original'
    ? {}
    : { width: size, height: size }

  return (
    <IconComponent
      className={className}
      style={style}
      {...sizeProps}
      {...restProps}
    />
  );
};


import dayjs from 'dayjs';
import React, { useCallback, useMemo, useState } from 'react';

import { CascadeDropdown } from '@components/CascadeDropdown';
import { IconButton } from '@components/IconButton';
import Icons from '@components/Icons';
import { commonDateFormat } from '@configs/standartDateFormat';
import { useInstrumentLists } from '@modules/instrumentLists';
import { useWidgetIdContext } from '@terminal/desktop/workspaces/default/components/Widget/context';
import { Icon } from '@uikit/Icon';
import Tooltip from '@uikit/Tooltip';

import { getComplexKeyForQuotes } from '@widgets/BondScreener/logic/utils';
import { ScreenerInstrumentData } from 'types/BondsScreener';

import styles from './AddToInstrumentsListIcon.module.scss';

type AddToInstrumentsListIconProps = {
  screenerInstrumentsData: ScreenerInstrumentData[];
};

const BookmarkIcon = () => <Icons.Bookmark className={styles.icon} />;

export const AddToInstrumentsListIcon: React.FC<AddToInstrumentsListIconProps> = ({ screenerInstrumentsData }) => {
  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
  const widgetId = useWidgetIdContext();
  const { createInstrumentList, changeInstrumentList, lists } = useInstrumentLists(widgetId);

  const handleAddInstrumentsList = useCallback(() => {
    const baseListName = `Скрининг ${dayjs().format(commonDateFormat.dateFormat)}`;

    // Подсчет количества уже существующих списков с такой же базовой подстрокой
    const existingCount = lists?.filter((list) => list.name.startsWith(baseListName)).length || 0;

    // Если существующих списков нет, создаем без скобок, иначе с номером
    const listName = existingCount === 0 ? baseListName : `${baseListName} (${existingCount + 1})`;

    createInstrumentList({
      instruments: screenerInstrumentsData?.map((scrInstr) =>
        getComplexKeyForQuotes({ bond: scrInstr, separator: ':' }),
      ),
      name: listName,
      actionSource: 'outside',
    });
  }, [createInstrumentList, screenerInstrumentsData, lists]);

  const handleUpdateInstrumentList = useCallback(
    (listId: string) => {
      const changedList = lists?.find((list) => list.id.toString() === listId);
      const currentInstruments = screenerInstrumentsData?.map((instr) =>
        getComplexKeyForQuotes({ bond: instr, separator: ':' }),
      );

      const originalInstruments = changedList?.instruments;
      // список уникальных инструментов
      const newInstruments = [...new Set([...currentInstruments, ...(originalInstruments || [])])];
      changeInstrumentList({ id: Number(listId), instruments: newInstruments }, 'addInstruments', currentInstruments);
    },
    [lists, screenerInstrumentsData, changeInstrumentList],
  );

  const items = useMemo(
    () => [
      {
        key: 'title-key',
        label: 'Добавьте в список инструментов:',
        type: 'group' as const,
      },
      ...(lists?.map((list) => ({
        key: list.id,
        label: `${list.name} (${list.instruments?.length})`,
        type: undefined,
      })) || []),
    ],
    [lists],
  );

  const footer = useMemo(
    () => (
      <div
        className={styles.addNewWrapper}
        onClick={handleAddInstrumentsList}
      >
        <Icon
          variant="plus"
          className={styles.addNewIcon}
        />
        Создать новый список
      </div>
    ),
    [handleAddInstrumentsList],
  );

  const handleChange = useCallback(
    (key: string) => {
      handleUpdateInstrumentList(key);
    },
    [handleUpdateInstrumentList],
  );

  return (
    <CascadeDropdown
      open={isDropdownOpen}
      placement="bottom"
      onOpenChange={setIsDropdownOpen}
      items={items}
      footer={footer}
      onChange={handleChange}
      renderWrapperClassName={styles.restrictedMenu}
    >
      <Tooltip title="Добавить в список инструментов">
        <IconButton
          icon={<BookmarkIcon />}
          onClick={() => setIsDropdownOpen(true)}
        />
      </Tooltip>
    </CascadeDropdown>
  );
};

@import 'colors.scss';

.icon {
  color: $surface-icon-basis-active-primary !important;
  width: 12px !important;
  height: 12px !important;
}

.addNewWrapper {
  display: flex;
  flex-direction: row;
  gap: 8px;
  align-items: center;
  cursor: pointer;
  padding: 12px;

  &:hover {
    background-color: $action-surface-hover;
  }
}

.addNewIcon {
  fill: $text-interface-primary-value;
}

.restrictedMenu {
  max-height: 360px;
  overflow-y: auto;
  overflow-x: hidden;
}