fixed bugs
This commit is contained in:
parent
2cddb55a5b
commit
25d2211ae6
@ -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;
|
||||||
|
|||||||
51
src/hooks/useVirtualList.tsx
Normal file
51
src/hooks/useVirtualList.tsx
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -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(() => {
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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[]> {
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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}>
|
||||||
|
|||||||
@ -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}
|
||||||
{pluralRu(filterdData?.length ?? 0, ['запись', 'записи', 'записей'])}
|
{pluralRu(filterdData?.length ?? 0, ['запись', 'записи', 'записей'])}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -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} />;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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}>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 () => {
|
||||||
|
|||||||
@ -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,
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user