Seth0330 commited on
Commit
389148d
·
verified ·
1 Parent(s): 582c857

Update frontend/src/pages/History.jsx

Browse files
Files changed (1) hide show
  1. frontend/src/pages/History.jsx +865 -6
frontend/src/pages/History.jsx CHANGED
@@ -1,12 +1,871 @@
1
- import React from "react";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  export default function History() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  return (
5
- <div className="min-h-screen bg-[#FAFAFA] p-8">
6
- <h1 className="text-xl font-bold text-slate-900 mb-2">Extraction History</h1>
7
- <p className="text-sm text-slate-500">
8
- This is a placeholder. Next step, we’ll plug in your full Base44 History UI here.
9
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  </div>
11
  );
12
  }
 
1
+ // frontend/src/pages/History.jsx
2
+
3
+ import React, { useState } from "react";
4
+ import { motion, AnimatePresence } from "framer-motion";
5
+ import {
6
+ FileText,
7
+ Clock,
8
+ CheckCircle2,
9
+ ChevronRight,
10
+ Download,
11
+ Eye,
12
+ Trash2,
13
+ Search,
14
+ Filter,
15
+ Calendar,
16
+ Upload,
17
+ Cpu,
18
+ TableProperties,
19
+ MonitorPlay,
20
+ TrendingUp,
21
+ TrendingDown,
22
+ Minus,
23
+ AlertCircle,
24
+ X,
25
+ FileSpreadsheet,
26
+ Table2,
27
+ } from "lucide-react";
28
+ import { Button } from "@/components/ui/button";
29
+ import { Input } from "@/components/ui/input";
30
+ import { Badge } from "@/components/ui/badge";
31
+ import {
32
+ Select,
33
+ SelectContent,
34
+ SelectItem,
35
+ SelectTrigger,
36
+ SelectValue,
37
+ } from "@/components/ui/select";
38
+ import {
39
+ DropdownMenu,
40
+ DropdownMenuContent,
41
+ DropdownMenuItem,
42
+ DropdownMenuSeparator,
43
+ DropdownMenuTrigger,
44
+ } from "@/components/ui/dropdown-menu";
45
+ import { cn } from "@/lib/utils";
46
+
47
+ // minimal “toast”
48
+ const toastSuccess = (msg) => {
49
+ console.log(msg);
50
+ };
51
+
52
+ // Mock history data with detailed timing
53
+ const mockHistory = [
54
+ {
55
+ id: 1,
56
+ fileName: "Invoice_Acme_Corp_2024.pdf",
57
+ fileType: "PDF",
58
+ fileSize: "2.4 MB",
59
+ extractedAt: "2024-01-15T14:32:00",
60
+ status: "completed",
61
+ confidence: 98.5,
62
+ fieldsExtracted: 25,
63
+ totalTime: 2847,
64
+ stages: {
65
+ uploading: { time: 342, status: "completed", variation: "normal" },
66
+ aiAnalysis: { time: 1523, status: "completed", variation: "normal" },
67
+ dataExtraction: { time: 756, status: "completed", variation: "fast" },
68
+ outputRendering: { time: 226, status: "completed", variation: "normal" },
69
+ },
70
+ },
71
+ {
72
+ id: 2,
73
+ fileName: "Contract_Agreement_v2.docx",
74
+ fileType: "DOCX",
75
+ fileSize: "1.8 MB",
76
+ extractedAt: "2024-01-15T12:15:00",
77
+ status: "completed",
78
+ confidence: 96.2,
79
+ fieldsExtracted: 42,
80
+ totalTime: 3421,
81
+ stages: {
82
+ uploading: { time: 289, status: "completed", variation: "fast" },
83
+ aiAnalysis: { time: 2134, status: "completed", variation: "slow" },
84
+ dataExtraction: { time: 812, status: "completed", variation: "normal" },
85
+ outputRendering: { time: 186, status: "completed", variation: "fast" },
86
+ },
87
+ },
88
+ {
89
+ id: 3,
90
+ fileName: "Receipt_Amazon_Jan15.jpg",
91
+ fileType: "JPG",
92
+ fileSize: "856 KB",
93
+ extractedAt: "2024-01-15T10:45:00",
94
+ status: "completed",
95
+ confidence: 99.1,
96
+ fieldsExtracted: 12,
97
+ totalTime: 1893,
98
+ stages: {
99
+ uploading: { time: 156, status: "completed", variation: "fast" },
100
+ aiAnalysis: { time: 1245, status: "completed", variation: "normal" },
101
+ dataExtraction: { time: 367, status: "completed", variation: "fast" },
102
+ outputRendering: { time: 125, status: "completed", variation: "fast" },
103
+ },
104
+ },
105
+ {
106
+ id: 4,
107
+ fileName: "Financial_Report_Q4.xlsx",
108
+ fileType: "XLSX",
109
+ fileSize: "4.2 MB",
110
+ extractedAt: "2024-01-14T16:20:00",
111
+ status: "completed",
112
+ confidence: 94.8,
113
+ fieldsExtracted: 156,
114
+ totalTime: 5632,
115
+ stages: {
116
+ uploading: { time: 567, status: "completed", variation: "slow" },
117
+ aiAnalysis: { time: 3245, status: "completed", variation: "slow" },
118
+ dataExtraction: { time: 1456, status: "completed", variation: "slow" },
119
+ outputRendering: { time: 364, status: "completed", variation: "normal" },
120
+ },
121
+ },
122
+ {
123
+ id: 5,
124
+ fileName: "Scan_Document_001.png",
125
+ fileType: "PNG",
126
+ fileSize: "3.1 MB",
127
+ extractedAt: "2024-01-14T09:30:00",
128
+ status: "failed",
129
+ confidence: 0,
130
+ fieldsExtracted: 0,
131
+ totalTime: 2156,
132
+ stages: {
133
+ uploading: { time: 423, status: "completed", variation: "normal" },
134
+ aiAnalysis: { time: 1733, status: "failed", variation: "error" },
135
+ dataExtraction: { time: 0, status: "skipped", variation: "skipped" },
136
+ outputRendering: { time: 0, status: "skipped", variation: "skipped" },
137
+ },
138
+ errorMessage: "Unable to detect text in image. Image quality too low.",
139
+ },
140
+ ];
141
+
142
+ const stageConfig = {
143
+ uploading: { label: "Uploading", icon: Upload, color: "blue" },
144
+ aiAnalysis: { label: "AI Analysis", icon: Cpu, color: "violet" },
145
+ dataExtraction: { label: "Data Extraction", icon: TableProperties, color: "emerald" },
146
+ outputRendering: { label: "Output Rendering", icon: MonitorPlay, color: "amber" },
147
+ };
148
+
149
+ const variationConfig = {
150
+ fast: { icon: TrendingDown, color: "text-emerald-500", label: "Faster than avg" },
151
+ normal: { icon: Minus, color: "text-slate-400", label: "Normal" },
152
+ slow: { icon: TrendingUp, color: "text-amber-500", label: "Slower than avg" },
153
+ error: { icon: AlertCircle, color: "text-red-500", label: "Error" },
154
+ skipped: { icon: Minus, color: "text-slate-300", label: "Skipped" },
155
+ };
156
 
157
  export default function History() {
158
+ const [searchQuery, setSearchQuery] = useState("");
159
+ const [selectedStatus, setSelectedStatus] = useState("all");
160
+ const [expandedReport, setExpandedReport] = useState(null);
161
+ const [isExporting, setIsExporting] = useState(false);
162
+
163
+ const filteredHistory = mockHistory.filter((item) => {
164
+ const matchesSearch = item.fileName.toLowerCase().includes(searchQuery.toLowerCase());
165
+ const matchesStatus = selectedStatus === "all" || item.status === selectedStatus;
166
+ return matchesSearch && matchesStatus;
167
+ });
168
+
169
+ const formatTime = (ms) => {
170
+ if (ms >= 1000) {
171
+ return `${(ms / 1000).toFixed(2)}s`;
172
+ }
173
+ return `${ms}ms`;
174
+ };
175
+
176
+ const formatTimeForExport = (ms) => {
177
+ return ms >= 1000 ? `${(ms / 1000).toFixed(2)}s` : `${ms}ms`;
178
+ };
179
+
180
+ const formatDate = (dateString) => {
181
+ const date = new Date(dateString);
182
+ return date.toLocaleDateString("en-US", {
183
+ month: "short",
184
+ day: "numeric",
185
+ hour: "2-digit",
186
+ minute: "2-digit",
187
+ });
188
+ };
189
+
190
+ const formatDateForExport = (dateString) => {
191
+ const date = new Date(dateString);
192
+ return date.toISOString().replace("T", " ").slice(0, 19);
193
+ };
194
+
195
+ const generateCSV = (data) => {
196
+ const headers = [
197
+ "File Name",
198
+ "File Type",
199
+ "File Size",
200
+ "Extracted At",
201
+ "Status",
202
+ "Confidence (%)",
203
+ "Fields Extracted",
204
+ "Total Time (ms)",
205
+ "Upload Time (ms)",
206
+ "Upload Status",
207
+ "Upload Variation",
208
+ "AI Analysis Time (ms)",
209
+ "AI Analysis Status",
210
+ "AI Analysis Variation",
211
+ "Data Extraction Time (ms)",
212
+ "Data Extraction Status",
213
+ "Data Extraction Variation",
214
+ "Output Rendering Time (ms)",
215
+ "Output Rendering Status",
216
+ "Output Rendering Variation",
217
+ "Error Message",
218
+ ];
219
+
220
+ const rows = data.map((item) => [
221
+ item.fileName,
222
+ item.fileType,
223
+ item.fileSize,
224
+ formatDateForExport(item.extractedAt),
225
+ item.status,
226
+ item.confidence,
227
+ item.fieldsExtracted,
228
+ item.totalTime,
229
+ item.stages.uploading.time,
230
+ item.stages.uploading.status,
231
+ item.stages.uploading.variation,
232
+ item.stages.aiAnalysis.time,
233
+ item.stages.aiAnalysis.status,
234
+ item.stages.aiAnalysis.variation,
235
+ item.stages.dataExtraction.time,
236
+ item.stages.dataExtraction.status,
237
+ item.stages.dataExtraction.variation,
238
+ item.stages.outputRendering.time,
239
+ item.stages.outputRendering.status,
240
+ item.stages.outputRendering.variation,
241
+ item.errorMessage || "",
242
+ ]);
243
+
244
+ const csvContent = [
245
+ headers.join(","),
246
+ ...rows.map((row) => row.map((cell) => `"${cell}"`).join(",")),
247
+ ].join("\n");
248
+
249
+ return csvContent;
250
+ };
251
+
252
+ const downloadFile = (content, fileName, mimeType) => {
253
+ const blob = new Blob([content], { type: mimeType });
254
+ const url = URL.createObjectURL(blob);
255
+ const link = document.createElement("a");
256
+ link.href = url;
257
+ link.download = fileName;
258
+ document.body.appendChild(link);
259
+ link.click();
260
+ document.body.removeChild(link);
261
+ URL.revokeObjectURL(url);
262
+ };
263
+
264
+ const handleExportCSV = () => {
265
+ setIsExporting(true);
266
+ setTimeout(() => {
267
+ const csvContent = generateCSV(filteredHistory);
268
+ downloadFile(
269
+ csvContent,
270
+ `extraction_history_${new Date().toISOString().slice(0, 10)}.csv`,
271
+ "text/csv;charset=utf-8;"
272
+ );
273
+ toastSuccess("CSV exported successfully");
274
+ setIsExporting(false);
275
+ }, 500);
276
+ };
277
+
278
+ const generateExcelXML = (data) => {
279
+ const headers = [
280
+ "File Name",
281
+ "File Type",
282
+ "File Size",
283
+ "Extracted At",
284
+ "Status",
285
+ "Confidence (%)",
286
+ "Fields Extracted",
287
+ "Total Time (ms)",
288
+ "Upload Time (ms)",
289
+ "Upload Status",
290
+ "Upload Variation",
291
+ "AI Analysis Time (ms)",
292
+ "AI Analysis Status",
293
+ "AI Analysis Variation",
294
+ "Data Extraction Time (ms)",
295
+ "Data Extraction Status",
296
+ "Data Extraction Variation",
297
+ "Output Rendering Time (ms)",
298
+ "Output Rendering Status",
299
+ "Output Rendering Variation",
300
+ "Error Message",
301
+ ];
302
+
303
+ const rows = data.map((item) => [
304
+ item.fileName,
305
+ item.fileType,
306
+ item.fileSize,
307
+ formatDateForExport(item.extractedAt),
308
+ item.status,
309
+ item.confidence,
310
+ item.fieldsExtracted,
311
+ item.totalTime,
312
+ item.stages.uploading.time,
313
+ item.stages.uploading.status,
314
+ item.stages.uploading.variation,
315
+ item.stages.aiAnalysis.time,
316
+ item.stages.aiAnalysis.status,
317
+ item.stages.aiAnalysis.variation,
318
+ item.stages.dataExtraction.time,
319
+ item.stages.dataExtraction.status,
320
+ item.stages.dataExtraction.variation,
321
+ item.stages.outputRendering.time,
322
+ item.stages.outputRendering.status,
323
+ item.stages.outputRendering.variation,
324
+ item.errorMessage || "",
325
+ ]);
326
+
327
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>
328
+ <?mso-application progid="Excel.Sheet"?>
329
+ <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
330
+ xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
331
+ <Worksheet ss:Name="Extraction History">
332
+ <Table>
333
+ <Row>`;
334
+
335
+ headers.forEach((header) => {
336
+ xml += `<Cell><Data ss:Type="String">${header}</Data></Cell>`;
337
+ });
338
+ xml += `</Row>`;
339
+
340
+ rows.forEach((row) => {
341
+ xml += `<Row>`;
342
+ row.forEach((cell) => {
343
+ const type = typeof cell === "number" ? "Number" : "String";
344
+ xml += `<Cell><Data ss:Type="${type}">${cell}</Data></Cell>`;
345
+ });
346
+ xml += `</Row>`;
347
+ });
348
+
349
+ xml += `</Table></Worksheet></Workbook>`;
350
+ return xml;
351
+ };
352
+
353
+ const handleExportExcel = () => {
354
+ setIsExporting(true);
355
+ setTimeout(() => {
356
+ const excelContent = generateExcelXML(filteredHistory);
357
+ downloadFile(
358
+ excelContent,
359
+ `extraction_history_${new Date().toISOString().slice(0, 10)}.xls`,
360
+ "application/vnd.ms-excel"
361
+ );
362
+ toastSuccess("Excel file exported successfully");
363
+ setIsExporting(false);
364
+ }, 500);
365
+ };
366
+
367
+ const handleExportSingleReport = (item, format) => {
368
+ if (format === "csv") {
369
+ const csvContent = generateCSV([item]);
370
+ downloadFile(
371
+ csvContent,
372
+ `${item.fileName.replace(/\.[^/.]+$/, "")}_report.csv`,
373
+ "text/csv;charset=utf-8;"
374
+ );
375
+ toastSuccess("Report exported as CSV");
376
+ } else {
377
+ const excelContent = generateExcelXML([item]);
378
+ downloadFile(
379
+ excelContent,
380
+ `${item.fileName.replace(/\.[^/.]+$/, "")}_report.xls`,
381
+ "application/vnd.ms-excel"
382
+ );
383
+ toastSuccess("Report exported as Excel");
384
+ }
385
+ };
386
+
387
  return (
388
+ <div className="min-h-screen bg-[#FAFAFA]">
389
+ {/* Header */}
390
+ <header className="bg-white border-b border-slate-200/80 sticky top-0 z-40">
391
+ <div className="px-8 py-4">
392
+ <h1 className="text-xl font-bold text-slate-900 tracking-tight">
393
+ Extraction History
394
+ </h1>
395
+ <p className="text-sm text-slate-500 mt-0.5">
396
+ View detailed reports and performance metrics for all extractions
397
+ </p>
398
+ </div>
399
+ </header>
400
+
401
+ {/* Content */}
402
+ <div className="p-8">
403
+ {/* Filters */}
404
+ <div className="flex items-center gap-4 mb-6">
405
+ <div className="relative flex-1 max-w-md">
406
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-400" />
407
+ <Input
408
+ placeholder="Search by file name..."
409
+ value={searchQuery}
410
+ onChange={(e) => setSearchQuery(e.target.value)}
411
+ className="pl-10 h-11 rounded-xl border-slate-200"
412
+ />
413
+ </div>
414
+ <Select
415
+ value={selectedStatus}
416
+ onValueChange={(value) => setSelectedStatus(value)}
417
+ >
418
+ <SelectTrigger className="w-40 h-11 rounded-xl border-slate-200">
419
+ <Filter className="h-4 w-4 mr-2 text-slate-400" />
420
+ <SelectValue placeholder="Status" />
421
+ </SelectTrigger>
422
+ <SelectContent>
423
+ <SelectItem value="all">All Status</SelectItem>
424
+ <SelectItem value="completed">Completed</SelectItem>
425
+ <SelectItem value="failed">Failed</SelectItem>
426
+ </SelectContent>
427
+ </Select>
428
+
429
+ {/* Export All Button */}
430
+ <DropdownMenu>
431
+ <DropdownMenuTrigger asChild>
432
+ <Button
433
+ className="h-11 px-4 rounded-xl bg-gradient-to-r from-indigo-600 to-violet-600 hover:from-indigo-700 hover:to-violet-700 shadow-lg shadow-indigo-500/25"
434
+ disabled={isExporting || filteredHistory.length === 0}
435
+ >
436
+ {isExporting ? (
437
+ <motion.div
438
+ animate={{ rotate: 360 }}
439
+ transition={{
440
+ duration: 1,
441
+ repeat: Infinity,
442
+ ease: "linear",
443
+ }}
444
+ className="mr-2"
445
+ >
446
+ <Download className="h-4 w-4" />
447
+ </motion.div>
448
+ ) : (
449
+ <Download className="h-4 w-4 mr-2" />
450
+ )}
451
+ Export All
452
+ </Button>
453
+ </DropdownMenuTrigger>
454
+ <DropdownMenuContent
455
+ align="end"
456
+ className="w-48 rounded-xl p-2"
457
+ >
458
+ <DropdownMenuItem
459
+ className="rounded-lg cursor-pointer"
460
+ onClick={handleExportCSV}
461
+ >
462
+ <Table2 className="h-4 w-4 mr-2 text-emerald-600" />
463
+ Export as CSV
464
+ </DropdownMenuItem>
465
+ <DropdownMenuItem
466
+ className="rounded-lg cursor-pointer"
467
+ onClick={handleExportExcel}
468
+ >
469
+ <FileSpreadsheet className="h-4 w-4 mr-2 text-green-600" />
470
+ Export as Excel
471
+ </DropdownMenuItem>
472
+ <DropdownMenuSeparator />
473
+ <div className="px-2 py-1.5 text-xs text-slate-500">
474
+ {filteredHistory.length} records will be exported
475
+ </div>
476
+ </DropdownMenuContent>
477
+ </DropdownMenu>
478
+ </div>
479
+
480
+ {/* Stats Overview */}
481
+ <div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
482
+ {[
483
+ {
484
+ label: "Total Extractions",
485
+ value: "247",
486
+ change: "+12 today",
487
+ color: "indigo",
488
+ },
489
+ {
490
+ label: "Success Rate",
491
+ value: "98.5%",
492
+ change: "+0.3%",
493
+ color: "emerald",
494
+ },
495
+ {
496
+ label: "Avg. Processing Time",
497
+ value: "2.8s",
498
+ change: "-0.2s",
499
+ color: "violet",
500
+ },
501
+ {
502
+ label: "Fields Extracted",
503
+ value: "4,521",
504
+ change: "+156 today",
505
+ color: "amber",
506
+ },
507
+ ].map((stat, index) => (
508
+ <motion.div
509
+ key={stat.label}
510
+ initial={{ opacity: 0, y: 20 }}
511
+ animate={{ opacity: 1, y: 0 }}
512
+ transition={{ delay: index * 0.1 }}
513
+ className="bg-white rounded-2xl border border-slate-200 p-5"
514
+ >
515
+ <p className="text-sm text-slate-500 mb-1">{stat.label}</p>
516
+ <p className="text-2xl font-bold text-slate-900">{stat.value}</p>
517
+ <p className={`text-xs text-${stat.color}-600 mt-1`}>
518
+ {stat.change}
519
+ </p>
520
+ </motion.div>
521
+ ))}
522
+ </div>
523
+
524
+ {/* History List */}
525
+ <div className="space-y-4">
526
+ {filteredHistory.map((item, index) => (
527
+ <motion.div
528
+ key={item.id}
529
+ initial={{ opacity: 0, y: 20 }}
530
+ animate={{ opacity: 1, y: 0 }}
531
+ transition={{ delay: index * 0.05 }}
532
+ className="bg-white rounded-2xl border border-slate-200 overflow-hidden"
533
+ >
534
+ {/* Main Row */}
535
+ <div
536
+ className="p-5 cursor-pointer hover:bg-slate-50/50 transition-colors"
537
+ onClick={() =>
538
+ setExpandedReport(
539
+ expandedReport === item.id ? null : item.id
540
+ )
541
+ }
542
+ >
543
+ <div className="flex items-center gap-4">
544
+ {/* File Icon */}
545
+ <div
546
+ className={cn(
547
+ "h-12 w-12 rounded-xl flex items-center justify-center",
548
+ item.status === "completed" ? "bg-indigo-50" : "bg-red-50"
549
+ )}
550
+ >
551
+ <FileText
552
+ className={cn(
553
+ "h-6 w-6",
554
+ item.status === "completed"
555
+ ? "text-indigo-600"
556
+ : "text-red-500"
557
+ )}
558
+ />
559
+ </div>
560
+
561
+ {/* File Info */}
562
+ <div className="flex-1 min-w-0">
563
+ <div className="flex items-center gap-2">
564
+ <h3 className="font-semibold text-slate-900 truncate">
565
+ {item.fileName}
566
+ </h3>
567
+ <Badge variant="secondary" className="text-xs">
568
+ {item.fileType}
569
+ </Badge>
570
+ </div>
571
+ <div className="flex items-center gap-4 mt-1 text-sm text-slate-500">
572
+ <span>{item.fileSize}</span>
573
+ <span className="flex items-center gap-1">
574
+ <Calendar className="h-3 w-3" />
575
+ {formatDate(item.extractedAt)}
576
+ </span>
577
+ </div>
578
+ </div>
579
+
580
+ {/* Stats */}
581
+ <div className="hidden md:flex items-center gap-6">
582
+ <div className="text-center">
583
+ <p className="text-xs text-slate-400">Time</p>
584
+ <p className="font-semibold text-slate-700">
585
+ {formatTime(item.totalTime)}
586
+ </p>
587
+ </div>
588
+ <div className="text-center">
589
+ <p className="text-xs text-slate-400">Fields</p>
590
+ <p className="font-semibold text-slate-700">
591
+ {item.fieldsExtracted}
592
+ </p>
593
+ </div>
594
+ <div className="text-center">
595
+ <p className="text-xs text-slate-400">Confidence</p>
596
+ <p
597
+ className={cn(
598
+ "font-semibold",
599
+ item.confidence >= 95
600
+ ? "text-emerald-600"
601
+ : item.confidence >= 90
602
+ ? "text-amber-600"
603
+ : "text-red-600"
604
+ )}
605
+ >
606
+ {item.confidence > 0 ? `${item.confidence}%` : "-"}
607
+ </p>
608
+ </div>
609
+ </div>
610
+
611
+ {/* Status & Actions */}
612
+ <div className="flex items-center gap-3">
613
+ <Badge
614
+ className={cn(
615
+ "capitalize",
616
+ item.status === "completed"
617
+ ? "bg-emerald-50 text-emerald-700 border-emerald-200"
618
+ : "bg-red-50 text-red-700 border-red-200"
619
+ )}
620
+ >
621
+ {item.status === "completed" ? (
622
+ <CheckCircle2 className="h-3 w-3 mr-1" />
623
+ ) : (
624
+ <AlertCircle className="h-3 w-3 mr-1" />
625
+ )}
626
+ {item.status}
627
+ </Badge>
628
+ <ChevronRight
629
+ className={cn(
630
+ "h-5 w-5 text-slate-400 transition-transform",
631
+ expandedReport === item.id && "rotate-90"
632
+ )}
633
+ />
634
+ </div>
635
+ </div>
636
+ </div>
637
+
638
+ {/* Expanded Report */}
639
+ <AnimatePresence>
640
+ {expandedReport === item.id && (
641
+ <motion.div
642
+ initial={{ height: 0, opacity: 0 }}
643
+ animate={{ height: "auto", opacity: 1 }}
644
+ exit={{ height: 0, opacity: 0 }}
645
+ transition={{ duration: 0.2 }}
646
+ className="overflow-hidden"
647
+ >
648
+ <div className="px-5 pb-5 pt-2 border-t border-slate-100">
649
+ {/* Error Message */}
650
+ {item.errorMessage && (
651
+ <div className="mb-4 p-4 bg-red-50 border border-red-100 rounded-xl">
652
+ <div className="flex items-center gap-2 text-red-700">
653
+ <AlertCircle className="h-4 w-4" />
654
+ <span className="font-medium">Error Details</span>
655
+ </div>
656
+ <p className="text-sm text-red-600 mt-1">
657
+ {item.errorMessage}
658
+ </p>
659
+ </div>
660
+ )}
661
+
662
+ {/* Performance Report Header */}
663
+ <div className="flex items-center justify-between mb-4">
664
+ <h4 className="font-semibold text-slate-800">
665
+ Performance Report
666
+ </h4>
667
+ <div className="flex items-center gap-2">
668
+ <Button
669
+ variant="ghost"
670
+ size="sm"
671
+ className="h-8 text-xs"
672
+ >
673
+ <Eye className="h-3 w-3 mr-1" />
674
+ View Output
675
+ </Button>
676
+ <DropdownMenu>
677
+ <DropdownMenuTrigger asChild>
678
+ <Button
679
+ variant="outline"
680
+ size="sm"
681
+ className="h-8 text-xs"
682
+ >
683
+ <Download className="h-3 w-3 mr-1" />
684
+ Export Report
685
+ </Button>
686
+ </DropdownMenuTrigger>
687
+ <DropdownMenuContent
688
+ align="end"
689
+ className="w-44 rounded-xl p-2"
690
+ >
691
+ <DropdownMenuItem
692
+ className="rounded-lg cursor-pointer text-xs"
693
+ onClick={(e) => {
694
+ e.stopPropagation();
695
+ handleExportSingleReport(item, "csv");
696
+ }}
697
+ >
698
+ <Table2 className="h-3 w-3 mr-2 text-emerald-600" />
699
+ Download CSV
700
+ </DropdownMenuItem>
701
+ <DropdownMenuItem
702
+ className="rounded-lg cursor-pointer text-xs"
703
+ onClick={(e) => {
704
+ e.stopPropagation();
705
+ handleExportSingleReport(item, "excel");
706
+ }}
707
+ >
708
+ <FileSpreadsheet className="h-3 w-3 mr-2 text-green-600" />
709
+ Download Excel
710
+ </DropdownMenuItem>
711
+ </DropdownMenuContent>
712
+ </DropdownMenu>
713
+ </div>
714
+ </div>
715
+
716
+ {/* Stage Timing Cards */}
717
+ <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
718
+ {Object.entries(item.stages).map(
719
+ ([stageKey, stageData]) => {
720
+ const config = stageConfig[stageKey];
721
+ const variationInfo =
722
+ variationConfig[stageData.variation];
723
+ const Icon = config.icon;
724
+ const VariationIcon = variationInfo.icon;
725
+
726
+ return (
727
+ <div
728
+ key={stageKey}
729
+ className={cn(
730
+ "relative p-4 rounded-xl border",
731
+ stageData.status === "completed"
732
+ ? "bg-slate-50 border-slate-200"
733
+ : stageData.status === "failed"
734
+ ? "bg-red-50 border-red-200"
735
+ : "bg-slate-50/50 border-slate-100"
736
+ )}
737
+ >
738
+ <div className="flex items-center gap-2 mb-3">
739
+ <div
740
+ className={cn(
741
+ "h-8 w-8 rounded-lg flex items-center justify-center",
742
+ `bg-${config.color}-100`
743
+ )}
744
+ >
745
+ <Icon
746
+ className={cn(
747
+ "h-4 w-4",
748
+ `text-${config.color}-600`
749
+ )}
750
+ />
751
+ </div>
752
+ <span className="text-sm font-medium text-slate-700">
753
+ {config.label}
754
+ </span>
755
+ </div>
756
+
757
+ <div className="flex items-end justify-between">
758
+ <div>
759
+ <p
760
+ className={cn(
761
+ "text-2xl font-bold",
762
+ stageData.status === "skipped"
763
+ ? "text-slate-300"
764
+ : stageData.status === "failed"
765
+ ? "text-red-600"
766
+ : "text-slate-900"
767
+ )}
768
+ >
769
+ {stageData.status === "skipped"
770
+ ? "-"
771
+ : formatTime(stageData.time)}
772
+ </p>
773
+ {stageData.status !== "skipped" && (
774
+ <div className="flex items-center gap-1 mt-1">
775
+ <VariationIcon
776
+ className={cn(
777
+ "h-3 w-3",
778
+ variationInfo.color
779
+ )}
780
+ />
781
+ <span
782
+ className={cn(
783
+ "text-xs",
784
+ variationInfo.color
785
+ )}
786
+ >
787
+ {variationInfo.label}
788
+ </span>
789
+ </div>
790
+ )}
791
+ </div>
792
+
793
+ {stageData.status === "completed" && (
794
+ <CheckCircle2 className="h-5 w-5 text-emerald-500" />
795
+ )}
796
+ {stageData.status === "failed" && (
797
+ <X className="h-5 w-5 text-red-500" />
798
+ )}
799
+ </div>
800
+
801
+ {/* Progress bar */}
802
+ <div className="mt-3 h-1.5 bg-slate-200 rounded-full overflow-hidden">
803
+ <motion.div
804
+ initial={{ width: 0 }}
805
+ animate={{
806
+ width:
807
+ stageData.status === "completed"
808
+ ? "100%"
809
+ : stageData.status === "failed"
810
+ ? "60%"
811
+ : "0%",
812
+ }}
813
+ transition={{ duration: 0.5, delay: 0.2 }}
814
+ className={cn(
815
+ "h-full rounded-full",
816
+ stageData.status === "failed"
817
+ ? "bg-red-500"
818
+ : `bg-${config.color}-500`
819
+ )}
820
+ />
821
+ </div>
822
+ </div>
823
+ );
824
+ }
825
+ )}
826
+ </div>
827
+
828
+ {/* Total Time Summary */}
829
+ <div className="mt-4 flex items-center justify-between p-4 bg-gradient-to-r from-indigo-50 to-violet-50 rounded-xl border border-indigo-100">
830
+ <div className="flex items-center gap-3">
831
+ <Clock className="h-5 w-5 text-indigo-600" />
832
+ <div>
833
+ <p className="text-sm font-medium text-slate-700">
834
+ Total Processing Time
835
+ </p>
836
+ <p className="text-xs text-slate-500">
837
+ From upload to output ready
838
+ </p>
839
+ </div>
840
+ </div>
841
+ <div className="text-right">
842
+ <p className="text-2xl font-bold text-indigo-600">
843
+ {formatTime(item.totalTime)}
844
+ </p>
845
+ <p className="text-xs text-slate-500">
846
+ {item.status === "completed"
847
+ ? "Completed successfully"
848
+ : "Process failed"}
849
+ </p>
850
+ </div>
851
+ </div>
852
+ </div>
853
+ </motion.div>
854
+ )}
855
+ </AnimatePresence>
856
+ </motion.div>
857
+ ))}
858
+ </div>
859
+
860
+ {filteredHistory.length === 0 && (
861
+ <div className="text-center py-16">
862
+ <div className="h-20 w-20 mx-auto rounded-2xl bg-slate-100 flex items-center justify-center mb-4">
863
+ <FileText className="h-10 w-10 text-slate-300" />
864
+ </div>
865
+ <p className="text-slate-500">No extractions found</p>
866
+ </div>
867
+ )}
868
+ </div>
869
  </div>
870
  );
871
  }