import { PlusOutlined } from '@ant-design/icons'; import { Col, Form, Row, Select, message } from 'antd'; import type React from 'react'; import { useEffect, useMemo, useRef, useState } from 'react'; import { ApiError } from '../../api/client'; import { useBeforeUnload } from '../../hooks/useBeforeUnload'; import { fetchAllLegalEntities, fetchRelatedGroup, fetchRelatedGroups } from '../../services/api/directories/company_groups'; import { getCompanyGroupsThunk } from '../../store/slices/companyGroupsSlice/thunks'; import { useAppDispatch, useAppSelector } from '../../store/store'; import type { ConsolidationCreate, LegalEntityBrief, Quarter, RelatedGroupOut } from '../../types/api'; import { MIN_PERIMETER } from '../../utils/consolidations'; import { CompanyMultiSelect } from './CompanyOptionRow'; import { membersFirst, preselectMembers } from './preselect'; import { AppModal } from '../../components/AppModal/AppModal'; type Props = { open: boolean; onCancel: () => void; onOk: (values: ConsolidationCreate) => Promise; }; export const NewConsolidationModal: React.FC = ({ open, onCancel, onOk }) => { const [form] = Form.useForm(); const dispatch = useAppDispatch(); const { raw_rows, loading } = useAppSelector((state) => state.company_groups); const [subgroups, setSubgroups] = useState([]); const [loadingSubgroups, setLoadingSubgroups] = useState(false); const subgroupReqRef = useRef(0); const [legEntities, setLegEntities] = useState([]); const [loadingEntities, setLoadingEntities] = useState(false); const legEntReqRef = useRef(0); const relatedReqRef = useRef(0); const [submitting, setSubmitting] = useState(false); const company_group_id = Form.useWatch('company_group_id', form); const related_group_id = Form.useWatch('related_group_id', form); const year = Form.useWatch('year', form); const semester = Form.useWatch('quarter', form); const legEntIds = Form.useWatch('legal_entity_ids', form) ?? []; const quarterOptions: { value: Quarter; label: string }[] = [ { value: 'Q1', label: '1 квартал' }, { value: 'Q2', label: '2 квартал' }, { value: 'Q3', label: '3 квартал' }, { value: 'Q4', label: '4 квартал' }, ]; useEffect(() => { if (open && raw_rows.length === 0 && !loading) { dispatch(getCompanyGroupsThunk()); } }, [open, raw_rows.length, loading, dispatch]); // не меньше двух ЮЛ: иначе сервер ответит 422 PERIMETER_TOO_SMALL const isSubmitDisabled = !company_group_id || !related_group_id || !year || !semester || legEntIds.length < MIN_PERIMETER; // начатая форма — несохранённые правки: браузер предупредит при закрытии вкладки useBeforeUnload(open && !!(company_group_id || related_group_id || year || semester)); const yearOptions = useMemo(() => { const y = new Date().getFullYear(); return Array.from({ length: 4 }, (_, i) => ({ value: y - i, label: String(y - i), })); }, []); const clearLegEnts = () => { relatedReqRef.current++; // сменили ГК: ответ по старой ГСК больше не нужен setLegEntities([]); setLoadingEntities(false); legEntReqRef.current++; }; const handleGroupChange = async (value?: string) => { form.setFieldsValue({ related_group_id: undefined, legal_entity_ids: [], }); setSubgroups([]); clearLegEnts(); if (!value) return; const reqId = ++subgroupReqRef.current; const leReqId = ++legEntReqRef.current; setLoadingSubgroups(true); setLoadingEntities(true); try { const [dataSubgroups, dataLegEnts] = await Promise.all([fetchRelatedGroups(value), fetchAllLegalEntities(value)]); if (reqId === subgroupReqRef.current) setSubgroups(dataSubgroups.filter((g) => g.kind === 'gsk')); if (leReqId === legEntReqRef.current) setLegEntities(dataLegEnts); } catch (e: unknown) { message.error(e instanceof ApiError ? e.message : 'Не удалось загрузить данные ГК'); } finally { if (reqId === subgroupReqRef.current) setLoadingSubgroups(false); if (leReqId === legEntReqRef.current) setLoadingEntities(false); } }; const handleRelatedGroupChange = async (value?: string) => { form.setFieldsValue({ legal_entity_ids: [] }); if (!value) return; const reqId = ++relatedReqRef.current; try { const detail = await fetchRelatedGroup(value); if (reqId === relatedReqRef.current) { const memberIds = detail.members.map((m) => m.legal_entity_id); setLegEntities((prev) => membersFirst(prev, memberIds)); form.setFieldsValue({ legal_entity_ids: preselectMembers(memberIds, legEntities) }); } } catch (e: unknown) { message.error(e instanceof ApiError ? e.message : 'Не удалось загрузить состав ГСК'); } }; const resetLocal = () => { relatedReqRef.current++; setSubgroups([]); setLoadingSubgroups(false); subgroupReqRef.current++; clearLegEnts(); }; const handleOk = async () => { let values: ConsolidationCreate; try { values = await form.validateFields(); } catch { return; // ошибки полей antd показывает сам } setSubmitting(true); try { await onOk(values); form.resetFields(); resetLocal(); } catch (e: unknown) { message.error(e instanceof ApiError ? e.message : 'Не удалось создать консолидацию'); } finally { setSubmitting(false); } }; const handleCancel = () => { form.resetFields(); resetLocal(); onCancel(); }; return ( }} okText='Создать' cancelText='Отмена' confirmLoading={submitting} destroyOnHidden width={640}>
(option?.label as string)?.toLowerCase().includes(input.toLowerCase())} options={subgroups.map((s) => ({ value: s.id, label: s.name }))} onChange={handleRelatedGroupChange} /> {/* ROW 3 - full width block, only after subgroup is selected */} {related_group_id && ( (v?.length ?? 0) >= MIN_PERIMETER ? Promise.resolve() : Promise.reject(new Error('Выберите не меньше двух ЮЛ')), }, ]}> )}
); };