"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 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 [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 onExportCsv = async () => { setCsvStatus("Generating export..."); const params = buildParams(); const query = params.toString() ? `?${params.toString()}` : ""; const res = await apiFetch(`/api/exports/csv${query}`); if (res.error) { setCsvStatus(res.error.message ?? "Export failed."); return; } if (res.data?.csv) { const blob = new Blob([res.data.csv], { type: "text/csv" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `ledgerone-export-${new Date().toISOString().slice(0, 10)}.csv`; a.click(); URL.revokeObjectURL(url); setCsvStatus(`Export ready (${res.data.rowCount ?? 0} rows) — file downloaded.`); } else { setCsvStatus("Export ready."); } }; 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}

}
{/* 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 → )}
); }