// 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 = `
View detailed reports and performance metrics for all extractions
{stat.label}
{stat.value}
{stat.change}
Loading extraction history...
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}%` : "-"}
{item.errorMessage}
{stageData.status === "skipped" ? "-" : formatTime(stageData.time)}
{stageData.status !== "skipped" && (Total Processing Time
From upload to output ready
{formatTime(item.totalTime)}
{item.status === "completed" ? "Completed successfully" : "Process failed"}
{history.length === 0 ? "No extraction history yet" : "No extractions match your filters"}
{history.length === 0 && (Upload a document to get started
)}