import React, { useState, useEffect, useMemo, useRef } from 'react'; import { ExtractedField, FileData } from '../types'; import { Check, Edit2, Download, RefreshCw, FileText, AlertTriangle, XCircle, ArrowRight, PenTool, CheckCircle2, Circle, LayoutTemplate, List, Move, ExternalLink } from 'lucide-react'; import { createFilledPdf } from '../services/pdfService'; import { jsPDF } from "jspdf"; interface ReviewPanelProps { fields: ExtractedField[]; formFile: FileData; sourceFile: FileData; summary: string; isFillablePdf: boolean; onReset: () => void; } export const ReviewPanel: React.FC = ({ fields: initialFields, formFile, sourceFile, summary, isFillablePdf, onReset }) => { const [fields, setFields] = useState(initialFields); const [activeField, setActiveField] = useState(null); const [previewUrl, setPreviewUrl] = useState(null); const [filterMode, setFilterMode] = useState<'ALL' | 'ATTENTION'>('ALL'); const [viewMode, setViewMode] = useState<'LIST' | 'FORM'>('LIST'); // Dragging state const [draggingField, setDraggingField] = useState(null); const containerRef = useRef(null); // Derived state for progress const verifiedCount = fields.filter(f => f.isVerified).length; const totalCount = fields.length; const progressPercent = Math.round((verifiedCount / totalCount) * 100); const fieldsRequiresAttention = useMemo(() => fields.filter(f => f.validation?.status !== 'VALID'), [fields]); // Generate preview for PDF download useEffect(() => { let active = true; let timeoutId: any; const generatePreview = async () => { // If it's a PDF, we try to show the filled version if (formFile.type === 'application/pdf') { try { const filledPdfBytes = await createFilledPdf(formFile.base64, fields, isFillablePdf); if (!active) return; const blob = new Blob([filledPdfBytes], { type: 'application/pdf' }); const url = URL.createObjectURL(blob); setPreviewUrl(prev => { if (prev && prev.startsWith('blob:')) URL.revokeObjectURL(prev); return url; }); } catch (e) { console.error("Failed to generate PDF preview", e); // Fallback to original if generation fails setPreviewUrl(formFile.previewUrl); } } else { // For images, just show the original setPreviewUrl(formFile.previewUrl); } }; // Debounce to avoid excessive PDF generation while typing timeoutId = setTimeout(generatePreview, 1000); return () => { active = false; clearTimeout(timeoutId); }; }, [fields, isFillablePdf, formFile]); // Cleanup blob URL on unmount useEffect(() => { return () => { setPreviewUrl(prev => { if (prev && prev.startsWith('blob:') && prev !== formFile.previewUrl) { URL.revokeObjectURL(prev); } return null; }); }; }, []); const handleUpdate = (index: number, newValue: string) => { const newFields = [...fields]; newFields[index] = { ...newFields[index], value: newValue, // Auto-verify when manually edited isVerified: true, validation: { ...newFields[index].validation!, status: 'VALID', message: 'Manually verified' } }; setFields(newFields); }; const handleCoordinateUpdate = (index: number, x: number, y: number) => { const newFields = [...fields]; if (newFields[index].coordinates) { newFields[index] = { ...newFields[index], coordinates: { ...newFields[index].coordinates!, x, y }, isVerified: true // Moving it implies verification }; setFields(newFields); } }; const toggleVerify = (index: number) => { const newFields = [...fields]; newFields[index] = { ...newFields[index], isVerified: !newFields[index].isVerified }; setFields(newFields); }; const applySuggestion = (index: number) => { const field = fields[index]; if (field.validation?.suggestion) { handleUpdate(index, field.validation.suggestion); } }; const handleDownload = async () => { if (formFile.type === 'application/pdf' && previewUrl) { const a = document.createElement('a'); a.href = previewUrl; a.download = `filled_${formFile.file.name}`; document.body.appendChild(a); a.click(); document.body.removeChild(a); } else { const doc = new jsPDF(); doc.text("Extracted Data", 20, 20); let y = 40; fields.forEach(f => { doc.text(`${f.label}: ${f.value}`, 20, y); y += 10; }); doc.save("data_report.pdf"); } }; // Drag handlers const handleDragStart = (e: React.MouseEvent, index: number) => { e.stopPropagation(); setDraggingField(index); setActiveField(index); }; const handleMouseMove = (e: React.MouseEvent) => { if (draggingField !== null && containerRef.current) { const rect = containerRef.current.getBoundingClientRect(); const relativeX = e.clientX - rect.left; const relativeY = e.clientY - rect.top; // Convert pixels to 0-1000 scale const scaleX = (relativeX / rect.width) * 1000; const scaleY = (relativeY / rect.height) * 1000; // Clamp values const clampedX = Math.max(0, Math.min(1000, scaleX)); const clampedY = Math.max(0, Math.min(1000, scaleY)); handleCoordinateUpdate(draggingField, clampedX, clampedY); } }; const handleMouseUp = () => { setDraggingField(null); }; // Sort: Unverified/Issues first, then verified const displayedFields = fields.map((f, i) => ({ ...f, originalIndex: i })) .sort((a, b) => { // Priority 1: Attention needed const aNeedsAttn = a.validation?.status !== 'VALID'; const bNeedsAttn = b.validation?.status !== 'VALID'; if (aNeedsAttn && !bNeedsAttn) return -1; if (!aNeedsAttn && bNeedsAttn) return 1; // Priority 2: Unverified if (!a.isVerified && b.isVerified) return -1; if (a.isVerified && !b.isVerified) return 1; return a.originalIndex - b.originalIndex; }) .filter(f => filterMode === 'ALL' || (f.validation?.status !== 'VALID' && !f.isVerified)); return (
{/* Header Section */}

Review & Verify

{summary}

{/* View Toggle */}
{/* Verification Progress */}
{verifiedCount} / {totalCount} Verified
{viewMode === 'LIST' ? ( /* ================= LIST VIEW ================= */
{/* Left Column: Preview */}
PDF Preview (Live)
{formFile.file.name}
{previewUrl ? ( formFile.type === 'application/pdf' ? ( <>