File "articleInputSelect.tsx"

Full Path: /var/www/html/gitep_front/src/entities/article/ui/articleInputSelect.tsx
File size: 8.44 KB
MIME-type: text/x-java
Charset: utf-8

import React, { useState, useEffect, useMemo } from 'react';
import { Flex, Select } from 'antd';
import styles from './article-input-select.module.scss';
import ArrowShow from '@shared/assets/images/icons/arrow-show.svg?react';
import CheckSvg from '@shared/assets/images/icons/check-new.svg';
import { belongsToProject, safeFind } from '@/pages/main/utils';
import { useUnit } from 'effector-react';

const { Option, OptGroup } = Select;
import { $articlesGroupsStore } from '@/entities/article/model';

interface Organization {
  id: number;
  name: string;
}

interface OrganizationSelectProps {
  initialOptions?: Organization[];
  isArticleValidate?: boolean;
  value?: number | null;
  onChange?: (value: number) => void;
  onSearch?: (value: string) => void;
  disabled: boolean;
  paymentCard: any;
  index: number;
}

const ArticleInputSelect: React.FC<OrganizationSelectProps> = ({
  initialOptions = [],
  value,
  isArticleValidate,
  onChange,
  onSearch,
  disabled,
  paymentCard,
  index,
}) => {
  const [options, setOptions] = useState<Organization[]>(initialOptions);
  const [selectedValue, setSelectedValue] = useState<number | null | undefined>(
    value,
  );
  const [searchQuery, setSearchQuery] = useState<string>('');
  const [open, setOpen] = useState<boolean>(false);

  const articles = useUnit($articlesGroupsStore).articlesGroups;

  const isArticleBelongProject = (articleId: any) => {
    const art = safeFind(
      articles,
      (a: any) => String(a.id) === String(articleId),
    );

    return belongsToProject(art, paymentCard?.distributions[index]?.project_id);
  };

  useEffect(() => {
    if (paymentCard?.distributions[index]?.project_id) {
      const filteredArticlesByPaymentType = articles.filter((article: any) =>
        paymentCard.payment_type === 'reception_from_1c'
          ? article.article_type === 'credit'
          : article.article_type === 'debit',
      );

      const idsToRemove = new Set(initialOptions.map((item) => item.id));
      const articlesWithoutExistArticleInProject =
        filteredArticlesByPaymentType.filter(
          (item: any) => !idsToRemove.has(item.id),
        );

      setOptions(
        Array.isArray(initialOptions)
          ? [...initialOptions, ...articlesWithoutExistArticleInProject]
          : [],
      );
    } else {
      setOptions(
        Array.isArray(initialOptions)
          ? initialOptions.filter((option: any) => !option.default_in_project)
          : [],
      );
    }
  }, [initialOptions, paymentCard?.distributions[index]?.project_id]);

  useEffect(() => {
    setSelectedValue(value);
  }, [value]);

  // разделение статей на группы
  const groupedOptions = useMemo(() => {
    const filteredOptions = options.filter((item: any) => item.id !== 1);

    if (!searchQuery) {
      return {
        projectArticles: filteredOptions.filter((option) =>
          isArticleBelongProject(option.id),
        ),
        nonProjectArticles: filteredOptions.filter(
          (option) => !isArticleBelongProject(option.id),
        ),
      };
    }

    const filteredBySearch = filteredOptions.filter((option) =>
      option.name.toLowerCase().includes(searchQuery.toLowerCase()),
    );

    return {
      projectArticles: filteredBySearch.filter((option) =>
        isArticleBelongProject(option.id),
      ),
      nonProjectArticles: filteredBySearch.filter(
        (option) => !isArticleBelongProject(option.id),
      ),
    };
  }, [options, searchQuery, paymentCard?.distributions[index]?.project_id]);

  const handleSelectChange = (value: number) => {
    setSelectedValue(value);
    onChange?.(value);
    setOpen(false);
  };

  const handleClear = () => {
    setSelectedValue(null);
    setSearchQuery('');
  };

  const handleSearch = (searchText: string) => {
    setSearchQuery(searchText);
    onSearch?.(searchText);
  };

  const handleDropdownOpenChange = (open: boolean) => {
    setOpen(open);
    if (!open) {
      setSearchQuery('');
    }
  };

  const renderOptions = () => {
    const hasProject = paymentCard?.distributions[index]?.project_id;

    if (!hasProject || searchQuery) {
      const allOptions = [
        ...groupedOptions.projectArticles,
        ...groupedOptions.nonProjectArticles,
      ];

      return allOptions.map((option) => (
        <Option key={option.id} value={option.id} label={option.name}>
          <Flex
            align='center'
            justify='space-between'
            style={{ width: '100%' }}
          >
            <span
              style={{
                maxWidth: '260px',
                whiteSpace: 'nowrap',
                overflow: 'hidden',
                textOverflow: 'ellipsis',
              }}
            >
              {option.name}
              {hasProject && !isArticleBelongProject(option.id) && (
                <span style={{ marginLeft: '8px', fontSize: '12px' }}>
                  Не в проекте
                </span>
              )}
            </span>

            {option.id === selectedValue && <img src={CheckSvg} alt='' />}
          </Flex>
        </Option>
      ));
    }

    return (
      <>
        {/* В проекте */}
        {groupedOptions.projectArticles.length > 0 && (
          <OptGroup
            key='in-project'
            label={
              <span style={{ fontSize: '12px', fontWeight: 400 }}>
                В проекте
              </span>
            }
          >
            {groupedOptions.projectArticles.map((option) => (
              <Option
                key={`in-${option.id}`}
                value={option.id}
                label={option.name}
              >
                <Flex
                  align='center'
                  justify='space-between'
                  style={{ width: '100%' }}
                >
                  <span
                    style={{
                      maxWidth: '260px',
                      whiteSpace: 'nowrap',
                      overflow: 'hidden',
                      textOverflow: 'ellipsis',
                    }}
                  >
                    {option.name}
                  </span>

                  {option.id === selectedValue && <img src={CheckSvg} alt='' />}
                </Flex>
              </Option>
            ))}
          </OptGroup>
        )}

        {/* "Не в проекте" */}
        {groupedOptions.nonProjectArticles.length > 0 && (
          <OptGroup
            key='not-in-project'
            label={
              <span style={{ fontSize: '12px', fontWeight: 400 }}>
                Не в проекте
              </span>
            }
          >
            {groupedOptions.nonProjectArticles.map((option) => (
              <Option
                key={`out-${option.id}`}
                value={option.id}
                label={option.name}
              >
                <Flex
                  align='center'
                  justify='space-between'
                  style={{ width: '100%' }}
                >
                  <span
                    style={{
                      maxWidth: '260px',
                      whiteSpace: 'nowrap',
                      overflow: 'hidden',
                      textOverflow: 'ellipsis',
                    }}
                  >
                    {option.name}
                  </span>

                  {option.id === selectedValue && <img src={CheckSvg} alt='' />}
                </Flex>
              </Option>
            ))}
          </OptGroup>
        )}

        {groupedOptions.projectArticles.length === 0 &&
          groupedOptions.nonProjectArticles.length === 0 && (
            <Option disabled value='no-data'>
              Нет доступных статей
            </Option>
          )}
      </>
    );
  };

  return (
    <Select
      showSearch
      style={{ width: '100%', height: '40px' }}
      placeholder='Выберите статью'
      onChange={handleSelectChange}
      onClear={handleClear}
      allowClear
      onSearch={handleSearch}
      value={selectedValue}
      status={isArticleValidate ? 'error' : ''}
      notFoundContent={options.length === 0 ? 'Ничего не найдено' : null}
      filterOption={false}
      disabled={disabled}
      open={open}
      onDropdownVisibleChange={handleDropdownOpenChange}
      suffixIcon={<ArrowShow className={styles.arrowIcon} />}
      optionFilterProp='children'
      optionLabelProp='label'
      popupMatchSelectWidth={400}
    >
      {renderOptions()}
    </Select>
  );
};

export default ArticleInputSelect;