import { DownloadOutlined, FilterOutlined } from '@ant-design/icons'; import { Alert, Button, Space, Tooltip, message } from 'antd'; import { useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { ApiError } from '../api/client'; 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 { exportOsv } from '../services/api/osv'; import type { ConsolidatedRowOut } from '../types/api'; import type { ConsRussianStatus } from '../types/consolidations/common_types'; import type { VersionCard } from '../types/consolidations/version_members'; import { exportFilename, saveBlob } from '../utils/download'; import { quarterShortLabel } from '../utils/labels'; import { statusColorMapping, statusMapping } from '../widgets/ConsoleTable/consts'; import { CounterpartyMapModal } from '../widgets/ConsolidatedOsv/CounterpartyMapModal'; import { OsvGaps } from '../widgets/ConsolidatedOsv/OsvGaps'; import { OsvTable } from '../widgets/ConsolidatedOsv/OsvTable'; import { OsvTabs } from '../widgets/ConsolidatedOsv/OsvTabs'; import { mapEntryId } from '../widgets/ConsolidatedOsv/counterpartyMapping'; import { OSV_PAGE_SIZE, useConsolidatedOsv } from '../widgets/ConsolidatedOsv/useConsolidatedOsv'; import { useCurrentUser } from '../auth/MeContext'; import ChangeStatusModal from '../widgets/StatusChangeModal/ChangeStatusModal'; function OsvContent({ versionId }: { versionId: string }) { const [card, setCard] = useState(null); const [statusModalOpen, setModalOpen] = useState(false); const [exporting, setExporting] = useState(false); // строка, для которой открыт выбор организации («Указать» или ИНН сопоставленного ЮЛ) const [mapping, setMapping] = useState(null); const osv = useConsolidatedOsv(versionId); const currentUser = useCurrentUser(); useEffect(() => { let actual = true; getVersionCard(versionId) .then((c) => { if (actual) setCard(c); }) .catch((e: unknown) => { if (actual) message.error(e instanceof ApiError ? e.message : 'Не удалось получить версию'); }); return () => { actual = false; }; }, [versionId]); const download = async () => { setExporting(true); try { const { blob, filename } = await exportOsv(versionId, osv.filters); saveBlob(blob, filename ?? exportFilename(card?.company_group.name ?? 'ГК', card?.year ?? 0, card?.quarter ?? '')); } catch (e: unknown) { message.error(e instanceof ApiError ? e.message : 'Не удалось выгрузить свод'); } finally { setExporting(false); } }; const s = osv.summary; const unmapped = s?.unmapped_total ?? 0; const onlyUnmapped = osv.filters.unmapped === true; const russianStatus = card ? (statusMapping[card.status] ?? card.status) : ''; const preset = statusColorMapping[russianStatus as ConsRussianStatus] ?? TAG_PRESETS.default; const groupName = card?.company_group.name ?? ''; const period = card ? `${card.year} · ${quarterShortLabel(card.quarter)}` : ''; return (
Статус отчёта {card.lock === null || (card.lock && card.lock.locked_by !== currentUser.id) ? ( ) : ( )} ) } />
{osv.error && } {unmapped > 0 && ( // `osv-mapping-alert` макета [30] / [32-osv-no-inn]
Требуется указать соответствия Для перехода к следующему шагу сопоставьте все внешние организации без ИНН. Осталось: {unmapped}. {/* макет оставляет кнопку как есть, снимать фильтр — воронкой ИНН или «Сбросить фильтры» (панель — позже) */}
)} ({ id: m.id, name: m.name }))} onPage={osv.setPage} onFilter={osv.patchFilters} onMap={setMapping} />
{mapping && ( setMapping(null)} onMapped={osv.refresh} /> )} { setModalOpen(false); }} />
); } export function ConsolidatedOsvPage() { const { version_id = '' } = useParams(); // key: при смене версии всё состояние (фильтры, страница, данные) создаётся заново return ; }