fix(web): «Новая консолидация» — «+ Создать» от двух ЮЛ, строка ЮЛ — флажок antd (AURORA-1589, PR #166)
Сервер отвечает 422 PERIMETER_TOO_SMALL при одном ЮЛ — кнопка неактивна раньше, у поля «Выберите не меньше двух ЮЛ». Строка ЮЛ — сам Checkbox (label на всю строку) вместо div role="checkbox" (biome useSemanticElements). Предупреждение браузера при начатой форме. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d71c46e846
commit
2fa672a2fb
@ -1,52 +1,36 @@
|
|||||||
import { memo, useCallback, useMemo, useState } from 'react';
|
|
||||||
import { LegalEntityBrief } from '../../types/api';
|
|
||||||
import { Checkbox, Empty, Input, Spin } from 'antd';
|
import { Checkbox, Empty, Input, Spin } from 'antd';
|
||||||
|
import { memo, useCallback, useMemo, useState } from 'react';
|
||||||
|
import type { LegalEntityBrief } from '../../types/api';
|
||||||
|
|
||||||
type RowProps = {
|
type RowProps = {
|
||||||
entity: LegalEntityBrief;
|
entity: LegalEntityBrief;
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
onToggle: (id: string) => void;
|
onToggle: (id: string) => void;
|
||||||
};
|
};
|
||||||
|
// Вся строка — флажок antd (<label>): клик по наименованию или ИНН переключает, клавиатура — как у флажка.
|
||||||
|
// Подпись растянута на ширину строки: наименование с многоточием слева, ИНН — колонкой справа.
|
||||||
const CompanyOptionRow = memo(({ entity, checked, onToggle }: RowProps) => (
|
const CompanyOptionRow = memo(({ entity, checked, onToggle }: RowProps) => (
|
||||||
<div
|
<Checkbox
|
||||||
role='checkbox'
|
checked={checked}
|
||||||
aria-checked={checked}
|
onChange={() => onToggle(entity.id)}
|
||||||
tabIndex={0}
|
styles={{
|
||||||
onClick={() => onToggle(entity.id)}
|
root: {
|
||||||
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',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 8,
|
width: '100%',
|
||||||
flex: 1,
|
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
overflow: 'hidden',
|
padding: '10px 8px',
|
||||||
}}>
|
borderRadius: 4,
|
||||||
<Checkbox checked={checked} tabIndex={-1} style={{ pointerEvents: 'none' }} />
|
borderBottom: '1px solid rgba(0, 0, 0, 0.05)',
|
||||||
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{entity.name}</span>
|
boxSizing: 'border-box',
|
||||||
|
},
|
||||||
|
label: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flex: 1, minWidth: 0 },
|
||||||
|
}}>
|
||||||
|
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0 }} title={entity.name}>
|
||||||
|
{entity.name}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span style={{ color: '#8c8c8c', flexShrink: 0 }}>{entity.inn}</span>
|
<span style={{ color: '#8c8c8c', flexShrink: 0 }}>{entity.inn}</span>
|
||||||
</div>
|
</Checkbox>
|
||||||
));
|
));
|
||||||
|
|
||||||
type CompanyMultiSelectProps = {
|
type CompanyMultiSelectProps = {
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { Col, Form, Modal, Row, Select, 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';
|
||||||
|
import { useBeforeUnload } from '../../hooks/useBeforeUnload';
|
||||||
import { fetchAllLegalEntities, fetchRelatedGroup, fetchRelatedGroups } from '../../services/api/directories/company_groups';
|
import { fetchAllLegalEntities, fetchRelatedGroup, fetchRelatedGroups } from '../../services/api/directories/company_groups';
|
||||||
import { getCompanyGroupsThunk } from '../../store/slices/companyGroupsSlice/thunks';
|
import { getCompanyGroupsThunk } from '../../store/slices/companyGroupsSlice/thunks';
|
||||||
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 { MIN_PERIMETER } from '../../utils/consolidations';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { CompanyMultiSelect } from './CompanyOptionRow';
|
import { CompanyMultiSelect } from './CompanyOptionRow';
|
||||||
|
import { membersFirst, preselectMembers } from './preselect';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -50,7 +52,10 @@ export const NewConsolidationModal: React.FC<Props> = ({ open, onCancel, onOk })
|
|||||||
}
|
}
|
||||||
}, [open, raw_rows.length, loading, dispatch]);
|
}, [open, raw_rows.length, loading, dispatch]);
|
||||||
|
|
||||||
const isSubmitDisabled = !company_group_id || !related_group_id || !year || !semester || !legEntIds?.length;
|
// не меньше двух ЮЛ: иначе сервер ответит 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 yearOptions = useMemo(() => {
|
||||||
const y = new Date().getFullYear();
|
const y = new Date().getFullYear();
|
||||||
@ -196,7 +201,15 @@ export const NewConsolidationModal: React.FC<Props> = ({ open, onCancel, onOk })
|
|||||||
{/* ROW 3 - full width block, only after subgroup is selected */}
|
{/* ROW 3 - full width block, only after subgroup is selected */}
|
||||||
{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={[
|
||||||
|
{
|
||||||
|
validator: (_, v: string[] | undefined) =>
|
||||||
|
(v?.length ?? 0) >= MIN_PERIMETER ? Promise.resolve() : Promise.reject(new Error('Выберите не меньше двух ЮЛ')),
|
||||||
|
},
|
||||||
|
]}>
|
||||||
<CompanyMultiSelect loading={loadingEntities} companies={legEntities} />
|
<CompanyMultiSelect loading={loadingEntities} companies={legEntities} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user