import { writeFileSync } from "fs"; writeFileSync("app/exports/page.tsx", `"use client"; import { useState } from "react"; import { AppShell } from "../../components/app-shell"; import { apiFetch } from "../../lib/api"; type ApiResponse = { data: T; meta: { timestamp: string; version: "v1" }; error: null | { message: string; code?: string }; }; type ExportData = { status: string; csv?: string; rowCount?: number }; type SheetsData = { spreadsheetUrl?: string; spreadsheetId?: string; rowCount?: number }; 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 [filters, setFilters] = useState({ startDate: "", endDate: "", minAmount: "", maxAmount: "", category: "", source: "", includeHidden: 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 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; } if (res.data.spreadsheetUrl) { setSheetsUrl(res.data.spreadsheetUrl); 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 (
{/* Date preset selector */}
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 actions */}
{/* CSV export */}

Download CSV

Raw and derived transaction fields in comma-separated format.

{csvStatus &&

{csvStatus}

}
{/* Google Sheets export */}

Export to Google Sheets

Creates a new Google Sheet with a dated tab. Requires Google OAuth to be configured.

{sheetsStatus &&

{sheetsStatus}

} {sheetsUrl && ( Open Sheet → )}
); } `); console.log("✅ exports/page.tsx written with CSV download + Google Sheets export");