diff --git a/src/app/dashboard/documents/ocr/page.tsx b/src/app/dashboard/documents/ocr/page.tsx new file mode 100644 index 0000000..d906dfb --- /dev/null +++ b/src/app/dashboard/documents/ocr/page.tsx @@ -0,0 +1,156 @@ +'use client'; + +import Link from 'next/link'; +import { useQuery } from '@tanstack/react-query'; +import { apiFetch } from '../../../../lib/api'; +import { useSession } from '../../../../components/session-provider'; + +interface Document { + id: string; filename: string; classification: string; + ocrStatus: string | null; paperlessId: string | null; + checksum: string | null; createdAt: string; +} + +const B = '/dashboard'; + +const OCR_META: Record = { + pending: { label: 'În așteptare', cls: 'bg-signal-warn/10 text-signal-warn', desc: 'OCR nu a procesat documentul' }, + done: { label: 'Complet', cls: 'bg-signal-ok/10 text-signal-ok', desc: 'Text extras cu succes' }, + failed: { label: 'Eșuat', cls: 'bg-signal-danger/10 text-signal-danger', desc: 'OCR a eșuat, necesită corecție' }, + not_needed: { label: 'Fără OCR', cls: 'bg-muted text-ink-faint', desc: 'Nu necesită procesare OCR' }, +}; + +export default function OcrReviewPage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + + const { data: pendingDocs = [], isLoading: loadingPending } = useQuery({ + queryKey: ['docs-ocr-pending', tenantId], + queryFn: () => apiFetch('/v1/documents?ocrStatus=pending&limit=100', { tenantId }), + enabled: Boolean(tenantId), + staleTime: 60_000, + }); + + const { data: failedDocs = [], isLoading: loadingFailed } = useQuery({ + queryKey: ['docs-ocr-failed', tenantId], + queryFn: () => apiFetch('/v1/documents?ocrStatus=failed&limit=100', { tenantId }), + enabled: Boolean(tenantId), + staleTime: 60_000, + }); + + const { data: allDocs = [] } = useQuery({ + queryKey: ['docs-all', tenantId], + queryFn: () => apiFetch('/v1/documents?limit=200', { tenantId }), + enabled: Boolean(tenantId), + staleTime: 120_000, + }); + + const isLoading = loadingPending || loadingFailed; + + const ocrStats: Record = {}; + for (const d of allDocs) { + const k = d.ocrStatus ?? 'not_needed'; + ocrStats[k] = (ocrStats[k] || 0) + 1; + } + + return ( +
+
+
+

OCR Review

+

+ {pendingDocs.length} documente pendente · {failedDocs.length} eșuate +

+
+ + Toate documentele → + +
+ + {/* OCR status summary */} +
+ {Object.entries(OCR_META).map(([status, meta]) => ( +
+

+ {ocrStats[status] ?? 0} +

+

{meta.label}

+
+ ))} +
+ + {isLoading ? ( +
Se încarcă…
+ ) : pendingDocs.length === 0 && failedDocs.length === 0 ? ( +
+

+

Toate documentele au fost procesate OCR.

+
+ ) : ( + <> + {/* Failed OCR - needs attention first */} + {failedDocs.length > 0 && ( +
+

+ ⚠ OCR Eșuat ({failedDocs.length}) — necesită corecție manuală +

+
+ {failedDocs.map((d) => ( +
+ 📄 +
+

{d.filename}

+
+ {d.classification} + {d.paperlessId && pls:{d.paperlessId}} +
+
+ + {new Date(d.createdAt).toLocaleDateString('ro-RO')} + +
+ ))} +
+
+ )} + + {/* Pending OCR */} + {pendingDocs.length > 0 && ( +
+

+ OCR Pendente ({pendingDocs.length}) — în coadă +

+
+ {pendingDocs.slice(0, 20).map((d) => ( +
+ 📄 +
+

{d.filename}

+
+ {d.classification} + {d.checksum && {d.checksum.slice(0, 8)}} +
+
+ + {new Date(d.createdAt).toLocaleDateString('ro-RO')} + +
+ ))} + {pendingDocs.length > 20 && ( +

și alte {pendingDocs.length - 20} documente…

+ )} +
+
+ )} + +
+

+ Procesare OCR: Documentele sunt procesate automat de Paperless-ngx. + Cele cu status "Eșuat" necesită reîncărcare sau corecție manuală în interfața Paperless-ngx. +

+
+ + )} +
+ ); +}