import { Alert, Button, DatePicker, Descriptions, Drawer, Result, Select, Space, Spin, Tabs, message } from 'antd'; import type { Dayjs } from 'dayjs'; import { useEffect, useState } from 'react'; import { ApiError } from '../../api/client'; import { exportFacts, getMatrix } from '../../services/api/qualityMetrics'; import type { DocumentType } from '../../types/api'; import type { ExportFormat, QualityCell, QualityFilters, QualityMatrixOut, RecognitionPipeline } from '../../types/qualityMetrics'; import { saveBlob } from '../../utils/download'; import { DocumentDrillList } from './DocumentDrillList'; import { MatrixTable } from './MatrixTable'; import { MonthlyTrend } from './MonthlyTrend'; import { formatShare } from './qualityView'; const DOC_TYPES: { value: DocumentType; label: string }[] = [ { value: 'BALANCE', label: 'Бухгалтерский баланс' }, { value: 'INCOME', label: 'ОФР' }, { value: 'OSV', label: 'ОСВ' }, ]; const PIPELINES: { value: RecognitionPipeline; label: string }[] = [ { value: 'image', label: 'PDF (распознавание)' }, { value: 'tabular', label: 'Excel' }, ]; const SCAN_OPTIONS = [ { value: 'true', label: 'Скан' }, { value: 'false', label: 'Цифровой PDF' }, ]; const iso = (d: Dayjs): string => d.format('YYYY-MM-DD'); /** Экран «Качество первички»: фильтры, матрица причин, технические отказы, динамика по месяцам, выгрузка. */ export function QualityMetricsPage() { const [filters, setFilters] = useState({}); const [matrix, setMatrix] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [drill, setDrill] = useState<{ cell: QualityCell; label: string } | null>(null); useEffect(() => { let cancelled = false; setLoading(true); setError(null); getMatrix(filters) .then((r) => { if (!cancelled) setMatrix(r); }) .catch((e: unknown) => { if (cancelled) return; setMatrix(null); setError(e instanceof Error ? e : new Error('Не удалось загрузить метрики')); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [filters]); const patch = (changes: Partial) => setFilters((f) => ({ ...f, ...changes })); const download = async (format: ExportFormat) => { try { const { blob, filename } = await exportFacts(filters, format); saveBlob(blob, filename ?? `quality-facts.${format}`); } catch (e: unknown) { message.error(e instanceof Error ? e.message : 'Не удалось выгрузить данные'); } }; if (error instanceof ApiError && error.status === 403) { return ; } return (

Качество первички

patch({ date_from: range?.[0] ? iso(range[0]) : undefined, date_to: range?.[1] ? iso(range[1]) : undefined }) } /> allowClear placeholder='Тип документа' style={{ width: 200 }} options={DOC_TYPES} onChange={(v) => patch({ document_type: v })} /> allowClear placeholder='Источник' style={{ width: 200 }} options={PIPELINES} onChange={(v) => patch({ pipeline: v })} /> allowClear placeholder='Скан / цифровой' style={{ width: 170 }} options={SCAN_OPTIONS} onChange={(v) => patch({ is_scan: v === undefined ? undefined : v === 'true' })} /> {error && } {matrix && matrix.denominator === 0 && matrix.in_progress_count === 0 && ( )} {matrix && ( <> {matrix.denominator} {matrix.unique_reports_count} {matrix.technical_failures.failed_files_count} из {matrix.technical_failures.total_files_count} ( {formatShare(matrix.technical_failures.share, matrix.technical_failures.total_files_count)}) setDrill({ cell, label })} />, }, { key: 'trend', label: 'Динамика по месяцам', children: }, ]} /> )} setDrill(null)} destroyOnClose> {drill && }
); }