// frontend/src/pages/History.jsx import React, { useState, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { FileText, Clock, CheckCircle2, ChevronRight, Download, Eye, Trash2, Search, Filter, Calendar, Upload, Cpu, TableProperties, MonitorPlay, TrendingUp, TrendingDown, Minus, AlertCircle, X, FileSpreadsheet, Table2, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { cn } from "@/lib/utils"; import { getHistory } from "@/services/api"; // minimal "toast" const toastSuccess = (msg) => { console.log(msg); }; const stageConfig = { uploading: { label: "Uploading", icon: Upload, color: "blue" }, aiAnalysis: { label: "AI Analysis", icon: Cpu, color: "violet" }, dataExtraction: { label: "Data Extraction", icon: TableProperties, color: "emerald" }, outputRendering: { label: "Output Rendering", icon: MonitorPlay, color: "amber" }, }; const variationConfig = { fast: { icon: TrendingDown, color: "text-emerald-500", label: "Faster than avg" }, normal: { icon: Minus, color: "text-slate-400", label: "Normal" }, slow: { icon: TrendingUp, color: "text-amber-500", label: "Slower than avg" }, error: { icon: AlertCircle, color: "text-red-500", label: "Error" }, skipped: { icon: Minus, color: "text-slate-300", label: "Skipped" }, }; export default function History() { const [searchQuery, setSearchQuery] = useState(""); const [selectedStatus, setSelectedStatus] = useState("all"); const [expandedReport, setExpandedReport] = useState(null); const [isExporting, setIsExporting] = useState(false); const [history, setHistory] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); // Fetch history on component mount useEffect(() => { const fetchHistory = async () => { setIsLoading(true); setError(null); try { const data = await getHistory(); setHistory(data); } catch (err) { console.error("Failed to fetch history:", err); setError(err.message || "Failed to load history"); setHistory([]); // Fallback to empty array } finally { setIsLoading(false); } }; fetchHistory(); }, []); const filteredHistory = history.filter((item) => { const matchesSearch = item.fileName?.toLowerCase().includes(searchQuery.toLowerCase()) ?? false; const matchesStatus = selectedStatus === "all" || item.status === selectedStatus; return matchesSearch && matchesStatus; }); const formatTime = (ms) => { if (ms >= 1000) { return `${(ms / 1000).toFixed(2)}s`; } return `${ms}ms`; }; const formatTimeForExport = (ms) => { return ms >= 1000 ? `${(ms / 1000).toFixed(2)}s` : `${ms}ms`; }; const formatDate = (dateString) => { const date = new Date(dateString); return date.toLocaleDateString("en-US", { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); }; const formatDateForExport = (dateString) => { const date = new Date(dateString); return date.toISOString().replace("T", " ").slice(0, 19); }; const generateCSV = (data) => { const headers = [ "File Name", "File Type", "File Size", "Extracted At", "Status", "Confidence (%)", "Fields Extracted", "Total Time (ms)", "Upload Time (ms)", "Upload Status", "Upload Variation", "AI Analysis Time (ms)", "AI Analysis Status", "AI Analysis Variation", "Data Extraction Time (ms)", "Data Extraction Status", "Data Extraction Variation", "Output Rendering Time (ms)", "Output Rendering Status", "Output Rendering Variation", "Error Message", ]; const rows = data.map((item) => [ item.fileName, item.fileType, item.fileSize, formatDateForExport(item.extractedAt), item.status, item.confidence, item.fieldsExtracted, item.totalTime, item.stages.uploading.time, item.stages.uploading.status, item.stages.uploading.variation, item.stages.aiAnalysis.time, item.stages.aiAnalysis.status, item.stages.aiAnalysis.variation, item.stages.dataExtraction.time, item.stages.dataExtraction.status, item.stages.dataExtraction.variation, item.stages.outputRendering.time, item.stages.outputRendering.status, item.stages.outputRendering.variation, item.errorMessage || "", ]); const csvContent = [ headers.join(","), ...rows.map((row) => row.map((cell) => `"${cell}"`).join(",")), ].join("\n"); return csvContent; }; const downloadFile = (content, fileName, mimeType) => { const blob = new Blob([content], { type: mimeType }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = fileName; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }; const handleExportCSV = () => { setIsExporting(true); setTimeout(() => { const csvContent = generateCSV(filteredHistory); downloadFile( csvContent, `extraction_history_${new Date().toISOString().slice(0, 10)}.csv`, "text/csv;charset=utf-8;" ); toastSuccess("CSV exported successfully"); setIsExporting(false); }, 500); }; const generateExcelXML = (data) => { const headers = [ "File Name", "File Type", "File Size", "Extracted At", "Status", "Confidence (%)", "Fields Extracted", "Total Time (ms)", "Upload Time (ms)", "Upload Status", "Upload Variation", "AI Analysis Time (ms)", "AI Analysis Status", "AI Analysis Variation", "Data Extraction Time (ms)", "Data Extraction Status", "Data Extraction Variation", "Output Rendering Time (ms)", "Output Rendering Status", "Output Rendering Variation", "Error Message", ]; const rows = data.map((item) => [ item.fileName, item.fileType, item.fileSize, formatDateForExport(item.extractedAt), item.status, item.confidence, item.fieldsExtracted, item.totalTime, item.stages.uploading.time, item.stages.uploading.status, item.stages.uploading.variation, item.stages.aiAnalysis.time, item.stages.aiAnalysis.status, item.stages.aiAnalysis.variation, item.stages.dataExtraction.time, item.stages.dataExtraction.status, item.stages.dataExtraction.variation, item.stages.outputRendering.time, item.stages.outputRendering.status, item.stages.outputRendering.variation, item.errorMessage || "", ]); let xml = ` `; headers.forEach((header) => { xml += `${header}`; }); xml += ``; rows.forEach((row) => { xml += ``; row.forEach((cell) => { const type = typeof cell === "number" ? "Number" : "String"; xml += `${cell}`; }); xml += ``; }); xml += `
`; return xml; }; const handleExportExcel = () => { setIsExporting(true); setTimeout(() => { const excelContent = generateExcelXML(filteredHistory); downloadFile( excelContent, `extraction_history_${new Date().toISOString().slice(0, 10)}.xls`, "application/vnd.ms-excel" ); toastSuccess("Excel file exported successfully"); setIsExporting(false); }, 500); }; const handleExportSingleReport = (item, format) => { if (format === "csv") { const csvContent = generateCSV([item]); downloadFile( csvContent, `${item.fileName.replace(/\.[^/.]+$/, "")}_report.csv`, "text/csv;charset=utf-8;" ); toastSuccess("Report exported as CSV"); } else { const excelContent = generateExcelXML([item]); downloadFile( excelContent, `${item.fileName.replace(/\.[^/.]+$/, "")}_report.xls`, "application/vnd.ms-excel" ); toastSuccess("Report exported as Excel"); } }; return (
{/* Header */}

Extraction History

View detailed reports and performance metrics for all extractions

{/* Content */}
{/* Filters */}
setSearchQuery(e.target.value)} className="pl-10 h-11 rounded-xl border-slate-200" />
{/* Export All Button */} Export as CSV Export as Excel
{filteredHistory.length} records will be exported
{/* Stats Overview */}
{(() => { const total = history.length; const completed = history.filter((h) => h.status === "completed").length; const successRate = total > 0 ? ((completed / total) * 100).toFixed(1) : 0; const avgTime = history.length > 0 ? history.reduce((sum, h) => sum + (h.totalTime || 0), 0) / history.length : 0; const totalFields = history.reduce((sum, h) => sum + (h.fieldsExtracted || 0), 0); return [ { label: "Total Extractions", value: total.toString(), change: "", color: "indigo", }, { label: "Success Rate", value: `${successRate}%`, change: total > 0 ? `${completed}/${total} successful` : "No data", color: "emerald", }, { label: "Avg. Processing Time", value: avgTime >= 1000 ? `${(avgTime / 1000).toFixed(1)}s` : `${Math.round(avgTime)}ms`, change: "", color: "violet", }, { label: "Fields Extracted", value: totalFields.toLocaleString(), change: "", color: "amber", }, ].map((stat, index) => (

{stat.label}

{stat.value}

{stat.change}

)); })()}
{/* Loading State */} {isLoading && (

Loading extraction history...

)} {/* History List */} {!isLoading && (
{filteredHistory.map((item, index) => ( {/* Main Row */}
setExpandedReport( expandedReport === item.id ? null : item.id ) } >
{/* File Icon */}
{/* File Info */}

{item.fileName}

{item.fileType}
{item.fileSize} {formatDate(item.extractedAt)}
{/* Stats */}

Time

{formatTime(item.totalTime)}

Fields

{item.fieldsExtracted}

Confidence

= 95 ? "text-emerald-600" : item.confidence >= 90 ? "text-amber-600" : "text-red-600" )} > {item.confidence > 0 ? `${item.confidence}%` : "-"}

{/* Status & Actions */}
{item.status === "completed" ? ( ) : ( )} {item.status}
{/* Expanded Report */} {expandedReport === item.id && (
{/* Error Message */} {item.errorMessage && (
Error Details

{item.errorMessage}

)} {/* Performance Report Header */}

Performance Report

{ e.stopPropagation(); handleExportSingleReport(item, "csv"); }} > Download CSV { e.stopPropagation(); handleExportSingleReport(item, "excel"); }} > Download Excel
{/* Stage Timing Cards */}
{Object.entries(item.stages).map( ([stageKey, stageData]) => { const config = stageConfig[stageKey]; const variationInfo = variationConfig[stageData.variation]; const Icon = config.icon; const VariationIcon = variationInfo.icon; return (
{config.label}

{stageData.status === "skipped" ? "-" : formatTime(stageData.time)}

{stageData.status !== "skipped" && (
{variationInfo.label}
)}
{stageData.status === "completed" && ( )} {stageData.status === "failed" && ( )}
{/* Progress bar */}
); } )}
{/* Total Time Summary */}

Total Processing Time

From upload to output ready

{formatTime(item.totalTime)}

{item.status === "completed" ? "Completed successfully" : "Process failed"}

)}
))} {filteredHistory.length === 0 && !error && (

{history.length === 0 ? "No extraction history yet" : "No extractions match your filters"}

{history.length === 0 && (

Upload a document to get started

)}
)}
)}
); }