gaurv007 commited on
Commit
5575628
·
verified ·
1 Parent(s): 3cd9067

feat: add ExportDropdown component (7 formats: PDF, HTML, MD, TXT, CSV, JSON)

Browse files
Files changed (1) hide show
  1. web/components/export-dropdown.tsx +69 -0
web/components/export-dropdown.tsx ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState, useRef, useEffect } from "react";
4
+ import { FileDown, ChevronDown, Loader2 } from "lucide-react";
5
+ import { EXPORT_FORMATS } from "@/lib/export-utils";
6
+ import type { AnalysisResult } from "@/lib/types";
7
+
8
+ export function ExportDropdown({ results }: { results: AnalysisResult }) {
9
+ const [open, setOpen] = useState(false);
10
+ const [exporting, setExporting] = useState<string | null>(null);
11
+ const ref = useRef<HTMLDivElement>(null);
12
+
13
+ useEffect(() => {
14
+ function handleClickOutside(e: MouseEvent) {
15
+ if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
16
+ }
17
+ document.addEventListener("mousedown", handleClickOutside);
18
+ return () => document.removeEventListener("mousedown", handleClickOutside);
19
+ }, []);
20
+
21
+ async function handleExport(key: string, fn: (r: AnalysisResult) => void | Promise<any>) {
22
+ setExporting(key);
23
+ try {
24
+ await fn(results);
25
+ } catch (e) {
26
+ console.error("Export failed:", e);
27
+ }
28
+ setExporting(null);
29
+ setOpen(false);
30
+ }
31
+
32
+ return (
33
+ <div ref={ref} className="relative">
34
+ <button
35
+ onClick={() => setOpen(!open)}
36
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-zinc-600 bg-white border border-zinc-200 rounded-lg hover:bg-zinc-50 hover:border-zinc-300 transition-all shadow-sm"
37
+ >
38
+ <FileDown className="w-3.5 h-3.5" />
39
+ Export
40
+ <ChevronDown className={`w-3 h-3 transition-transform ${open ? "rotate-180" : ""}`} />
41
+ </button>
42
+
43
+ {open && (
44
+ <div className="absolute right-0 top-full mt-1.5 w-64 bg-white border border-zinc-200 rounded-xl shadow-xl z-50 overflow-hidden animate-in fade-in slide-in-from-top-1 duration-150">
45
+ <div className="px-3 py-2 border-b border-zinc-100">
46
+ <p className="text-[10px] font-semibold text-zinc-400 uppercase tracking-wider">Export Report</p>
47
+ </div>
48
+ <div className="py-1">
49
+ {EXPORT_FORMATS.map((fmt) => (
50
+ <button
51
+ key={fmt.key}
52
+ onClick={() => handleExport(fmt.key, fmt.fn)}
53
+ disabled={exporting !== null}
54
+ className="w-full flex items-center gap-3 px-3 py-2.5 text-left hover:bg-zinc-50 transition-colors disabled:opacity-40"
55
+ >
56
+ <span className="text-base w-5 text-center">{fmt.icon}</span>
57
+ <div className="flex-1 min-w-0">
58
+ <p className="text-sm font-medium text-zinc-700">{fmt.label}</p>
59
+ <p className="text-[10px] text-zinc-400">{fmt.description}</p>
60
+ </div>
61
+ {exporting === fmt.key && <Loader2 className="w-3.5 h-3.5 text-zinc-400 animate-spin" />}
62
+ </button>
63
+ ))}
64
+ </div>
65
+ </div>
66
+ )}
67
+ </div>
68
+ );
69
+ }