diff --git a/web/src/widgets/NewConsoleModal/CompanyOptionRow.tsx b/web/src/widgets/NewConsoleModal/CompanyOptionRow.tsx new file mode 100644 index 0000000..ab95355 --- /dev/null +++ b/web/src/widgets/NewConsoleModal/CompanyOptionRow.tsx @@ -0,0 +1,120 @@ +import { memo, useCallback, useMemo, useState } from 'react'; +import { LegalEntityBrief } from '../../types/api'; +import { Checkbox, Empty, Input, Spin } from 'antd'; + +type RowProps = { + entity: LegalEntityBrief; + checked: boolean; + onToggle: (id: string) => void; +}; +const CompanyOptionRow = memo(({ entity, checked, onToggle }: RowProps) => ( +
onToggle(entity.id)} + onKeyDown={(e) => { + if (e.key === ' ' || e.key === 'Enter') { + e.preventDefault(); + onToggle(entity.id); + } + }} + style={{ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + width: '100%', + minWidth: 0, + gap: 12, + padding: '10px 8px', + borderRadius: 4, + borderBottom: '1px solid rgba(0, 0, 0, 0.05)', + cursor: 'pointer', + boxSizing: 'border-box', + }}> + + + {entity.name} + + + {entity.inn} +
+)); + +type CompanyMultiSelectProps = { + companies: LegalEntityBrief[]; + loading?: boolean; + // injected by Form.Item: + value?: string[]; + onChange?: (value: string[]) => void; +}; + +export const CompanyMultiSelect: React.FC = ({ companies, loading, value = [], onChange }) => { + const [search, setSearch] = useState(''); + + const toggle = useCallback( + (id: string) => { + const next = value.includes(id) ? value.filter((v) => v !== id) : [...value, id]; + onChange?.(next); + }, + [value, onChange], + ); + + const filtered = useMemo(() => { + if (!search) return companies; + const q = search.toLowerCase(); + return companies.filter((s) => s.name.toLowerCase().includes(q)); + }, [companies, search]); + + return ( +
+ setSearch(e.target.value)} + style={{ marginBottom: 12 }} + /> + + {loading ? ( +
+ +
+ ) : null} + {!loading && filtered.length === 0 ? ( + + ) : ( +
+ {filtered.map((s) => ( + + ))} +
+ )} + + {!loading && companies.length > 0 && ( +
+ + Выбрано: {value.length} / {companies.length} + +
+ )} +
+ ); +}; diff --git a/web/src/widgets/NewConsoleModal/NewConsoleModal.tsx b/web/src/widgets/NewConsoleModal/NewConsoleModal.tsx index 843001a..e11c0b7 100644 --- a/web/src/widgets/NewConsoleModal/NewConsoleModal.tsx +++ b/web/src/widgets/NewConsoleModal/NewConsoleModal.tsx @@ -1,4 +1,4 @@ -import { Checkbox, Col, Empty, Form, Input, Modal, Row, Select, Spin, message } from 'antd'; +import { Col, Form, Modal, Row, Select, message } from 'antd'; import type React from 'react'; import { useEffect, useMemo, useRef, useState } from 'react'; import { ApiError } from '../../api/client'; @@ -7,6 +7,8 @@ import { getCompanyGroupsThunk } from '../../store/slices/companyGroupsSlice/thu import { useAppDispatch, useAppSelector } from '../../store/store'; import type { ConsolidationCreate, LegalEntityBrief, Quarter, RelatedGroupOut } from '../../types/api'; import { membersFirst, preselectMembers } from './preselect'; +import { PlusOutlined } from '@ant-design/icons'; +import { CompanyMultiSelect } from './CompanyOptionRow'; type Props = { open: boolean; @@ -30,13 +32,11 @@ export const NewConsolidationModal: React.FC = ({ open, onCancel, onOk }) const relatedReqRef = useRef(0); const [submitting, setSubmitting] = useState(false); - const [legEntSearch, setLegEntSearch] = useState(''); - 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 legEntIds = Form.useWatch('legal_entity_ids', form) ?? []; const quarterOptions: { value: Quarter; label: string }[] = [ { value: 'Q1', label: '1 квартал' }, @@ -64,7 +64,6 @@ export const NewConsolidationModal: React.FC = ({ open, onCancel, onOk }) relatedReqRef.current++; // сменили ГК: ответ по старой ГСК больше не нужен setLegEntities([]); setLoadingEntities(false); - setLegEntSearch(''); legEntReqRef.current++; }; @@ -114,7 +113,6 @@ export const NewConsolidationModal: React.FC = ({ open, onCancel, onOk }) setLoadingSubgroups(false); subgroupReqRef.current++; clearLegEnts(); - setLegEntSearch(''); }; const handleOk = async () => { @@ -142,25 +140,19 @@ export const NewConsolidationModal: React.FC = ({ open, onCancel, onOk }) onCancel(); }; - const filteredLegEnts = useMemo(() => { - if (!legEntSearch) return legEntities; - const q = legEntSearch.toLowerCase(); - return legEntities.filter((s) => s.name.toLowerCase().includes(q)); - }, [legEntities, legEntSearch]); - return ( }} okText='Создать' cancelText='Отмена' confirmLoading={submitting} destroyOnHidden width={640}> -
+ @@ -205,78 +197,7 @@ export const NewConsolidationModal: React.FC = ({ open, onCancel, onOk }) {related_group_id && ( - -
- setLegEntSearch(e.target.value)} - style={{ marginBottom: 12 }} - /> - - {loadingEntities ? ( -
- -
- ) : null} - {!loadingEntities && filteredLegEnts.length === 0 && ( - - )} - {!loadingEntities && filteredLegEnts.length > 0 && ( -
- {filteredLegEnts.map((s) => ( - -
- {s.name} - - {s.inn} - -
-
- ))} -
- )} - - {!loadingEntities && legEntities.length > 0 && ( -
- - Выбрано: {legEntIds?.length ?? 0} / {legEntities.length} - -
- )} -
-
+
)}