363 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { EditOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';
import { Alert, Button, ConfigProvider, Select, Space, Tooltip } from 'antd';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import type { GoodwillRow, GoodwillTableResponse } from '../../../types/goodwill/goodwill';
import { useNotificationApi } from '../../../utils/useNotification';
import { ConsolidationChessFormatTableFilter } from '../../ConsolidationChess/ConsolidationChessFormatTableFilter/ConsolidationChessFormatTableFilter';
import { ConsolidationGoodwillTable } from '../ConsolidationGoodwillTable/ConsolidationGoodwillTable';
import { getGoodwillColumns } from '../ConsolidationGoodwillTable/config';
import { ColumnSettings, useVisibleColumns } from '../../../components/GenericTable';
import { ApiError } from '../../../api/client';
// --- ТЕСТОВЫЕ ДАННЫЕ ---
const MOCK_DATA: GoodwillTableResponse = {
version_id: '30d45049-4dd6-4479-9cf7-33f4e71301ef',
goodwill_enabled: true,
editable: true,
read_only_reason: null,
rows: [
{
head_legal_entity: { id: 'head-1', name: 'Агрохолдинг "Просторы" АО' },
subsidiary_legal_entity: { id: 'sub-1-1', name: 'ООО "Дон Агро"' },
ownership_share: '100.0000',
financial_investment_amount: '4060.00',
charter_capital_amount: '371.17',
additional_capital_amount: '0.00',
participation_amount: '371.14',
goodwill_amount: '3688.86',
include_in_kfo: true,
comment: 'Основная дочерняя компания',
updated_by: { id: 'user-1', name: 'Иванов И.И.' },
updated_at: '2026-10-15T10:00:00Z',
},
{
head_legal_entity: { id: 'head-1', name: 'Агрохолдинг "Просторы" АО' },
subsidiary_legal_entity: { id: 'sub-1-2', name: 'АО "Дон Агро"' },
ownership_share: '75.0000',
financial_investment_amount: '260.01',
charter_capital_amount: '120.00',
additional_capital_amount: '0.00',
participation_amount: '119.99',
goodwill_amount: '-140.02',
include_in_kfo: false,
comment: null,
updated_by: null,
updated_at: null,
},
{
head_legal_entity: { id: 'head-1', name: 'Агрохолдинг "Просторы" АО' },
subsidiary_legal_entity: { id: 'sub-1-3', name: 'ООО "Дон Мучнов"' },
ownership_share: '100.0000',
financial_investment_amount: '130.00',
charter_capital_amount: '0.01',
additional_capital_amount: '0.00',
participation_amount: '0.01',
goodwill_amount: '129.99',
include_in_kfo: true,
comment: '',
updated_by: null,
updated_at: null,
},
{
head_legal_entity: { id: 'head-1', name: 'Агрохолдинг "Просторы" АО' },
subsidiary_legal_entity: { id: 'sub-1-4', name: 'ООО "Инвестком"' },
ownership_share: '100.0000',
financial_investment_amount: '267.00',
charter_capital_amount: '0.01',
additional_capital_amount: '0.00',
participation_amount: '0.01',
goodwill_amount: '266.99',
include_in_kfo: false,
comment: null,
updated_by: null,
updated_at: null,
},
{
head_legal_entity: { id: 'head-2', name: 'Инвестком АО' },
subsidiary_legal_entity: { id: 'sub-2-1', name: 'ООО "Юг-Инвест"' },
ownership_share: '50.0000',
financial_investment_amount: '8500.00',
charter_capital_amount: '1000.00',
additional_capital_amount: '500.00',
participation_amount: '500.00',
goodwill_amount: '8000.00',
include_in_kfo: true,
comment: 'Требует пересмотра',
updated_by: { id: 'user-2', name: 'Петров П.П.' },
updated_at: '2026-10-20T14:30:00Z',
},
],
totals: {
financial_investment_amount: '13217.01',
participation_amount: '991.15',
goodwill_amount: '12225.86',
goodwill_in_kfo_amount: '11817.85',
},
};
// ------------------------
export const GoodwillContent = () => {
const { version_id } = useParams<{ version_id: string }>();
const notification = useNotificationApi();
const [data, setData] = useState<GoodwillTableResponse | null>(null);
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [decimals, setDecimals] = useState(2);
const [units, setUnits] = useState('thousand');
const [isEditMode, setIsEditMode] = useState(false);
const [editedRows, setEditedRows] = useState<Record<string, Partial<GoodwillRow>>>({});
const goodwillTableRef = useRef<HTMLDivElement>(null);
const [isGoodwillFullscreen, setIsGoodwillFullscreen] = useState(false);
const [goodwillFilter, setGoodwillFilter] = useState('allValue');
const handleRowEdit = useCallback(
(rowKey: string, field: keyof GoodwillRow, value: any) => {
const isParent = rowKey.startsWith('head-') && !rowKey.includes('-sub-');
if (isParent && field === 'include_in_kfo') {
const headId = rowKey.replace('head-', '');
const children = data?.rows.filter((r) => r.head_legal_entity.id === headId) || [];
setEditedRows((prev) => {
const next = { ...prev };
children.forEach((child) => {
const childKey = `${headId}-${child.subsidiary_legal_entity.id}`;
next[childKey] = { ...next[childKey], include_in_kfo: value };
});
return next;
});
return;
}
setEditedRows((prev) => ({
...prev,
[rowKey]: { ...prev[rowKey], [field]: value },
}));
},
[data?.rows],
);
const columns = useMemo(() => {
return getGoodwillColumns({ units, decimals, isEditMode, editedRows, onRowEdit: handleRowEdit });
}, [units, decimals, isEditMode, editedRows, handleRowEdit]);
const groupsConfig = useMemo(() => {
return { rowKey: 'key' as any, columns: columns as any };
}, [columns]);
const { visibleKeys, setVisibleKeys } = useVisibleColumns(groupsConfig);
const toggleFullscreen = async (ref: React.RefObject<HTMLDivElement>, setFullscreen: (val: boolean) => void) => {
if (!ref.current) return;
try {
if (!document.fullscreenElement) {
await ref.current.requestFullscreen();
setFullscreen(true);
} else {
await document.exitFullscreen();
setFullscreen(false);
}
} catch (e) {
notification.error({ description: e instanceof Error ? e.message : 'Ошибка' });
}
};
const dropFiltersHandler = () => {
setGoodwillFilter('allValue');
};
const loadData = useCallback(async () => {
setLoading(true);
try {
await new Promise((resolve) => setTimeout(resolve, 500));
setData(MOCK_DATA);
const initialEdited: Record<string, Partial<GoodwillRow>> = {};
MOCK_DATA.rows.forEach((row) => {
if (row.include_in_kfo) {
const key = `${row.head_legal_entity.id}-${row.subsidiary_legal_entity.id}`;
initialEdited[key] = {
include_in_kfo: true,
ownership_share: row.ownership_share,
comment: row.comment,
};
}
});
setEditedRows(initialEdited);
} catch (e) {
if (e instanceof ApiError) {
notification.error({ description: e.message });
}
} finally {
setLoading(false);
}
}, [notification]);
const handleSave = async () => {
setSaving(true);
try {
await new Promise((resolve) => setTimeout(resolve, 800));
notification.success({ description: 'Изменения сохранены (тестовый режим)' });
setEditedRows({});
setIsEditMode(false);
loadData();
} catch (e) {
if (e instanceof ApiError) {
notification.error({ description: e.message });
}
} finally {
setSaving(false);
}
};
const filteredData = useMemo(() => {
if (!data) return null;
if (goodwillFilter === 'allValue') return data;
const filteredRows = data.rows.filter((row) => {
const amount = Number(row.goodwill_amount);
if (goodwillFilter === 'positive') return amount > 0;
if (goodwillFilter === 'negative') return amount < 0;
return true;
});
return {
...data,
rows: filteredRows,
};
}, [data, goodwillFilter]);
const canSaveEditData = useMemo(() => {
if (!isEditMode) {
return;
}
const hasErrors = Object.values(editedRows).some((elem) => {
if (!elem.include_in_kfo) return false;
const isShareEmpty = !elem.ownership_share || String(elem.ownership_share).trim() === '';
const isCommentEmpty = !elem.comment || String(elem.comment).trim() === '';
return isShareEmpty || isCommentEmpty;
});
return !hasErrors;
}, [editedRows, isEditMode]);
useEffect(() => {
loadData();
}, [loadData]);
return (
<ConfigProvider getPopupContainer={() => (document.fullscreenElement as HTMLElement) || document.body}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, width: '100%' }}>
{isEditMode && (
<Alert
title={<span style={{ color: '#874d00' }}>Доступны для редактирования столбцы 'Доля владения' и 'Комментарий'.</span>}
type='warning'
action={
<Space>
<Button
onClick={() => {
setIsEditMode(false);
setEditedRows({});
}}>
Отменить изменения
</Button>
<Tooltip title={!canSaveEditData ? 'Не все данные заполнены' : undefined}>
<Button type='primary' disabled={!canSaveEditData} loading={saving} onClick={handleSave}>
Сохранить
</Button>
</Tooltip>
</Space>
}
/>
)}
<div
ref={goodwillTableRef}
style={{
display: 'flex',
flexDirection: 'column',
height: isGoodwillFullscreen ? '100vh' : 'auto',
padding: isGoodwillFullscreen ? 16 : 0,
backgroundColor: isGoodwillFullscreen ? '#fff' : 'transparent',
}}>
<ConsolidationGoodwillTable
data={filteredData}
loading={loading}
decimals={decimals}
units={units}
isFullscreen={isGoodwillFullscreen}
isEditMode={isEditMode}
editedRows={editedRows}
onRowEdit={handleRowEdit}
visibleKeys={visibleKeys}
toolbar={
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
flexWrap: 'wrap',
gap: 16,
}}>
<div style={{ display: 'flex', alignItems: 'flex-start', flexWrap: 'wrap', gap: 16, flex: '1 1 auto' }}>
<Space>
<Space orientation='vertical' size={0}>
<span style={{ fontSize: 12, color: '#667085' }}>Гудвил</span>
<Select
style={{ minWidth: 200 }}
value={goodwillFilter}
options={[
{ value: 'allValue', label: 'Все значения' },
{ value: 'positive', label: 'Положительные' },
{ value: 'negative', label: 'Отрицательные' },
]}
onChange={(e) => setGoodwillFilter(e)}
variant='underlined'
/>
</Space>
{goodwillFilter !== 'allValue' ? <Button onClick={() => dropFiltersHandler()}>Сбросить фильтры</Button> : null}
</Space>
<div
style={{
width: 1,
height: 50,
backgroundColor: '#e5e7eb',
}}
/>
<ConsolidationChessFormatTableFilter
decimals={decimals}
units={units}
onDecimalsChange={setDecimals}
onUnitsChange={setUnits}
/>
</div>
<div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
<ColumnSettings defs={groupsConfig.columns} visibleKeys={visibleKeys} onChange={setVisibleKeys} />
<Button
type={isEditMode ? 'default' : 'primary'}
icon={<EditOutlined />}
onClick={() => setIsEditMode(true)}
disabled={isEditMode || !data?.editable}>
Редактировать
</Button>
<Button
onClick={() => toggleFullscreen(goodwillTableRef, setIsGoodwillFullscreen)}
icon={isGoodwillFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}>
{isGoodwillFullscreen ? 'Свернуть' : 'На весь экран'}
</Button>
</div>
</div>
}
/>
</div>
</div>
</ConfigProvider>
);
};