125 lines
5.3 KiB
TypeScript
125 lines
5.3 KiB
TypeScript
import { Button, Space, Tooltip, message } from 'antd';
|
||
import { type ReactNode, useEffect, useState } from 'react';
|
||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||
import { ApiError } from '../api/client';
|
||
import { useCurrentUser } from '../auth/MeContext';
|
||
import PageTitle from '../components/PageTitle';
|
||
import { TableTag } from '../components/TableTag/TableTag';
|
||
import { TAG_PRESETS } from '../components/TableTag/presets';
|
||
import { getVersionCard } from '../services/api/consolidations';
|
||
import type { OsvSummaryOut } from '../types/api';
|
||
import type { ConsRussianStatus } from '../types/consolidations/common_types';
|
||
import type { VersionCard } from '../types/consolidations/version_members';
|
||
import { CONS_STATUS_LABEL, canChangeStatus, consStatusDescription, isLockedBy } from '../utils/consolidations';
|
||
import { quarterShortLabel } from '../utils/labels';
|
||
import { statusColorMapping } from '../widgets/ConsoleTable/consts';
|
||
import { ChessContent } from '../widgets/ConsolidatedChessContent/ConsolidatedChessContent';
|
||
import { OsvTabs } from '../widgets/ConsolidatedOsv/OsvTabs';
|
||
import { OsvContent } from '../widgets/ConsolidatedOsvContent/ConsolidatedOsvContent';
|
||
import ChangeStatusModal from '../widgets/StatusChangeModal/ChangeStatusModal';
|
||
|
||
function ReportContent() {
|
||
const [card, setCard] = useState<VersionCard | null>(null);
|
||
const [statusModalOpen, setModalOpen] = useState(false);
|
||
// сводка ОСВ (пары без встречных записей) — для модалки при уходе с вкладки «ОСВ»
|
||
const [osvSummary, setOsvSummary] = useState<OsvSummaryOut | null>(null);
|
||
const currentUser = useCurrentUser();
|
||
const { version_id, tab } = useParams<{ tab?: string; version_id: string }>();
|
||
const navigation = useNavigate();
|
||
const { pathname } = useLocation();
|
||
const TAB_COMPONENTS: Record<string, ReactNode> = {
|
||
osv: <OsvContent card={card} onSummaryChange={setOsvSummary} />,
|
||
|
||
chess: <ChessContent card={card} />,
|
||
};
|
||
|
||
const ActiveTab = TAB_COMPONENTS[tab as string] ?? null;
|
||
|
||
const russianStatus = card ? (CONS_STATUS_LABEL[card.status] ?? card.status) : '';
|
||
// под заголовком — пояснение статуса, не его название
|
||
const description = card ? consStatusDescription(card.status) : '';
|
||
const preset = statusColorMapping[russianStatus as ConsRussianStatus] ?? TAG_PRESETS.default;
|
||
const statusChangeAllowed = card ? canChangeStatus(card.status, card.lock, currentUser.id) : false;
|
||
const ownLock = card ? isLockedBy(card.lock, currentUser.id) : false;
|
||
const groupName = card?.company_group.name ?? '';
|
||
const period = card ? `${card.year} · ${quarterShortLabel(card.quarter)}` : '';
|
||
|
||
useEffect(() => {
|
||
if (version_id) {
|
||
let actual = true;
|
||
getVersionCard(version_id)
|
||
.then((c) => {
|
||
if (actual) setCard(c);
|
||
})
|
||
.catch((e: unknown) => {
|
||
if (actual) message.error(e instanceof ApiError ? e.message : 'Не удалось получить версию');
|
||
});
|
||
return () => {
|
||
actual = false;
|
||
};
|
||
}
|
||
}, [version_id]);
|
||
|
||
useEffect(() => {
|
||
const isValidTab = !tab || tab === 'osv' || tab === 'chess';
|
||
|
||
if (!isValidTab) {
|
||
const pathParts = pathname.split('/');
|
||
pathParts[pathParts.length - 1] = 'osv';
|
||
const newPath = pathParts.join('/');
|
||
navigation(newPath, { replace: true });
|
||
}
|
||
}, [tab, navigation, pathname]);
|
||
return (
|
||
<div style={{ padding: 12, display: 'flex', flexDirection: 'column', height: '100vh', gap: 12 }}>
|
||
<div style={{ flexShrink: 0, display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||
<PageTitle
|
||
backable
|
||
location={['Отчётность', groupName, period]}
|
||
header={card ? `${groupName} — ${card.year} ${quarterShortLabel(card.quarter)}` : 'Сводная ОСВ'}
|
||
description={description}
|
||
extra={
|
||
card && (
|
||
<Space size={8} style={{ display: 'flex', gap: 4 }}>
|
||
<span style={{ color: '#667085', fontSize: 12 }}>Статус отчёта</span>
|
||
<TableTag
|
||
style={{
|
||
padding: '3px 10px',
|
||
width: 158,
|
||
display: 'inline-flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
fontSize: 11,
|
||
}}
|
||
text={russianStatus}
|
||
{...preset}
|
||
/>
|
||
{/* активна при своей блокировке и наличии ручных переходов из текущего статуса */}
|
||
<Tooltip title={ownLock ? undefined : 'Возьмите консолидацию в работу'}>
|
||
<Button
|
||
size='middle'
|
||
style={{ fontSize: 12 }}
|
||
type='default'
|
||
disabled={!statusChangeAllowed}
|
||
onClick={() => setModalOpen(true)}>
|
||
Изменить статус
|
||
</Button>
|
||
</Tooltip>
|
||
</Space>
|
||
)
|
||
}
|
||
/>
|
||
<OsvTabs summary={osvSummary} versionId={version_id ?? ''} status={card?.status} />
|
||
</div>
|
||
{ActiveTab}
|
||
{card && <ChangeStatusModal open={statusModalOpen} card={card} onSuccess={setCard} onClose={() => setModalOpen(false)} />}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function ConsolidationReportPage() {
|
||
const { version_id = '' } = useParams<{ version_id: string }>();
|
||
// key: при смене версии всё состояние (карточка, фильтры, страница, данные) создаётся заново
|
||
return <ReportContent key={version_id} />;
|
||
}
|