"use client"; import { useEffect, useState } from "react"; import { AppShell } from "../../components/app-shell"; import { apiFetch } from "@/lib/api"; type ExportData = { status: string; csv?: string; rowCount?: number }; type SignedExportData = { status: string; downloadUrl?: string; expiresAt?: string; expiresInSeconds?: number; singleUse?: boolean }; type SheetsData = { spreadsheetUrl?: string; url?: string; spreadsheetId?: string; rowCount?: number }; type GoogleStatus = { connected: boolean; googleEmail?: string; connectedAt?: string }; export default function ExportsPage() { const [csvStatus, setCsvStatus] = useState(""); const [jsonStatus, setJsonStatus] = useState(""); const [xlsxStatus, setXlsxStatus] = useState(""); const [pdfStatus, setPdfStatus] = useState(""); const [sheetsStatus, setSheetsStatus] = useState(""); const [sheetsUrl, setSheetsUrl] = useState(null); const [sheetsLoading, setSheetsLoading] = useState(false); const [datePreset, setDatePreset] = useState("custom"); const [googleStatus, setGoogleStatus] = useState(null); const [disconnecting, setDisconnecting] = useState(false); const [filters, setFilters] = useState({ startDate: "", endDate: "", minAmount: "", maxAmount: "", category: "", source: "", includeHidden: false, }); useEffect(() => { apiFetch("/api/google/status").then((res) => { if (!res.error) setGoogleStatus(res.data ?? { connected: false }); }); }, []); const applyPreset = (preset: string) => { setDatePreset(preset); if (preset === "custom") return; const now = new Date(); const end = new Date(now.getFullYear(), now.getMonth(), now.getDate()); let start = new Date(end); if (preset === "this_month") { start = new Date(end.getFullYear(), end.getMonth(), 1); } else if (preset === "last_month") { start = new Date(end.getFullYear(), end.getMonth() - 1, 1); end.setDate(0); } else if (preset === "last_6_months") { start = new Date(end.getFullYear(), end.getMonth() - 5, 1); } else if (preset === "last_year") { start = new Date(end.getFullYear() - 1, 0, 1); end.setMonth(11, 31); } const fmt = (d: Date) => d.toISOString().slice(0, 10); setFilters((prev) => ({ ...prev, startDate: fmt(start), endDate: fmt(end) })); }; const buildParams = () => { const params = new URLSearchParams(); if (filters.startDate) params.set("start_date", filters.startDate); if (filters.endDate) params.set("end_date", filters.endDate); if (filters.minAmount) params.set("min_amount", filters.minAmount); if (filters.maxAmount) params.set("max_amount", filters.maxAmount); if (filters.category) params.set("category", filters.category); if (filters.source) params.set("source", filters.source); if (filters.includeHidden) params.set("include_hidden", "true"); return params; }; const downloadSignedFile = (url: string) => { const a = document.createElement("a"); a.href = url; a.rel = "noopener"; a.click(); }; const requestSignedDownload = async (format: "csv" | "json" | "xlsx" | "pdf") => { const params = buildParams(); const query = params.toString() ? `?${params.toString()}` : ""; return apiFetch(`/api/exports/${format}${query}`); }; const onExportCsv = async () => { setCsvStatus("Generating export..."); const res = await requestSignedDownload("csv"); if (res.error) { setCsvStatus(res.error.message ?? "Export failed."); return; } if (res.data?.downloadUrl) { downloadSignedFile(res.data.downloadUrl); setCsvStatus(`Signed one-time link created. Download expires in ${res.data.expiresInSeconds ?? 120} seconds.`); } else { setCsvStatus("Export link created."); } }; const onExportJson = async () => { setJsonStatus("Generating JSON export..."); const res = await requestSignedDownload("json"); if (res.error) { setJsonStatus(res.error.message ?? "JSON export failed."); return; } if (res.data?.downloadUrl) { downloadSignedFile(res.data.downloadUrl); setJsonStatus(`Signed one-time link created. Download expires in ${res.data.expiresInSeconds ?? 120} seconds.`); } else { setJsonStatus("JSON export link created."); } }; const onExportXlsx = async () => { setXlsxStatus("Generating XLSX export..."); const res = await requestSignedDownload("xlsx"); if (res.error) { setXlsxStatus(res.error.message ?? "XLSX export failed."); return; } if (res.data?.downloadUrl) { downloadSignedFile(res.data.downloadUrl); setXlsxStatus(`Signed one-time link created. Download expires in ${res.data.expiresInSeconds ?? 120} seconds.`); } else { setXlsxStatus("XLSX export link created."); } }; const onExportPdf = async () => { setPdfStatus("Generating PDF export..."); const res = await requestSignedDownload("pdf"); if (res.error) { setPdfStatus(res.error.message ?? "PDF export failed."); return; } if (res.data?.downloadUrl) { downloadSignedFile(res.data.downloadUrl); setPdfStatus(`Signed one-time link created. Download expires in ${res.data.expiresInSeconds ?? 120} seconds.`); } else { setPdfStatus("PDF export link created."); } }; const onConnectGoogle = async () => { const res = await apiFetch<{ authUrl: string }>("/api/google/connect"); if (res.error) { setSheetsStatus(res.error.message ?? "Failed to get Google auth URL."); return; } if (res.data?.authUrl) { window.location.href = res.data.authUrl; } }; const onDisconnectGoogle = async () => { setDisconnecting(true); const res = await apiFetch("/api/google/disconnect", { method: "DELETE" }); setDisconnecting(false); if (!res.error) { setGoogleStatus({ connected: false }); setSheetsStatus("Google account disconnected."); setSheetsUrl(null); } }; const onExportSheets = async () => { setSheetsLoading(true); setSheetsStatus("Creating Google Sheet..."); setSheetsUrl(null); const body: Record = {}; if (filters.startDate) body.startDate = filters.startDate; if (filters.endDate) body.endDate = filters.endDate; if (filters.minAmount) body.minAmount = filters.minAmount; if (filters.maxAmount) body.maxAmount = filters.maxAmount; if (filters.category) body.category = filters.category; if (filters.includeHidden) body.includeHidden = true; const res = await apiFetch("/api/exports/sheets", { method: "POST", body: JSON.stringify(body), }); setSheetsLoading(false); if (res.error) { setSheetsStatus(res.error.message ?? "Google Sheets export failed."); return; } const url = res.data?.url ?? res.data?.spreadsheetUrl ?? null; if (url) { setSheetsUrl(url); setSheetsStatus(`Sheet created with ${res.data?.rowCount ?? 0} rows.`); } else { setSheetsStatus("Sheet created."); } }; const inputCls = "mt-2 w-full rounded-xl border border-border bg-background/50 px-4 py-2 text-sm text-foreground focus:border-primary focus:ring-primary focus:outline-none"; const labelCls = "text-xs text-muted-foreground font-semibold uppercase tracking-wider"; return (
{/* Filters */}
setFilters((p) => ({ ...p, startDate: e.target.value }))} className={inputCls} disabled={datePreset !== "custom"} />
setFilters((p) => ({ ...p, endDate: e.target.value }))} className={inputCls} disabled={datePreset !== "custom"} />
setFilters((p) => ({ ...p, category: e.target.value }))} className={inputCls} />
setFilters((p) => ({ ...p, minAmount: e.target.value }))} className={inputCls} />
setFilters((p) => ({ ...p, maxAmount: e.target.value }))} className={inputCls} />
{/* Export cards */}
{/* CSV */}

Download CSV

Raw and derived transaction fields in comma-separated format.

{csvStatus &&

{csvStatus}

}
{/* JSON */}

Download JSON

Structured transaction export for custom tools.

{jsonStatus &&

{jsonStatus}

}
{/* XLSX */}

Download XLSX

Excel workbook with filtered transaction data.

{xlsxStatus &&

{xlsxStatus}

}
{/* PDF */}

Download PDF

Printable ledger snapshot for audit packets.

{pdfStatus &&

{pdfStatus}

}
{/* Google Sheets */}

Export to Google Sheets

{googleStatus?.connected ? (

{googleStatus.googleEmail}

) : (

Connect your Google account to export directly to Sheets.

)}
{googleStatus?.connected ? ( <> ) : ( )} {sheetsStatus &&

{sheetsStatus}

} {sheetsUrl && ( Open Sheet → )}
); }