fixed bugs

This commit is contained in:
Grog 2026-10-19 09:33:27 +03:00
parent 2cddb55a5b
commit 25d2211ae6
16 changed files with 308 additions and 195 deletions

View File

@ -39,4 +39,9 @@ export const TAG_PRESETS = {
borderColor: 'rgba(245, 158, 11, 0.25)', borderColor: 'rgba(245, 158, 11, 0.25)',
color: 'rgb(146, 64, 14)', color: 'rgb(146, 64, 14)',
}, },
gray: {
color: 'rgb(71, 84, 103)',
bgColor: 'rgb(248, 250, 252)',
borderColor: 'rgba(15, 23, 42, 0.08)',
},
} as const; } as const;

View File

@ -0,0 +1,51 @@
import { useCallback, useMemo, useRef, useState } from "react";
type Options = {
/** высота одного элемента в px (фиксированная) */
itemHeight: number;
/** высота контейнера в px (видимая область) */
containerHeight: number;
/** сколько элементов рендерить дополнительно сверху/снизу */
overscan?: number;
};
export function useVirtualList<T>(items: T[], options: Options) {
const { itemHeight, containerHeight, overscan = 5 } = options;
const [scrollTop, setScrollTop] = useState(0);
const ref = useRef<HTMLDivElement | null>(null);
const totalHeight = items.length * itemHeight;
const startIndex = Math.max(
0,
Math.floor(scrollTop / itemHeight) - overscan,
);
const endIndex = Math.min(
items.length,
Math.ceil((scrollTop + containerHeight) / itemHeight) + overscan,
);
const visibleItems = useMemo(
() => items.slice(startIndex, endIndex).map((item, i) => ({
item,
index: startIndex + i,
})),
[items, startIndex, endIndex],
);
const offsetY = startIndex * itemHeight;
const onScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
setScrollTop(e.currentTarget.scrollTop);
}, []);
return {
ref,
onScroll,
visibleItems,
totalHeight,
offsetY,
};
}

View File

@ -95,15 +95,24 @@ export function CatalogueGroup() {
} }
async function getRelatedGroupsHandler() { async function getRelatedGroupsHandler() {
setRelatedGroupLoading(true);
if (activeCompanyGroup?.id) { if (activeCompanyGroup?.id) {
const res = await fetchRelatedGroups(activeCompanyGroup?.id); setRelatedGroupLoading(true);
const gsks = res.filter((elem) => elem.kind === 'gsk'); try {
const gszs = res.filter((elem) => elem.kind === 'gsz'); const res = await fetchRelatedGroups(activeCompanyGroup?.id);
setGskData(gsks); const gsks = res.filter((elem) => elem.kind === 'gsk');
setGszData(gszs); const gszs = res.filter((elem) => elem.kind === 'gsz');
setGskData(gsks);
setGszData(gszs);
} catch (e) {
if (e instanceof ApiError) {
notification.error({
description: e.message,
});
}
} finally {
setRelatedGroupLoading(false);
}
} }
setRelatedGroupLoading(false);
} }
useEffect(() => { useEffect(() => {

View File

@ -7,9 +7,10 @@ import type {
MoveCatalogueJurProps, MoveCatalogueJurProps,
WithdrawCatalogueJurProps, WithdrawCatalogueJurProps,
} from '../../../types/catalogueJur/catalogueJur'; } from '../../../types/catalogueJur/catalogueJur';
import { LE_PAGE } from './company_groups';
export async function fetchCatalogueJur(params: GetCatalogueJurPropsI) { export async function fetchCatalogueJur(params: GetCatalogueJurPropsI) {
const res = await api.get<Page<ICatalogueJur>>('/directories/legal-entities', params); const res = await api.get<Page<ICatalogueJur>>('/directories/legal-entities', {limit:LE_PAGE, offset:0, ...params});
return res; return res;
} }

View File

@ -3,7 +3,7 @@ import type { LegalEntityBrief, Page, RelatedGroupDetail, RelatedGroupOut } from
import type { CreateCompanyGroupProps, ICompanyGroupOut } from '../../../types/companyGroups/companyGroups'; import type { CreateCompanyGroupProps, ICompanyGroupOut } from '../../../types/companyGroups/companyGroups';
const CG_PAGE = 200; const CG_PAGE = 200;
const LE_PAGE = 200; export const LE_PAGE = 200;
/** Все видимые ГК — дочитываем страницы, пока не получим total. */ /** Все видимые ГК — дочитываем страницы, пока не получим total. */
export async function fetchCompanyGroups(): Promise<ICompanyGroupOut[]> { export async function fetchCompanyGroups(): Promise<ICompanyGroupOut[]> {

View File

@ -52,13 +52,13 @@ export type WithdrawCatalogueJurProps = {
export type CatalogueJurFormProps = { export type CatalogueJurFormProps = {
inn?: string; inn?: string;
kpp?: string; kpp?: string | null;
crm_id: string; crm_id: string;
entity_type: 'ЮЛ' | 'ИП'; entity_type: 'ЮЛ' | 'ИП';
name: string; name: string;
aliases?: string[] | { value: string; label: string }[]; aliases?: string[] | { value: string; label: string }[];
company_group_id?: string; company_group_id?: string;
regional_office_id?: string; regional_office_id?: string | null;
gsk_id?: string; gsk_id?: string | null;
gsz_id?: string; gsz_id?: string | null;
}; };

View File

@ -1,4 +1,4 @@
import { Col, Form, Input, InputNumber, Modal, Row, Select } from 'antd'; import { Col, Form, Input, Modal, Row, Select } from 'antd';
import type React from 'react'; import type React from 'react';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { ApiError } from '../../api/client'; import { ApiError } from '../../api/client';
@ -37,40 +37,62 @@ export const CatalogueJurForm: React.FC<Props> = ({ open, onCancel, onOk, jurId
async function getInitialData() { async function getInitialData() {
if (jurId) { if (jurId) {
setInitialDataLoading(true); setInitialDataLoading(true);
const data = await fetchOneCatalogueJur(jurId); try {
form.setFieldsValue({ const data = await fetchOneCatalogueJur(jurId);
name: data?.name, form.setFieldsValue({
aliases: data?.aliases?.map((aliase) => aliase.alias), name: data?.name,
crm_id: data?.crm_id, aliases: data?.aliases?.map((aliase) => aliase.alias),
entity_type: data?.entity_type, crm_id: data?.crm_id,
inn: data.inn, entity_type: data?.entity_type,
kpp: data?.kpp, inn: data.inn,
regional_office_id: data?.regional_office_id, kpp: data?.kpp,
company_group_id: data?.company_group_id, regional_office_id: data?.regional_office_id,
gsk_id: data.memberships?.find((elem) => elem.kind === 'gsk')?.related_group_id, company_group_id: data?.company_group_id,
gsz_id: data.memberships?.find((elem) => elem.kind === 'gsz')?.related_group_id, gsk_id: data.memberships?.find((elem) => elem.kind === 'gsk')?.related_group_id,
}); gsz_id: data.memberships?.find((elem) => elem.kind === 'gsz')?.related_group_id,
});
setInitialDataLoading(false); } catch (e) {
if (e instanceof ApiError) {
notification.error({ description: e.message });
} else {
console.error(e);
}
} finally {
setInitialDataLoading(false);
}
} }
} }
async function getOfficesOptionsHandler() { async function getOfficesOptionsHandler() {
setLoadingOfficesOptions(true); setLoadingOfficesOptions(true);
const res = await loadOffices(); try {
const options = res.map((elem) => elem && { value: elem.id, label: elem.name }); const res = await loadOffices();
setOfficesOptions(options); const options = res.map((elem) => elem && { value: elem.id, label: elem.name });
setLoadingOfficesOptions(false); setOfficesOptions(options);
} catch (e) {
if (e instanceof ApiError) {
notification.error({ description: e.message });
}
} finally {
setLoadingOfficesOptions(false);
}
} }
async function getRelatedGroupsHandler() { async function getRelatedGroupsHandler() {
if (companyGroupId) { if (!companyGroupId) return;
setLoadingRelatedGroupsOptions(true);
setLoadingRelatedGroupsOptions(true);
try {
const res = await fetchRelatedGroups(companyGroupId); const res = await fetchRelatedGroups(companyGroupId);
const gsks = res.filter((elem) => elem.kind === 'gsk'); const gsks = res.filter((elem) => elem.kind === 'gsk');
const gszs = res.filter((elem) => elem.kind === 'gsz'); const gszs = res.filter((elem) => elem.kind === 'gsz');
setGskData(gsks); setGskData(gsks);
setGszData(gszs); setGszData(gszs);
} catch (e) {
if (e instanceof ApiError) {
notification.error({ description: e.message });
}
} finally {
setLoadingRelatedGroupsOptions(false); setLoadingRelatedGroupsOptions(false);
} }
} }
@ -81,15 +103,20 @@ export const CatalogueJurForm: React.FC<Props> = ({ open, onCancel, onOk, jurId
const values = await form.validateFields(); const values = await form.validateFields();
await onOk({ await onOk({
...values, ...values,
kpp: type === 'ИП' ? undefined : values.kpp?.toString(), kpp: type === 'ИП' ? null : values.kpp?.toString(),
inn: jurId ? undefined : values?.inn?.toString(), inn: jurId ? undefined : values?.inn?.toString(),
company_group_id: jurId ? undefined : values.company_group_id, company_group_id: jurId ? undefined : values.company_group_id,
gsz_id: values.gsz_id ? values?.gsz_id : null,
gsk_id: values.gsk_id ? values?.gsk_id : null,
regional_office_id: values.regional_office_id ? values?.regional_office_id : null,
}); });
} catch (e) { } catch (e) {
if (e instanceof ApiError) { if (e instanceof ApiError) {
notification.error({ notification.error({
description: e.message, description: e.message,
}); });
} else if (e instanceof Error && e.name !== 'ValidationError') {
console.error(e);
} }
} finally { } finally {
setConfirmLoading(false); setConfirmLoading(false);
@ -112,7 +139,12 @@ export const CatalogueJurForm: React.FC<Props> = ({ open, onCancel, onOk, jurId
useEffect(() => { useEffect(() => {
getOfficesOptionsHandler(); getOfficesOptionsHandler();
}, []); }, []);
useEffect(() => { useEffect(() => {
form.setFieldsValue({
gsk_id: undefined,
gsz_id: undefined,
});
getRelatedGroupsHandler(); getRelatedGroupsHandler();
}, [companyGroupId]); }, [companyGroupId]);
@ -122,7 +154,7 @@ export const CatalogueJurForm: React.FC<Props> = ({ open, onCancel, onOk, jurId
open={open} open={open}
onCancel={handleCancel} onCancel={handleCancel}
onOk={handleOk} onOk={handleOk}
okText={jurId ? 'Сохраить' : 'Добавить'} okText={jurId ? 'Сохранить' : 'Добавить'}
cancelText='Отмена' cancelText='Отмена'
destroyOnHidden destroyOnHidden
width={720} width={720}
@ -145,7 +177,7 @@ export const CatalogueJurForm: React.FC<Props> = ({ open, onCancel, onOk, jurId
{ pattern: /^\d{10}$/, message: '10 цифр' }, { pattern: /^\d{10}$/, message: '10 цифр' },
] ]
}> }>
<InputNumber <Input
disabled={!!jurId} disabled={!!jurId}
style={{ width: '100%' }} style={{ width: '100%' }}
placeholder={type === 'ИП' ? '000000000000' : '0000000000'} placeholder={type === 'ИП' ? '000000000000' : '0000000000'}
@ -154,13 +186,13 @@ export const CatalogueJurForm: React.FC<Props> = ({ open, onCancel, onOk, jurId
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={6}> <Col span={6}>
<Form.Item name='kpp' label='КПП' rules={[{ pattern: /^\d{9}$/, message: '9 цифр' }]}> <Form.Item name='kpp' label='КПП' rules={[{ pattern: /^[0-9]{4}[0-9A-Z]{2}[0-9]{3}$/, message: 'Некорректный формат' }]}>
<InputNumber placeholder='000000000' maxLength={9} disabled={type === 'ИП'} style={{ width: '100%' }} /> <Input placeholder='000000000' maxLength={9} disabled={type === 'ИП'} style={{ width: '100%' }} />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={6}> <Col span={6}>
<Form.Item name='crm_id' label='ID ЮЛ в CRM' rules={[{ len: 11, message: 'Должно быть 11 символов' }]}> <Form.Item name='crm_id' label='ID ЮЛ в CRM'>
<Input maxLength={11} /> <Input />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={6}> <Col span={6}>

View File

@ -54,7 +54,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur
const [jurId, setJurId] = useState(''); const [jurId, setJurId] = useState('');
const filterdData = useMemo(() => { const filterdData = useMemo(() => {
if (relatedGroupFilter?.id) { if (relatedGroupFilter?.id && !outside_group) {
return rawRows?.filter((row) => { return rawRows?.filter((row) => {
return row?.memberships?.some((member) => member.related_group_id === relatedGroupFilter.id); return row?.memberships?.some((member) => member.related_group_id === relatedGroupFilter.id);
}); });
@ -243,7 +243,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur
<ColumnSettings defs={catalogueJurConfig.columns} visibleKeys={visibleKeys} onChange={setVisibleKeys} /> <ColumnSettings defs={catalogueJurConfig.columns} visibleKeys={visibleKeys} onChange={setVisibleKeys} />
</Space> </Space>
</Space> </Space>
{relatedGroupFilter?.id && ( {(relatedGroupFilter?.id && !outside_group) && (
<div <div
style={{ style={{
display: 'flex', display: 'flex',
@ -269,7 +269,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur
<span style={{ color: '#874d00' }}>Фильтр по:</span> <span style={{ color: '#874d00' }}>Фильтр по:</span>
<span style={{ fontWeight: 600, color: '#ad4e00' }}>{relatedGroupFilter.code}</span> <span style={{ fontWeight: 600, color: '#ad4e00' }}>{relatedGroupFilter.code}</span>
<span style={{ color: '#874d00' }}> <span style={{ color: '#874d00' }}>
— {filterdData?.length} — {filterdData?.length} &nbsp;
{pluralRu(filterdData?.length ?? 0, ['запись', 'записи', 'записей'])} {pluralRu(filterdData?.length ?? 0, ['запись', 'записи', 'записей'])}
</span> </span>
</div> </div>

View File

@ -12,8 +12,10 @@ export const catalogueJurConfig: TableConfig<ICatalogueJur> = {
title: 'Статус', title: 'Статус',
features: { sort: true }, features: { sort: true },
render: (_: unknown, row) => { render: (_: unknown, row) => {
// #TODO поменять логику если запись не активна const preset = !row.closed ? TAG_PRESETS.green : TAG_PRESETS.gray;
return <TableTag text={!row.closed ? 'Активно' : 'Не активно'} {...TAG_PRESETS.green} />; const text = !row.closed ? 'Активно' : 'Не активно';
return <TableTag text={text} {...preset} />;
}, },
}, },
{ {

View File

@ -26,10 +26,17 @@ export const CompanyGroupForm: React.FC<Props> = ({ open, onCancel, onOk }) => {
async function getOfficesOptionsHandler() { async function getOfficesOptionsHandler() {
setLoadingOptions(true); setLoadingOptions(true);
const res = await loadOffices(); try {
const options = res.map((elem) => elem && { value: elem.id, label: elem.name }); const res = await loadOffices();
setOfficesOptions(options); const options = res.map((elem) => elem && { value: elem.id, label: elem.name });
setLoadingOptions(false); setOfficesOptions(options);
} catch (e) {
if (e instanceof ApiError) {
notification.error({ description: e.message });
}
} finally {
setLoadingOptions(false);
}
} }
const handleOk = async () => { const handleOk = async () => {
@ -45,6 +52,8 @@ export const CompanyGroupForm: React.FC<Props> = ({ open, onCancel, onOk }) => {
notification.error({ notification.error({
description: e.message, description: e.message,
}); });
} else if (e instanceof Error && e.name !== 'ValidationError') {
console.error(e);
} }
} finally { } finally {
setConfirmLoading(false); setConfirmLoading(false);
@ -78,8 +87,8 @@ export const CompanyGroupForm: React.FC<Props> = ({ open, onCancel, onOk }) => {
<Row gutter={16}> <Row gutter={16}>
<Col span={12}> <Col span={12}>
<Form.Item name='crm_id' label='ID ГК в CRM' rules={[{ len: 11, message: 'Должно быть 11 символов' }]}> <Form.Item name='crm_id' label='ID ГК в CRM'>
<Input maxLength={11} placeholder='Генерируется автоматически' /> <Input placeholder='Генерируется автоматически' />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>

View File

@ -1,10 +1,11 @@
import { FolderOpenOutlined, SearchOutlined } from '@ant-design/icons';
import { Empty, Input, Listy, Spin, Tag, Typography } from 'antd';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { setActiveCompanyGroup } from '../../store/slices/companyGroupsSlice/companyGroupsSlice'; import { Input, List, Spin, Empty, Typography, Tag } from 'antd';
import { getCompanyGroupsThunk } from '../../store/slices/companyGroupsSlice/thunks'; import { FolderOpenOutlined, SearchOutlined } from '@ant-design/icons';
import { useAppDispatch, useAppSelector } from '../../store/store'; import { useAppDispatch, useAppSelector } from '../../store/store';
import './CompanyGroupsList.css'; import { getCompanyGroupsThunk } from '../../store/slices/companyGroupsSlice/thunks';
import { setActiveCompanyGroup } from '../../store/slices/companyGroupsSlice/companyGroupsSlice';
import { useVirtualList } from '../../hooks/useVirtualList';
const { Text } = Typography; const { Text } = Typography;
export const CompanyGroupsList = () => { export const CompanyGroupsList = () => {
@ -22,10 +23,22 @@ export const CompanyGroupsList = () => {
return rawRows.filter((g) => g.name.toLowerCase().includes(q) || g.crm_id?.toLowerCase().includes(q)); return rawRows.filter((g) => g.name.toLowerCase().includes(q) || g.crm_id?.toLowerCase().includes(q));
}, [rawRows, query]); }, [rawRows, query]);
const { ref, onScroll, visibleItems, totalHeight, offsetY } = useVirtualList(filtered, {
itemHeight: 56,
containerHeight: 900,
overscan: 5,
});
useEffect(() => { useEffect(() => {
dispatch(getCompanyGroupsThunk()); dispatch(getCompanyGroupsThunk());
}, [dispatch]); }, [dispatch]);
useEffect(() => {
if (!activeCompanyGroup && rawRows?.length) {
dispatch(setActiveCompanyGroup(rawRows?.[0]));
}
}, [rawRows, activeCompanyGroup, dispatch]);
return ( return (
<div <div
style={{ style={{
@ -36,7 +49,7 @@ export const CompanyGroupsList = () => {
borderRight: '1px solid #f0f0f0', borderRight: '1px solid #f0f0f0',
borderRadius: 12, borderRadius: 12,
}}> }}>
<div style={{ padding: '12px 16px', borderBottom: '1px solid #0f172a0f' }}> <div style={{ padding: 12 }}>
<Input <Input
allowClear allowClear
prefix={<SearchOutlined style={{ color: '#bfbfbf' }} />} prefix={<SearchOutlined style={{ color: '#bfbfbf' }} />}
@ -46,124 +59,100 @@ export const CompanyGroupsList = () => {
/> />
</div> </div>
<div style={{ flex: 1, overflowY: 'auto', padding: '10px' }}> <div ref={ref} onScroll={onScroll} style={{ flex: 1, overflowY: 'auto', padding: '0 8px 8px' }}>
{loading && ( {loading ? (
<div style={{ textAlign: 'center', padding: 24 }}> <div style={{ textAlign: 'center', padding: 24 }}>
<Spin /> <Spin />
</div> </div>
)} ) : filtered.length === 0 ? (
{!loading && filtered.length === 0 && (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description='Компании и организации не найдены' style={{ marginTop: 32 }} /> <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description='Компании и организации не найдены' style={{ marginTop: 32 }} />
)} ) : (
{!loading && filtered.length > 0 && ( <div style={{ height: totalHeight, position: 'relative' }}>
<Listy <div style={{ transform: `translateY(${offsetY}px)` }}>
virtual <List
rowKey='id' dataSource={visibleItems}
items={filtered} split={false}
height={900} renderItem={({item}) => {
itemRender={(item) => { const active = item.id === activeCompanyGroup?.id;
const active = item.id === activeCompanyGroup?.id; return (
return ( <List.Item
<button onClick={() => dispatch(setActiveCompanyGroup(item))}
type='button'
onClick={() => dispatch(setActiveCompanyGroup(item))}
style={{
display: 'block',
width: '100%',
border: 0,
font: 'inherit',
textAlign: 'left',
cursor: 'pointer',
borderRadius: 8,
padding: '10px 12px',
marginBottom: 4,
background: active ? '#e6f4ea' : 'transparent',
boxShadow: active ? 'inset 0 0 0 1px #2ea64b3d, inset 3px 0 0 #258141' : 'inset 0 0 0 1px #0f172a14',
transition: 'background 0.15s',
borderBottom: 0,
}}
onMouseEnter={(e) => {
if (!active) {
(e.currentTarget as HTMLElement).style.background = '#fafafa';
}
}}
onMouseLeave={(e) => {
if (!active) {
(e.currentTarget as HTMLElement).style.background = 'transparent';
}
}}>
<div
style={{
display: 'flex',
alignItems: 'center',
width: '100%',
gap: 12,
minWidth: 0,
}}>
<Tag
style={{ style={{
cursor: 'pointer',
borderRadius: 8, borderRadius: 8,
width: 28, padding: '10px 12px',
height: 28, marginBottom: 4,
display: 'flex', background: active ? '#e6f4ea' : 'transparent',
alignItems: 'center', boxShadow: active ? 'inset 0 0 0 1px #2ea64b3d, inset 3px 0 0 #258141' : 'inset 0 0 0 1px #0f172a14',
justifyContent: 'center', transition: 'background 0.15s',
}} }}
color='success'> onMouseEnter={(e) => {
<FolderOpenOutlined if (!active) {
style={{ (e.currentTarget as HTMLElement).style.background = '#fafafa';
fontSize: 18, }
color: 'green', }}
flexShrink: 0, onMouseLeave={(e) => {
}} if (!active) {
/> (e.currentTarget as HTMLElement).style.background = 'transparent';
</Tag> }
<div style={{ flex: 1, minWidth: 0 }}>
<Text
strong
ellipsis
style={{
display: 'block',
fontSize: 14,
lineHeight: '20px',
}}>
{item.name}
</Text>
<Text
type='secondary'
ellipsis
style={{
display: 'block',
fontSize: 12,
lineHeight: '16px',
}}>
CRM ID: {item.crm_id ?? '-'}
</Text>
</div>
<Tag
style={{
margin: 0,
borderRadius: 10,
background: '#f5f5f5',
border: 'none',
color: '#595959',
fontWeight: 500,
minWidth: 24,
height: 20,
lineHeight: '20px',
textAlign: 'center',
padding: '0 8px',
flexShrink: 0,
}}> }}>
{item.legal_entities_count} <div
</Tag> style={{
</div> display: 'flex',
</button> alignItems: 'center',
); width: '100%',
}} gap: 12,
/> minWidth: 0,
}}>
<FolderOpenOutlined style={{ fontSize: 18, color: '#8c8c8c', flexShrink: 0 }} />
<div style={{ flex: 1, minWidth: 0 }}>
<Text
strong
ellipsis
style={{
display: 'block',
fontSize: 14,
lineHeight: '20px',
}}>
{item.name}
</Text>
<Text
type='secondary'
ellipsis
style={{
display: 'block',
fontSize: 12,
lineHeight: '16px',
}}>
CRM ID: {item.crm_id ?? '-'}
</Text>
</div>
<Tag
style={{
margin: 0,
borderRadius: 10,
background: '#f5f5f5',
border: 'none',
color: '#595959',
fontWeight: 500,
minWidth: 24,
height: 20,
lineHeight: '20px',
textAlign: 'center',
padding: '0 8px',
flexShrink: 0,
}}>
{item.legal_entities_count}
</Tag>
</div>
</List.Item>
);
}}
/>
</div>
</div>
)} )}
</div> </div>
</div> </div>

View File

@ -5,9 +5,10 @@ type Props = {
onCancel: () => void; onCancel: () => void;
onOk: () => void | Promise<void>; onOk: () => void | Promise<void>;
loading?: boolean; loading?: boolean;
kind: 'gsk' | 'gsz';
}; };
export const DeleteRelatedGroupModal: React.FC<Props> = ({ open, onCancel, onOk, loading }) => { export const DeleteRelatedGroupModal: React.FC<Props> = ({ open, onCancel, onOk, loading, kind }) => {
return ( return (
<Modal <Modal
title='Удалить?' title='Удалить?'
@ -19,7 +20,7 @@ export const DeleteRelatedGroupModal: React.FC<Props> = ({ open, onCancel, onOk,
okButtonProps={{ danger: true }} okButtonProps={{ danger: true }}
confirmLoading={loading} confirmLoading={loading}
centered> centered>
<p style={{ margin: 0, color: '#595959' }}>Группа связанных компаний будет удалена.</p> <p style={{ margin: 0, color: '#595959' }}>Группа связанных {kind === 'gsk' ? 'компаний' : 'заёмщиков'} будет удалена.</p>
</Modal> </Modal>
); );
}; };

View File

@ -36,15 +36,22 @@ export const MoveCatalogueJurForm: React.FC<Props> = ({ open, onCancel, onOk, ju
async function getInitialData() { async function getInitialData() {
setInitialDataLoading(true); setInitialDataLoading(true);
const data = await fetchOneCatalogueJur(jurId); try {
form.setFieldsValue({ const data = await fetchOneCatalogueJur(jurId);
name: data?.name, form.setFieldsValue({
company_group_name: data?.company_group_name ?? '', name: data?.name,
old_company_group_id: data?.company_group_id ?? '', company_group_name: data?.company_group_name ?? '',
company_group_id: undefined, old_company_group_id: data?.company_group_id ?? '',
effective_date: '', company_group_id: undefined,
}); effective_date: '',
setInitialDataLoading(false); });
} catch (e) {
if (e instanceof ApiError) {
notification.error({ description: e.message });
}
} finally {
setInitialDataLoading(false);
}
} }
const handleOk = async () => { const handleOk = async () => {

View File

@ -36,7 +36,7 @@ export const RelatedGroupForm: React.FC<Props> = ({ open, onCancel, onOk, relate
form.setFieldsValue({ form.setFieldsValue({
crm_id: data?.crm_id, crm_id: data?.crm_id,
name: data?.name, name: data?.name,
created_date: data?.created_date, created_date: data?.created_date ?? dayjs().format('YYYY-MM-DD'),
company_group_id: data?.company_group_id, company_group_id: data?.company_group_id,
}); });
} catch (e) { } catch (e) {
@ -53,7 +53,7 @@ export const RelatedGroupForm: React.FC<Props> = ({ open, onCancel, onOk, relate
try { try {
const { created_date, name, crm_id, company_group_id } = await form.validateFields(); const { created_date, name, crm_id, company_group_id } = await form.validateFields();
await onOk({ await onOk({
created_date, created_date: created_date ?? dayjs().format('YYYY-MM-DD'),
name, name,
crm_id, crm_id,
company_group_id: relatedGroupId ? undefined : company_group_id, company_group_id: relatedGroupId ? undefined : company_group_id,
@ -85,7 +85,7 @@ export const RelatedGroupForm: React.FC<Props> = ({ open, onCancel, onOk, relate
return ( return (
<Modal <Modal
title={`${relatedGroupId ? 'Изменить' : 'Создать'} группу связанных ${kind === 'gsk' ? 'компаний' : 'заемщииков'}`} title={`${relatedGroupId ? 'Изменить' : 'Создать'} группу связанных ${kind === 'gsk' ? 'компаний' : 'заемщиков'}`}
open={open} open={open}
onCancel={handleCancel} onCancel={handleCancel}
onOk={handleOk} onOk={handleOk}
@ -95,8 +95,8 @@ export const RelatedGroupForm: React.FC<Props> = ({ open, onCancel, onOk, relate
loading={initialDataLoading} loading={initialDataLoading}
confirmLoading={confirmLoading}> confirmLoading={confirmLoading}>
<Form form={form} layout='vertical'> <Form form={form} layout='vertical'>
<FormItem name='crm_id' label='ID в CRM' rules={[{ len: 11, message: 'Должно быть 11 символов' }]}> <FormItem name='crm_id' label='ID в CRM'>
<Input maxLength={11} placeholder='Генерируется автоматически' /> <Input placeholder='Генерируется автоматически' />
</FormItem> </FormItem>
<FormItem name='name' label='Наименование' rules={[{ required: true, message: 'Укажите наименование' }]}> <FormItem name='name' label='Наименование' rules={[{ required: true, message: 'Укажите наименование' }]}>
<Input /> <Input />
@ -115,7 +115,6 @@ export const RelatedGroupForm: React.FC<Props> = ({ open, onCancel, onOk, relate
<Form.Item <Form.Item
name='created_date' name='created_date'
label='Дата создания' label='Дата создания'
rules={[{ required: true, message: 'Укажите дату' }]}
getValueProps={(value: string | undefined) => ({ getValueProps={(value: string | undefined) => ({
value: value ? dayjs(value) : undefined, value: value ? dayjs(value) : undefined,
})} })}

View File

@ -180,6 +180,7 @@ export function RelatedGroupTable({ tabsComponent, data, loading, getData, activ
loading={deletingLoading} loading={deletingLoading}
onCancel={() => closeDeleteRelatedGroupHandler()} onCancel={() => closeDeleteRelatedGroupHandler()}
onOk={deleteRelatedGroupHandler} onOk={deleteRelatedGroupHandler}
kind={activeTab}
/> />
)} )}
</div> </div>

View File

@ -34,14 +34,21 @@ export const WithdrawCatalogueJurForm: React.FC<Props> = ({ open, onCancel, onOk
async function getInitialData() { async function getInitialData() {
setInitialDataLoading(true); setInitialDataLoading(true);
const data = await fetchOneCatalogueJur(jurId); try {
form.setFieldsValue({ const data = await fetchOneCatalogueJur(jurId);
name: data?.name, form.setFieldsValue({
event: undefined, name: data?.name,
effective_date: '', event: undefined,
comment: '', effective_date: '',
}); comment: '',
setInitialDataLoading(false); });
} catch (e) {
if (e instanceof ApiError) {
notification.error({ description: e.message });
}
} finally {
setInitialDataLoading(false);
}
} }
const handleOk = async () => { const handleOk = async () => {
@ -100,7 +107,7 @@ export const WithdrawCatalogueJurForm: React.FC<Props> = ({ open, onCancel, onOk
<DatePicker minDate={dayjs()} style={{ width: '100%' }} format='DD.MM.YYYY' placeholder='ДД.ММ.ГГГГ' /> <DatePicker minDate={dayjs()} style={{ width: '100%' }} format='DD.MM.YYYY' placeholder='ДД.ММ.ГГГГ' />
</Form.Item> </Form.Item>
<FormItem name='comment' label='Комментарий'> <FormItem name='comment' label='Комментарий'>
<TextArea maxLength={6} rows={4} placeholder='Необязательно' /> <TextArea rows={4} placeholder='Необязательно' />
</FormItem> </FormItem>
</Form> </Form>
</Modal> </Modal>