From 0fd3dd9ebadd1174a5c88a39dd00ba7c5cd4af7e Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 21 Feb 2024 04:58:10 +0000 Subject: [PATCH 1/2] feat: add submissions table --- package.json | 4 + src/app/(main)/form/[id]/page.tsx | 40 ++- .../(main)/form/[id]/submissions-table.tsx | 230 ++++++++++++++++++ src/components/ui/checkbox.tsx | 30 +++ src/components/ui/popover.tsx | 31 +++ src/components/ui/table.tsx | 117 +++++++++ 6 files changed, 444 insertions(+), 8 deletions(-) create mode 100644 src/app/(main)/form/[id]/submissions-table.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/table.tsx diff --git a/package.json b/package.json index e13f001..ef9a8c4 100644 --- a/package.json +++ b/package.json @@ -21,16 +21,19 @@ "@lucia-auth/adapter-drizzle": "1.0.0", "@planetscale/database": "^1.11.0", "@radix-ui/react-alert-dialog": "^1.0.5", + "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-tabs": "^1.0.4", "@react-email/components": "^0.0.12", "@react-email/render": "^0.0.10", "@t3-oss/env-nextjs": "^0.7.1", "@tanstack/react-query": "^4.36.1", + "@tanstack/react-table": "^8.12.0", "@trpc/client": "^10.43.6", "@trpc/next": "^10.43.6", "@trpc/react-query": "^10.43.6", @@ -41,6 +44,7 @@ "drizzle-orm": "^0.29.3", "json-to-zod": "^1.1.2", "lucia": "3.0.0", + "lucide-react": "^0.335.0", "next": "^14.1.0", "next-themes": "^0.2.1", "nodemailer": "^6.9.7", diff --git a/src/app/(main)/form/[id]/page.tsx b/src/app/(main)/form/[id]/page.tsx index 9a881c8..887af60 100644 --- a/src/app/(main)/form/[id]/page.tsx +++ b/src/app/(main)/form/[id]/page.tsx @@ -1,18 +1,42 @@ import { api } from "~/trpc/server"; +import { Copy } from "lucide-react"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { SubmissionsTable } from "./submissions-table"; export default async function FormPage({ params }: { params: { id: string } }) { - const formSubmissions = await api.formData.all.query({ formId: params.id }); + const formId = params.id; + const [form, formSubmissions] = await Promise.all([ + api.form.get.query({ formId }), + api.formData.all.query({ formId }), + ]); return ( -
-
My Form Id: {params.id}
-
My Submittions
- +
- {formSubmissions.map((f) => { - return
{JSON.stringify(f.data as string)}
; - })} +

{form?.title}

+ +
+ + {formId} + + +
+ + + + Submissions + Setup + Analytics + Settings + + + + + Change your password here. + Look at your analytics here + Edit your form here +
); } diff --git a/src/app/(main)/form/[id]/submissions-table.tsx b/src/app/(main)/form/[id]/submissions-table.tsx new file mode 100644 index 0000000..fc2c147 --- /dev/null +++ b/src/app/(main)/form/[id]/submissions-table.tsx @@ -0,0 +1,230 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +"use client"; + +import * as React from "react"; +import { + CaretSortIcon, + ChevronDownIcon, + DotsHorizontalIcon, +} from "@radix-ui/react-icons"; +import { + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; +import type { + ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, +} from "@tanstack/react-table"; + +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; + +export function SubmissionsTable({ submissions }: any) { + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState( + [], + ); + const [columnVisibility, setColumnVisibility] = + React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}); + + const columns: ColumnDef[] = [ + { + id: "select", + header: ({ table }) => ( + + table.toggleAllPageRowsSelected(!!value) + } + aria-label="Select all" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + aria-label="Select row" + /> + ), + enableSorting: false, + enableHiding: false, + }, + + ...Object.keys(submissions[0].data).map((submission: any) => { + return { + accessorKey: submission, + header: () => { + return ( + + ); + }, + cell: ({ row }: any) => ( +
{row.getValue(submission)}
+ ), + }; + }), + + { + id: "actions", + enableHiding: false, + cell: ({ row }) => { + const payment = row.original; + + return ( + + + + + + Actions + navigator.clipboard.writeText(payment.id)} + > + Copy payment ID + + + View customer + View payment details + + + ); + }, + }, + ]; + + const table = useReactTable({ + data: submissions.map((submission: any) => submission.data), + columns: columns, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: setRowSelection, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + }, + }); + + return ( +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of{" "} + {table.getFilteredRowModel().rows.length} row(s) selected. +
+
+ + +
+
+
+ ); +} diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..ad4ddbc --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,30 @@ +"use client" + +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import { cn } from "~/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 0000000..1a477d6 --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,31 @@ +"use client"; + +import * as React from "react"; +import * as PopoverPrimitive from "@radix-ui/react-popover"; + +import { cn } from "~/lib/utils"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent }; diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 0000000..b3b3711 --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,117 @@ +import * as React from "react" + +import { cn } from "~/lib/utils" + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)) +Table.displayName = "Table" + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className + )} + {...props} + /> +)) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} From f80b56d6829d431bd3429ed48586a02e17e30de6 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 21 Feb 2024 05:19:16 +0000 Subject: [PATCH 2/2] chore: minor types --- .../(main)/form/[id]/submissions-table.tsx | 54 +++++++++---------- src/server/db/schema.ts | 6 +++ tailwind.config.ts | 2 +- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/app/(main)/form/[id]/submissions-table.tsx b/src/app/(main)/form/[id]/submissions-table.tsx index fc2c147..9d5b359 100644 --- a/src/app/(main)/form/[id]/submissions-table.tsx +++ b/src/app/(main)/form/[id]/submissions-table.tsx @@ -7,11 +7,7 @@ "use client"; import * as React from "react"; -import { - CaretSortIcon, - ChevronDownIcon, - DotsHorizontalIcon, -} from "@radix-ui/react-icons"; +import { DotsHorizontalIcon } from "@radix-ui/react-icons"; import { flexRender, getCoreRowModel, @@ -31,7 +27,6 @@ import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { DropdownMenu, - DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, @@ -46,8 +41,13 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import type { FormData } from "@/server/db/schema"; + +type SubmissionsTableProps = { + submissions: FormData[]; +}; -export function SubmissionsTable({ submissions }: any) { +export function SubmissionsTable({ submissions }: SubmissionsTableProps) { const [sorting, setSorting] = React.useState([]); const [columnFilters, setColumnFilters] = React.useState( [], @@ -56,21 +56,25 @@ export function SubmissionsTable({ submissions }: any) { React.useState({}); const [rowSelection, setRowSelection] = React.useState({}); - const columns: ColumnDef[] = [ + const columns: ColumnDef[] = [ { id: "select", - header: ({ table }) => ( - - table.toggleAllPageRowsSelected(!!value) - } - aria-label="Select all" - /> - ), + header: ({ table }) => { + console.log(table.getIsAllPageRowsSelected()); + + return ( + + table.toggleAllPageRowsSelected(!!value) + } + aria-label="Select all" + /> + ); + }, cell: ({ row }) => ( { + ...Object.keys(submissions[0]?.data as object).map((submission: any) => { return { accessorKey: submission, header: () => { @@ -103,8 +107,6 @@ export function SubmissionsTable({ submissions }: any) { id: "actions", enableHiding: false, cell: ({ row }) => { - const payment = row.original; - return ( @@ -115,11 +117,7 @@ export function SubmissionsTable({ submissions }: any) { Actions - navigator.clipboard.writeText(payment.id)} - > - Copy payment ID - + Copy submission ID View customer View payment details diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index 6773815..2b83c87 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -155,3 +155,9 @@ export const postRelations = relations(posts, ({ one }) => ({ export type Post = typeof posts.$inferSelect; export type NewPost = typeof posts.$inferInsert; + +export type FormData = typeof formDatas.$inferSelect; +export type NewFormData = typeof formDatas.$inferInsert; + +export type Form = typeof forms.$inferSelect; +export type NewForm = typeof forms.$inferInsert; diff --git a/tailwind.config.ts b/tailwind.config.ts index 35b3a19..65781ed 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -9,7 +9,7 @@ export default { center: true, padding: "2rem", screens: { - lg: "768px", + lg: "1000px", }, }, extend: {