import { WarningOutlined } from '@ant-design/icons';
import { Button, Segmented, Tooltip } from 'antd';
import { useEffect, useRef, useState } from 'react';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { AppModal } from '../../components/AppModal/AppModal';
import { getOsvSummary } from '../../services/api/osv';
import type { CounterPairOut, OsvSummaryOut } from '../../types/api';
import type { ConsStatus } from '../../types/consolidations/common_types';
import {
counterPairsWarning,
decideOsvLeave,
rememberCounterPairsShown,
reportTabNavigation,
resolveLeaveAfterFetch,
} 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: 'Гудвилл',
},
{
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<{ key: string; summary: OsvSummaryOut | null } | null>(null);
const activeKey = tab || 'osv';
const go = (newKey: string) => {
const { to, options } = reportTabNavigation(location.pathname, activeKey, newKey);
navigate(to, options);
};
const [checking, setChecking] = useState(false);
// актуальные версия и признак «компонент жив» — ответ запроса, пришедший после смены версии/размонтирования, игнорируется
const liveRef = useRef({ versionId, mounted: true });
liveRef.current.versionId = versionId;
useEffect(() => {
liveRef.current.mounted = true;
return () => {
liveRef.current.mounted = false;
};
}, []);
const handleTabChange = async (value: string | number) => {
const newKey = String(value);
if (newKey === activeKey || checking) return;
if (activeKey !== 'osv') {
go(newKey);
return;
}
const storage = safeSessionStorage();
let found: OsvSummaryOut | null = summary;
let decision: 'warn' | 'go' | 'perform' = decideOsvLeave({ summary: found, versionId, status, storage });
if (decision === 'perform') {
setChecking(true);
const requestedFor = versionId;
let result: OsvSummaryOut | null = null;
try {
result = await getOsvSummary(requestedFor);
} catch {
// не удалось проверить — не держим пользователя на вкладке
}
if (!liveRef.current.mounted) return;
setChecking(false);
if (liveRef.current.versionId !== requestedFor) return;
found = result;
decision = resolveLeaveAfterFetch({ summary: result, versionId, status, storage });
}
if (decision === 'warn' && found) {
rememberCounterPairsShown(versionId, found.counter_pairs, storage);
setPending({ key: newKey, summary: found });
} else {
go(newKey);
}
};
const options = TAB_CONFIG.map((tabItem) => {
const isActive = tabItem.key === activeKey;
const label = (
{tabItem.label}
);
return {
value: tabItem.key,
disabled: tabItem.disabled,
label: tabItem.tooltip ? {label} : label,
};
});
return (
<>
{
setPending(null);
navigate(
{ pathname: location.pathname, search: pairsViewSearch({ view: 'pairs', holder: null, missing: null }) },
{ replace: true },
);
}}
onStay={() => setPending(null)}
onGo={() => {
const next = pending?.key;
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 (
Показать пары
,
,
,
]}>
);
}