2026-10-27 18:56:51 +04:00

182 lines
5.1 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 { WarningOutlined } from '@ant-design/icons';
import { Button, Modal, Segmented, Tooltip } from 'antd';
import { useState } from 'react';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import type { CounterPairOut, OsvSummaryOut } from '../../types/api';
import type { ConsStatus } from '../../types/consolidations/common_types';
import { counterPairsWarning, rememberCounterPairsShown, reportTabNavigation, shouldWarnCounterPairs } from '../../utils/consolidations';
import { pairsViewSearch } from './counterPairs';
const TAB_CONFIG = [
{ key: 'osv', label: 'ОСВ' },
{ key: 'chess', label: 'Шахматка' },
{
key: 'correction',
label: 'Таблица корректировки',
disabled: true,
tooltip: 'Будет в следующих срезах',
},
{
key: 'goodwill',
label: 'Гудвилл',
disabled: true,
tooltip: 'Будет в следующих срезах',
},
{
key: 'kfo',
label: 'КФО',
disabled: true,
tooltip: 'Будет в следующих срезах',
},
{
key: 'kfo_common',
label: 'КФО Общий',
disabled: true,
tooltip: 'Будет в следующих срезах',
},
{
key: 'kfo_confirm',
label: 'Подтверждение КФО клиента',
disabled: true,
tooltip: 'Будет в следующих срезах',
},
];
type Props = {
/** Сводка ОСВ: есть пары без встречных записей — переход «ОСВ» → «Шахматка» через модалку. */
summary: OsvSummaryOut | null;
versionId: string;
status: ConsStatus | undefined;
};
/** `sessionStorage` может бросать (приватный режим) — тогда `null`. */
const safeSessionStorage = (): Storage | null => {
try {
return window.sessionStorage;
} catch {
return null;
}
};
/** Ряд вкладок шагов консолидации (макет [30]); вкладка — сегмент адреса `/versions/:version_id/:tab`. */
export function OsvTabs({ summary, versionId, status }: Props) {
const navigate = useNavigate();
const location = useLocation();
const { tab } = useParams<{ tab?: string }>();
const [pending, setPending] = useState<string | null>(null);
const activeKey = tab || 'osv';
const go = (newKey: string) => {
const { to, options } = reportTabNavigation(location.pathname, activeKey, newKey);
navigate(to, options);
};
const handleTabChange = (value: string | number) => {
const newKey = String(value);
if (newKey === activeKey) return;
const pairs = summary?.counter_pairs ?? [];
if (activeKey === 'osv' && newKey === 'chess' && shouldWarnCounterPairs(versionId, status, pairs, safeSessionStorage())) {
rememberCounterPairsShown(versionId, pairs, safeSessionStorage());
setPending(newKey);
} else {
go(newKey);
}
};
const options = TAB_CONFIG.map((tabItem) => {
const isActive = tabItem.key === activeKey;
const label = (
<span
key={tabItem.key}
style={{
color: isActive ? '#2a8c3a' : undefined,
fontWeight: isActive ? 600 : 400,
}}>
{tabItem.label}
</span>
);
return {
value: tabItem.key,
disabled: tabItem.disabled,
label: tabItem.tooltip ? <Tooltip title={tabItem.tooltip}>{label}</Tooltip> : label,
};
});
return (
<>
<Segmented
value={activeKey}
onChange={handleTabChange}
style={{ alignSelf: 'flex-start', maxWidth: '100%', overflowX: 'auto' }}
options={options}
/>
<CounterPairsModal
open={pending !== null}
pairs={summary?.counter_pairs ?? []}
unmappedTotal={summary?.unmapped_total ?? 0}
onShow={() => {
setPending(null);
navigate(
{ pathname: location.pathname, search: pairsViewSearch({ view: 'pairs', holder: null, missing: null }) },
{ replace: true },
);
}}
onStay={() => setPending(null)}
onGo={() => {
const next = pending;
setPending(null);
if (next) go(next);
}}
/>
</>
);
}
/** Модалка перед шахматкой: у части участников нет встречных записей (`05-domain-rules.md` §3 п. 9). */
export function CounterPairsModal({
open,
pairs,
unmappedTotal,
onShow,
onStay,
onGo,
}: {
open: boolean;
pairs: readonly CounterPairOut[];
unmappedTotal: number;
onShow: () => void;
onStay: () => void;
onGo: () => void;
}) {
const { headline, details } = counterPairsWarning(pairs, unmappedTotal);
return (
<Modal
open={open}
title='Проверьте встречные записи участников'
centered
onCancel={onStay}
footer={[
<Button key='show' onClick={onShow}>
Показать пары
</Button>,
<Button key='stay' onClick={onStay}>
Остаться
</Button>,
<Button key='go' type='primary' onClick={onGo}>
Перейти
</Button>,
]}>
<div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
<WarningOutlined style={{ color: '#dc6803', fontSize: 20, marginTop: 2 }} />
<div>
<div style={{ fontWeight: 600 }}>{headline}</div>
<div style={{ color: '#667085' }}>{details}</div>
</div>
</div>
</Modal>
);
}