Export CSV based on currently visible columns instead of hardcoded list

- CSV export now respects column visibility settings, exporting only the
  columns the user has toggled on
- Boolean fields export as "Yes"/"No", arrays as comma-separated values
- Toast message shows row and column counts for confirmation

Made-with: Cursor
This commit is contained in:
Randall Stillwell 2026-04-09 10:35:58 -05:00
parent ec51c049c3
commit 05a40cca51

View file

@ -6,7 +6,7 @@ import { toast } from "sonner";
import { StatCards, type StatFilter } from "./stat-cards"; import { StatCards, type StatFilter } from "./stat-cards";
import { Filters } from "./filters"; import { Filters } from "./filters";
import { DataTable, getDefaultColumnVisibility } from "./data-table"; import { DataTable, getDefaultColumnVisibility, ALL_TOGGLEABLE_COLUMNS } from "./data-table";
import { SelectionToolbar } from "./selection-toolbar"; import { SelectionToolbar } from "./selection-toolbar";
import { UploadModal, type UploadingFile } from "./upload-modal"; import { UploadModal, type UploadingFile } from "./upload-modal";
import { createColumns, COPYABLE_FIELDS, type ResponseCard } from "./columns"; import { createColumns, COPYABLE_FIELDS, type ResponseCard } from "./columns";
@ -173,17 +173,25 @@ export function DashboardContent() {
return; return;
} }
const headers = [ const visibleCols = ALL_TOGGLEABLE_COLUMNS.filter(
"Name", "Email", "Phone", "City", "State", "Gender", (col) => columnVisibility[col.id] !== false
"Visit Type", "Attendance", "Service", "OCR Status", "Review Status", );
"Confidence",
]; if (visibleCols.length === 0) {
const rows = data.map((c) => [ toast.error("No columns visible to export");
c.name || "", c.email || "", c.cellPhone || "", c.city || "", return;
c.state || "", c.gender || "", c.visitType || "", }
c.attendanceDuration || "", c.serviceAttended || "", c.ocrStatus,
c.reviewStatus, c.ocrConfidence?.toString() || "", const headers = visibleCols.map((c) => c.label);
]); const rows = data.map((card) =>
visibleCols.map((col) => {
const val = (card as Record<string, unknown>)[col.id];
if (val == null) return "";
if (typeof val === "boolean") return val ? "Yes" : "No";
if (Array.isArray(val)) return val.filter(Boolean).join(", ");
return String(val);
})
);
const csv = [headers, ...rows] const csv = [headers, ...rows]
.map((r) => r.map((v) => `"${v.replace(/"/g, '""')}"`).join(",")) .map((r) => r.map((v) => `"${v.replace(/"/g, '""')}"`).join(","))
@ -196,7 +204,7 @@ export function DashboardContent() {
a.download = `response-cards-${new Date().toISOString().split("T")[0]}.csv`; a.download = `response-cards-${new Date().toISOString().split("T")[0]}.csv`;
a.click(); a.click();
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
toast.success("CSV exported"); toast.success(`Exported ${data.length} rows × ${visibleCols.length} columns`);
}; };
const columns = React.useMemo( const columns = React.useMemo(