Skip to content

Commit fd74852

Browse files
committed
Export button in tables
1 parent 343bfce commit fd74852

File tree

3 files changed

+62
-3
lines changed

3 files changed

+62
-3
lines changed

apps/dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"canvas-confetti": "^1.9.2",
4141
"clsx": "^2.0.0",
4242
"dotenv-cli": "^7.3.0",
43+
"export-to-csv": "^1.3.0",
4344
"geist": "^1",
4445
"jose": "^5.2.2",
4546
"lucide-react": "^0.378.0",

apps/dashboard/src/components/data-table/elements/toolbar.tsx

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import { Cross2Icon } from "@radix-ui/react-icons";
44
import { Button } from "@stackframe/stack-ui";
5-
import { Table } from "@tanstack/react-table";
5+
import { Cell, Table } from "@tanstack/react-table";
66
import { DataTableViewOptions } from "./view-options";
7+
import { DownloadIcon } from "lucide-react";
8+
import { mkConfig, generateCsv, download } from 'export-to-csv';
79

810
interface DataTableToolbarProps<TData> {
911
table: Table<TData>,
@@ -19,7 +21,7 @@ export function DataTableToolbar<TData>({
1921

2022
return (
2123
<div className="flex items-center justify-between">
22-
<div className="flex flex-1 items-center gap-2 flex-wrap">
24+
<div className="flex items-center gap-2 flex-wrap">
2325
{toolbarRender?.(table)}
2426
{(isFiltered || isSorted) && (
2527
<Button
@@ -35,7 +37,54 @@ export function DataTableToolbar<TData>({
3537
</Button>
3638
)}
3739
</div>
38-
<DataTableViewOptions table={table} />
40+
<div className="flex-1" />
41+
<div className="flex items-center gap-2 flex-wrap">
42+
<DataTableViewOptions table={table} />
43+
<Button
44+
variant="outline"
45+
size="sm"
46+
className="ml-auto hidden h-8 lg:flex"
47+
onClick={() => {
48+
const csvConfig = mkConfig({
49+
fieldSeparator: ',',
50+
filename: 'data',
51+
decimalSeparator: '.',
52+
useKeysAsHeaders: true,
53+
});
54+
55+
const renderCellValue = (cell: Cell<TData, unknown>) => {
56+
const rendered = cell.renderValue();
57+
if (rendered === null) {
58+
return undefined;
59+
}
60+
if (['string', 'number', 'boolean', 'undefined'].includes(typeof rendered)) {
61+
return rendered;
62+
}
63+
if (rendered instanceof Date) {
64+
return rendered.toISOString();
65+
}
66+
if (typeof rendered === 'object') {
67+
return JSON.stringify(rendered);
68+
}
69+
};
70+
71+
72+
const rowModel = table.getRowModel();
73+
const rows = rowModel.rows.map(row => Object.fromEntries(row.getAllCells().map(c => [c.column.id, renderCellValue(c)]).filter(([_, v]) => v !== undefined)));
74+
console.log(table.getAllColumns());
75+
if (rows.length === 0) {
76+
alert("No data to export");
77+
return;
78+
}
79+
console.log(rows);
80+
const csv = generateCsv(csvConfig)(rows as any);
81+
download(csvConfig)(csv);
82+
}}
83+
>
84+
<DownloadIcon className="mr-2 h-4 w-4" />
85+
Export CSV
86+
</Button>
87+
</div>
3988
</div>
4089
);
4190
}

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)