289 lines
9.5 KiB
TypeScript
289 lines
9.5 KiB
TypeScript
import { DownloadOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';
|
||
import { Button, ConfigProvider } from 'antd';
|
||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||
import { useParams } from 'react-router-dom';
|
||
import { ApiError } from '../../api/client';
|
||
import {
|
||
exportChessTableAll,
|
||
exportChessTableManual,
|
||
fetchChessConsolidations,
|
||
fetchChessConsolidationsManual,
|
||
fetchChessConsolidationsResult,
|
||
} from '../../services/consodilation/intercompanyMatrix';
|
||
import type { IntercompanyMatrixResponse, IntercompanyMatrixResultResponse } from '../../types/chess/chess';
|
||
import type { VersionCard } from '../../types/consolidations/version_members';
|
||
import { exportFilename, saveBlob } from '../../utils/download';
|
||
import { useNotificationApi } from '../../utils/useNotification';
|
||
import { ConsolidationChessFormatTableFilter } from '../ConsolidationChessFormatTableFilter/ConsolidationChessFormatTableFilter';
|
||
import { ConsolidationChessResTable } from '../ConsolidationChessResTable/ConsolidationChessResTable';
|
||
import { ConsolidationChessTable } from '../ConsolidationChessTable/ConsolidationChessTable';
|
||
import { ConsolidationChessTableFilter } from '../ConsolidationChessTableFilter/ConsolidationChessTableFilter';
|
||
|
||
export const ChessContent = ({ card }: { card: VersionCard | null }) => {
|
||
const { version_id } = useParams<{ version_id: string }>();
|
||
const notification = useNotificationApi();
|
||
|
||
const [data, setData] = useState<IntercompanyMatrixResponse | null>(null);
|
||
const [loading, setLoading] = useState(false);
|
||
const [expoerLoading, setExportLoading] = useState(false);
|
||
const [companies, setCompanies] = useState<string[]>([]);
|
||
const [decimals, setDecimals] = useState(2);
|
||
const [units, setUnits] = useState('thousand');
|
||
const [debit, setDebit] = useState<string[]>([]);
|
||
const [credit, setCredit] = useState<string[]>([]);
|
||
|
||
const [chessResData, setChessResData] = useState<IntercompanyMatrixResultResponse | null>(null);
|
||
const [chessResUnits, setChessResUnits] = useState('thousand');
|
||
const [chessResDecimals, setChessResDecimals] = useState(2);
|
||
const [chessResLoading, setChessResLoading] = useState(false);
|
||
|
||
const chessTableRef = useRef<HTMLDivElement>(null);
|
||
const resTableRef = useRef<HTMLDivElement>(null);
|
||
const [isChessFullscreen, setIsChessFullscreen] = useState(false);
|
||
const [isResFullscreen, setIsResFullscreen] = useState(false);
|
||
|
||
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) {
|
||
if (e instanceof ApiError) {
|
||
notification.error({ description: e.message });
|
||
}
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
const handleFullscreenChange = () => {
|
||
if (!document.fullscreenElement) {
|
||
setIsChessFullscreen(false);
|
||
setIsResFullscreen(false);
|
||
}
|
||
};
|
||
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
||
return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
||
}, []);
|
||
|
||
const loadData = async (vId: string, dIds: string[], cIds: string[], isActual: () => boolean) => {
|
||
if ((dIds.length > 0 && cIds.length === 0) || (dIds.length === 0 && cIds.length > 0)) {
|
||
notification.info({ description: 'Для сравнений по Дт и Кт нужно заполнить оба фильтра' });
|
||
return;
|
||
}
|
||
setLoading(true);
|
||
try {
|
||
let res: IntercompanyMatrixResponse;
|
||
if (dIds?.length && cIds?.length) {
|
||
res = await fetchChessConsolidationsManual(vId, { debit_account_ids: dIds, credit_account_ids: cIds });
|
||
} else {
|
||
res = await fetchChessConsolidations(vId);
|
||
}
|
||
if (isActual()) {
|
||
setData(res);
|
||
}
|
||
} catch (e) {
|
||
if (isActual() && e instanceof ApiError) {
|
||
notification.error({ description: e.message });
|
||
}
|
||
} finally {
|
||
if (isActual()) {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
};
|
||
const loadResData = async (vid: string) => {
|
||
setChessResLoading(true);
|
||
try {
|
||
const res = await fetchChessConsolidationsResult(vid);
|
||
setChessResData(res);
|
||
} catch (e) {
|
||
if (e instanceof ApiError) {
|
||
notification.error({ description: e.message });
|
||
}
|
||
} finally {
|
||
setChessResLoading(false);
|
||
}
|
||
};
|
||
|
||
const download = async () => {
|
||
if (!version_id) {
|
||
return;
|
||
}
|
||
setExportLoading(true);
|
||
try {
|
||
if (debit?.length && credit?.length) {
|
||
const { blob, filename } = await exportChessTableManual(version_id, { debit_account_ids: debit, credit_account_ids: credit });
|
||
saveBlob(blob, filename ?? exportFilename(card?.company_group.name ?? 'ГК', card?.year ?? 0, card?.quarter ?? ''));
|
||
} else {
|
||
const { blob, filename } = await exportChessTableAll(version_id);
|
||
saveBlob(blob, filename ?? exportFilename(card?.company_group.name ?? 'ГК', card?.year ?? 0, card?.quarter ?? ''));
|
||
}
|
||
} catch (e) {
|
||
if (e instanceof ApiError) {
|
||
notification.error({ description: e.message });
|
||
}
|
||
} finally {
|
||
setExportLoading(false);
|
||
}
|
||
};
|
||
|
||
const filteredData = useMemo(() => {
|
||
if (!data) return null;
|
||
if (companies.length === 0) return data;
|
||
|
||
const filteredCompanies = data.companies.filter((c) => companies.includes(c.id));
|
||
const filteredCells = data.cells.filter((c) => companies.includes(c.company_a_id) && companies.includes(c.company_b_id));
|
||
|
||
return { companies: filteredCompanies, cells: filteredCells };
|
||
}, [data, companies]);
|
||
|
||
useEffect(() => {
|
||
if (!version_id) return;
|
||
|
||
let isMounted = true;
|
||
const isActual = () => isMounted;
|
||
|
||
loadData(version_id, debit, credit, isActual);
|
||
|
||
return () => {
|
||
isMounted = false;
|
||
};
|
||
}, [version_id, debit, credit]);
|
||
|
||
useEffect(() => {
|
||
if (!version_id) return;
|
||
loadResData(version_id);
|
||
}, [version_id]);
|
||
|
||
return (
|
||
<ConfigProvider getPopupContainer={() => (document.fullscreenElement as HTMLElement) || document.body}>
|
||
<div
|
||
style={{
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
overflowY: 'auto',
|
||
overflowX: 'hidden',
|
||
gap: 24,
|
||
paddingBottom: 32,
|
||
width: '100%',
|
||
}}>
|
||
<div
|
||
ref={chessTableRef}
|
||
style={{
|
||
backgroundColor: isChessFullscreen ? '#fff' : 'transparent',
|
||
padding: isChessFullscreen ? 6 : 0,
|
||
height: isChessFullscreen ? '100vh' : 'auto',
|
||
overflow: isChessFullscreen ? 'hidden' : 'visible',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
}}>
|
||
<ConsolidationChessTable
|
||
data={filteredData}
|
||
loading={loading}
|
||
decimals={decimals}
|
||
units={units}
|
||
isFullscreen={isChessFullscreen}
|
||
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' }}>
|
||
<ConsolidationChessTableFilter
|
||
companies={companies}
|
||
onCompaniesChange={setCompanies}
|
||
debit={debit}
|
||
onDebitChange={setDebit}
|
||
credit={credit}
|
||
onCreditChange={setCredit}
|
||
allCompanies={data?.companies || []}
|
||
/>
|
||
<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 }}>
|
||
<Button loading={expoerLoading} onClick={() => download()} icon={<DownloadOutlined />}>
|
||
Экспорт
|
||
</Button>
|
||
<Button
|
||
onClick={() => toggleFullscreen(chessTableRef, setIsChessFullscreen)}
|
||
icon={isChessFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}>
|
||
{isChessFullscreen ? 'Свернуть' : 'На весь экран'}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
}
|
||
/>
|
||
</div>
|
||
|
||
<div
|
||
ref={resTableRef}
|
||
style={{
|
||
backgroundColor: isResFullscreen ? '#fff' : 'transparent',
|
||
padding: isResFullscreen ? 6 : 0,
|
||
height: isResFullscreen ? '100vh' : 'auto',
|
||
overflow: isResFullscreen ? 'hidden' : 'visible',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
}}>
|
||
<ConsolidationChessResTable
|
||
data={chessResData}
|
||
loading={chessResLoading}
|
||
decimals={chessResDecimals}
|
||
units={chessResUnits}
|
||
isFullscreen={isResFullscreen}
|
||
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' }}>
|
||
<ConsolidationChessFormatTableFilter
|
||
decimals={chessResDecimals}
|
||
units={chessResUnits}
|
||
onDecimalsChange={setChessResDecimals}
|
||
onUnitsChange={setChessResUnits}
|
||
/>
|
||
</div>
|
||
<div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
|
||
<Button
|
||
onClick={() => toggleFullscreen(resTableRef, setIsResFullscreen)}
|
||
icon={isResFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}>
|
||
{isResFullscreen ? 'Свернуть' : 'На весь экран'}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</ConfigProvider>
|
||
);
|
||
};
|