document_agent / frontend /components /ExportPanel.tsx
Jai-rathore29's picture
Deploy: DocAgent backend (deterministic date-anomaly fix)
f65e025
Raw
History Blame Contribute Delete
4.37 kB
"use client";
import { useState } from "react";
import { Check, Copy, FileJson, FileSpreadsheet, FileText } from "lucide-react";
import { api } from "@/lib/api";
import type { DocumentDetail } from "@/lib/types";
const FORMATS = [
{
id: "json",
label: "JSON",
desc: "Full structured payload — fields, tables, classification, anomalies.",
icon: FileJson,
tint: "from-amber-500/20 to-amber-600/5 text-amber-300 ring-amber-500/20",
},
{
id: "csv",
label: "CSV",
desc: "Flat field / value / confidence table for spreadsheets.",
icon: FileText,
tint: "from-emerald-500/20 to-emerald-600/5 text-emerald-300 ring-emerald-500/20",
},
{
id: "xlsx",
label: "Excel",
desc: "Multi-sheet workbook with fields and extracted tables.",
icon: FileSpreadsheet,
tint: "from-green-500/20 to-green-600/5 text-green-300 ring-green-500/20",
},
];
export default function ExportPanel({ detail }: { detail: DocumentDetail }) {
const ready = detail.status === "ready";
const [copied, setCopied] = useState(false);
const ex = detail.extraction;
const previewObj: Record<string, unknown> = {
doc_type: detail.classification?.doc_type,
confidence: detail.classification?.confidence,
};
if (ex && ex.fields.length > 0) {
previewObj.fields = Object.fromEntries(ex.fields.map((f) => [f.name, f.value]));
}
if (ex && ex.records?.length > 0) {
previewObj.record_type = ex.record_type;
previewObj.records = ex.records.slice(0, 5).map((r) =>
Object.fromEntries(r.fields.map((f) => [f.name, f.value])),
);
if (ex.records.length > 5) previewObj["…"] = `${ex.records.length - 5} more records`;
}
const preview = JSON.stringify(previewObj, null, 2);
return (
<div className="h-full overflow-y-auto p-5">
<h3 className="mb-1 text-sm font-semibold text-white">Export extracted data</h3>
<p className="mb-5 text-[11px] text-white/40">
Download structured output for downstream systems — ERP, accounting, CRM.
</p>
<div className="grid gap-3 sm:grid-cols-3">
{FORMATS.map((f) => {
const Icon = f.icon;
return (
<a
key={f.id}
href={ready ? api.exportUrl(detail.id, f.id) : undefined}
onClick={(e) => !ready && e.preventDefault()}
className={`card group flex flex-col gap-3 rounded-xl p-4 transition ${
ready
? "hover:-translate-y-0.5 hover:ring-brand-500/30"
: "cursor-not-allowed opacity-50"
}`}
>
<div
className={`flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br ring-1 ${f.tint}`}
>
<Icon size={18} />
</div>
<div>
<p className="text-sm font-semibold text-white">{f.label}</p>
<p className="mt-0.5 text-[11px] leading-snug text-white/45">{f.desc}</p>
</div>
<span className="mt-auto text-xs font-medium text-brand-300 opacity-0 transition group-hover:opacity-100">
Download →
</span>
</a>
);
})}
</div>
{!ready && (
<p className="mt-4 text-center text-[11px] text-amber-300/80">
Export becomes available once processing completes.
</p>
)}
{detail.extraction && (
<div className="mt-6">
<div className="mb-2 flex items-center justify-between">
<p className="text-[11px] font-semibold uppercase tracking-wider text-white/40">
Preview
</p>
<button
onClick={() => {
navigator.clipboard.writeText(preview);
setCopied(true);
setTimeout(() => setCopied(false), 1200);
}}
className="flex items-center gap-1 text-[11px] text-white/45 transition hover:text-white"
>
{copied ? <Check size={11} className="text-emerald-400" /> : <Copy size={11} />}
Copy JSON
</button>
</div>
<pre className="card max-h-72 overflow-auto rounded-xl p-4 text-[11px] leading-relaxed text-white/70">
{preview}
</pre>
</div>
)}
</div>
);
}