fix: выравнивание ИНН при создании новой консолидации, + и убраны звёзды обязательности
This commit is contained in:
parent
a7600444e6
commit
2e75de33cc
120
web/src/widgets/NewConsoleModal/CompanyOptionRow.tsx
Normal file
120
web/src/widgets/NewConsoleModal/CompanyOptionRow.tsx
Normal file
@ -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) => (
|
||||||
|
<div
|
||||||
|
role='checkbox'
|
||||||
|
aria-checked={checked}
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => 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',
|
||||||
|
}}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}>
|
||||||
|
<Checkbox checked={checked} tabIndex={-1} style={{ pointerEvents: 'none' }} />
|
||||||
|
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{entity.name}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span style={{ color: '#8c8c8c', flexShrink: 0 }}>{entity.inn}</span>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
type CompanyMultiSelectProps = {
|
||||||
|
companies: LegalEntityBrief[];
|
||||||
|
loading?: boolean;
|
||||||
|
// injected by Form.Item:
|
||||||
|
value?: string[];
|
||||||
|
onChange?: (value: string[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CompanyMultiSelect: React.FC<CompanyMultiSelectProps> = ({ 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 (
|
||||||
|
<div style={{ border: '1px solid #d9d9d9', borderRadius: 8, padding: 12 }} className='multiselect'>
|
||||||
|
<Input.Search
|
||||||
|
placeholder='Поиск по ЮЛ'
|
||||||
|
allowClear
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
style={{ marginBottom: 12 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div style={{ textAlign: 'center', padding: 24 }}>
|
||||||
|
<Spin />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{!loading && filtered.length === 0 ? (
|
||||||
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={companies.length === 0 ? 'Нет компаний' : 'Нет совпадений'} />
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxHeight: 200,
|
||||||
|
overflowY: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 4,
|
||||||
|
paddingRight: 16,
|
||||||
|
overflowX: 'hidden',
|
||||||
|
}}>
|
||||||
|
{filtered.map((s) => (
|
||||||
|
<CompanyOptionRow key={s.id} entity={s} checked={value.includes(s.id)} onToggle={toggle} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && companies.length > 0 && (
|
||||||
|
<div style={{ marginTop: 8, display: 'flex', gap: 12 }}>
|
||||||
|
<span style={{ marginLeft: 'auto', color: '#8c8c8c' }}>
|
||||||
|
Выбрано: {value.length} / {companies.length}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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 type React from 'react';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { ApiError } from '../../api/client';
|
import { ApiError } from '../../api/client';
|
||||||
@ -7,6 +7,8 @@ import { getCompanyGroupsThunk } from '../../store/slices/companyGroupsSlice/thu
|
|||||||
import { useAppDispatch, useAppSelector } from '../../store/store';
|
import { useAppDispatch, useAppSelector } from '../../store/store';
|
||||||
import type { ConsolidationCreate, LegalEntityBrief, Quarter, RelatedGroupOut } from '../../types/api';
|
import type { ConsolidationCreate, LegalEntityBrief, Quarter, RelatedGroupOut } from '../../types/api';
|
||||||
import { membersFirst, preselectMembers } from './preselect';
|
import { membersFirst, preselectMembers } from './preselect';
|
||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { CompanyMultiSelect } from './CompanyOptionRow';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -30,13 +32,11 @@ export const NewConsolidationModal: React.FC<Props> = ({ open, onCancel, onOk })
|
|||||||
const relatedReqRef = useRef(0);
|
const relatedReqRef = useRef(0);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
const [legEntSearch, setLegEntSearch] = useState('');
|
|
||||||
|
|
||||||
const company_group_id = Form.useWatch('company_group_id', form);
|
const company_group_id = Form.useWatch('company_group_id', form);
|
||||||
const related_group_id = Form.useWatch('related_group_id', form);
|
const related_group_id = Form.useWatch('related_group_id', form);
|
||||||
const year = Form.useWatch('year', form);
|
const year = Form.useWatch('year', form);
|
||||||
const semester = Form.useWatch('quarter', 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 }[] = [
|
const quarterOptions: { value: Quarter; label: string }[] = [
|
||||||
{ value: 'Q1', label: '1 квартал' },
|
{ value: 'Q1', label: '1 квартал' },
|
||||||
@ -64,7 +64,6 @@ export const NewConsolidationModal: React.FC<Props> = ({ open, onCancel, onOk })
|
|||||||
relatedReqRef.current++; // сменили ГК: ответ по старой ГСК больше не нужен
|
relatedReqRef.current++; // сменили ГК: ответ по старой ГСК больше не нужен
|
||||||
setLegEntities([]);
|
setLegEntities([]);
|
||||||
setLoadingEntities(false);
|
setLoadingEntities(false);
|
||||||
setLegEntSearch('');
|
|
||||||
legEntReqRef.current++;
|
legEntReqRef.current++;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -114,7 +113,6 @@ export const NewConsolidationModal: React.FC<Props> = ({ open, onCancel, onOk })
|
|||||||
setLoadingSubgroups(false);
|
setLoadingSubgroups(false);
|
||||||
subgroupReqRef.current++;
|
subgroupReqRef.current++;
|
||||||
clearLegEnts();
|
clearLegEnts();
|
||||||
setLegEntSearch('');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOk = async () => {
|
const handleOk = async () => {
|
||||||
@ -142,25 +140,19 @@ export const NewConsolidationModal: React.FC<Props> = ({ open, onCancel, onOk })
|
|||||||
onCancel();
|
onCancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredLegEnts = useMemo(() => {
|
|
||||||
if (!legEntSearch) return legEntities;
|
|
||||||
const q = legEntSearch.toLowerCase();
|
|
||||||
return legEntities.filter((s) => s.name.toLowerCase().includes(q));
|
|
||||||
}, [legEntities, legEntSearch]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title='Новая консолидация'
|
title='Новая консолидация'
|
||||||
open={open}
|
open={open}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
okButtonProps={{ disabled: isSubmitDisabled }}
|
okButtonProps={{ disabled: isSubmitDisabled, icon: <PlusOutlined /> }}
|
||||||
okText='Создать'
|
okText='Создать'
|
||||||
cancelText='Отмена'
|
cancelText='Отмена'
|
||||||
confirmLoading={submitting}
|
confirmLoading={submitting}
|
||||||
destroyOnHidden
|
destroyOnHidden
|
||||||
width={640}>
|
width={640}>
|
||||||
<Form form={form} layout='vertical' preserve={false} initialValues={{ legal_entity_ids: [] }}>
|
<Form form={form} requiredMark={false} layout='vertical' preserve={false} initialValues={{ legal_entity_ids: [] }}>
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item name='company_group_id' label='Группа компаний' rules={[{ required: true, message: 'Укажите ГК' }]}>
|
<Form.Item name='company_group_id' label='Группа компаний' rules={[{ required: true, message: 'Укажите ГК' }]}>
|
||||||
@ -205,78 +197,7 @@ export const NewConsolidationModal: React.FC<Props> = ({ open, onCancel, onOk })
|
|||||||
{related_group_id && (
|
{related_group_id && (
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Form.Item name='legal_entity_ids' label='ЮЛ консолидации' rules={[{ required: true, message: 'Выберите хотя бы одно ЮЛ' }]}>
|
<Form.Item name='legal_entity_ids' label='ЮЛ консолидации' rules={[{ required: true, message: 'Выберите хотя бы одно ЮЛ' }]}>
|
||||||
<Checkbox.Group style={{ width: '100%' }}>
|
<CompanyMultiSelect loading={loadingEntities} companies={legEntities} />
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
border: '1px solid #d9d9d9',
|
|
||||||
borderRadius: 8,
|
|
||||||
padding: 12,
|
|
||||||
width: '100%',
|
|
||||||
}}>
|
|
||||||
<Input.Search
|
|
||||||
placeholder='Поиск ЮЛ...'
|
|
||||||
allowClear
|
|
||||||
value={legEntSearch}
|
|
||||||
onChange={(e) => setLegEntSearch(e.target.value)}
|
|
||||||
style={{ marginBottom: 12 }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{loadingEntities ? (
|
|
||||||
<div style={{ textAlign: 'center', padding: 24 }}>
|
|
||||||
<Spin />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{!loadingEntities && filteredLegEnts.length === 0 && (
|
|
||||||
<Empty
|
|
||||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
|
||||||
description={legEntities.length === 0 ? 'Нет ЮЛ в ГК' : 'Ничего не найдено'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!loadingEntities && filteredLegEnts.length > 0 && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
maxHeight: 200,
|
|
||||||
overflowY: 'auto',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
width: '100%',
|
|
||||||
gap: 8,
|
|
||||||
}}>
|
|
||||||
{filteredLegEnts.map((s) => (
|
|
||||||
<Checkbox key={s.id} value={s.id} style={{ width: '100%', display: 'flex' }}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
justifySelf: 'flex-start',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
width: '100%',
|
|
||||||
gap: '30px',
|
|
||||||
}}>
|
|
||||||
<span style={{ display: 'inline-block' }}>{s.name}</span>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
display: 'inline-block',
|
|
||||||
color: 'rgb(102, 112, 133)',
|
|
||||||
fontSize: 11,
|
|
||||||
}}>
|
|
||||||
{s.inn}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Checkbox>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!loadingEntities && legEntities.length > 0 && (
|
|
||||||
<div style={{ marginTop: 8, display: 'flex', gap: 12 }}>
|
|
||||||
<span style={{ marginLeft: 'auto', color: '#8c8c8c' }}>
|
|
||||||
Выбрано: {legEntIds?.length ?? 0} / {legEntities.length}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Checkbox.Group>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user