diff --git a/src/app/dashboard/admin/@users/export.tsx b/src/app/dashboard/admin/@tools/export.tsx similarity index 54% rename from src/app/dashboard/admin/@users/export.tsx rename to src/app/dashboard/admin/@tools/export.tsx index 5ecb0ba4c..2fc42097f 100644 --- a/src/app/dashboard/admin/@users/export.tsx +++ b/src/app/dashboard/admin/@tools/export.tsx @@ -5,9 +5,14 @@ import { useEffect, useState } from "react" import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert" import { Button } from "~/components/ui/button" -import { TableProps } from "./table" +import { type Payment, type Project, type User } from "~/server/db/types" -export default function ExportButton({ data }: TableProps) { +export interface ExportButtonProps { + data: Array + label: string +} + +export default function ExportButton({ data, label }: ExportButtonProps) { const [open, setOpen] = useState(false) useEffect(() => { if (!open) return @@ -17,19 +22,44 @@ export default function ExportButton({ data }: TableProps) { const downloadCSV = () => { if (!data || data.length === 0) { setOpen(true) - console.log("open") return } - const headers = Object.keys(data[0] as Record).join(",") - const rows = data.map((row) => Object.values(row).join(",")).join("\n") - const csv = headers + "\n" + rows + const headers = Object.keys(data[0] as Record) + const escapeCSV = (value: unknown): string => { + if (value == null) return "" + + let str: string + if (typeof value === "object") { + if (value instanceof Date) { + str = value.toISOString() + } else { + // Stringify JSON objects/arrays + str = JSON.stringify(value) + } + } else { + str = String(value) + } + + // Escape CSV: wrap in quotes if contains comma, quote, or newline + // and escape internal quotes by doubling them + if (/[",\n\r]/.test(str)) { + return `"${str.replace(/"/g, '""')}"` + } + return str + } + + const rows = data + .map((row) => headers.map((key) => escapeCSV((row as Record)[key])).join(",")) + .join("\n") + + const csv = headers.join(",") + "\n" + rows const blob = new Blob([csv], { type: "text/csv" }) const url = window.URL.createObjectURL(blob) const a = document.createElement("a") a.setAttribute("href", url) - a.setAttribute("download", "output.csv") + a.setAttribute("download", `${label}.csv`) a.click() window.URL.revokeObjectURL(url) setOpen(true) @@ -38,7 +68,7 @@ export default function ExportButton({ data }: TableProps) { return (
{open && (!data || data.length === 0) && ( @@ -49,7 +79,7 @@ export default function ExportButton({ data }: TableProps) { {open && data && data.length > 0 && ( Export Successful - Your data has been exported as `output.csv`. + Your data has been exported as {`${label}.csv`}. )}
diff --git a/src/app/dashboard/admin/@tools/page.tsx b/src/app/dashboard/admin/@tools/page.tsx index b334cc783..38fc1e1ac 100644 --- a/src/app/dashboard/admin/@tools/page.tsx +++ b/src/app/dashboard/admin/@tools/page.tsx @@ -1,6 +1,12 @@ +import { api } from "~/trpc/server" + +import ExportButton from "./export" import UpdateEmail from "./update-email" export default async function AdminUserTable() { + const projects = await api.admin.projects.getAllProjects.query() + const payments = await api.admin.analytics.getAllPayments.query() + return ( <>
@@ -9,6 +15,10 @@ export default async function AdminUserTable() {
+
+ + +
) } diff --git a/src/app/dashboard/admin/@users/table.tsx b/src/app/dashboard/admin/@users/table.tsx index b6840dbd0..5adb01847 100644 --- a/src/app/dashboard/admin/@users/table.tsx +++ b/src/app/dashboard/admin/@users/table.tsx @@ -63,13 +63,13 @@ import { cn } from "~/lib/utils" import { type User } from "~/server/db/types" import { api } from "~/trpc/react" -import ExportButton from "./export" +import ExportButton from "../@tools/export" import AddUserForm from "./form" -type UserProps = Omit +type DisplayColumn = Omit export interface TableProps { - data: Array + data: Array } interface UserTableProps extends TableProps { refetch: () => void @@ -92,7 +92,7 @@ const sortIcon = (sortOrder: string | boolean) => { } } -const columns = (updateRole: ({ id, role }: UpdateUserRoleFunctionProps) => void): ColumnDef[] => [ +const columns = (updateRole: ({ id, role }: UpdateUserRoleFunctionProps) => void): ColumnDef[] => [ { id: "Select", enableSorting: false, @@ -341,7 +341,7 @@ const UserTable = ({ data, isRefetching, ...props }: UserTableProps) => { return ( <> -
+
{data.length > 0 && ( <> {selectedRowIDs.length > 0 && ( @@ -418,7 +418,7 @@ const UserTable = ({ data, isRefetching, ...props }: UserTableProps) => { })} - +