-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from eight-labs/feat/submissions-table
feat: add submissions table
- Loading branch information
Showing
8 changed files
with
449 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<div> | ||
<div>My Form Id: {params.id}</div> | ||
<div>My Submittions</div> | ||
|
||
<div className="space-y-6"> | ||
<div> | ||
{formSubmissions.map((f) => { | ||
return <div key={f.id}>{JSON.stringify(f.data as string)}</div>; | ||
})} | ||
<h1 className="text-3xl font-medium">{form?.title}</h1> | ||
|
||
<div className="mt-2 flex items-center gap-2"> | ||
<span className="inline-flex items-center rounded-lg bg-muted px-2 py-0.5 text-sm font-medium"> | ||
{formId} | ||
</span> | ||
<Copy className="h-4 w-4 text-muted-foreground" /> | ||
</div> | ||
</div> | ||
|
||
<Tabs defaultValue="submissions"> | ||
<TabsList> | ||
<TabsTrigger value="submissions">Submissions</TabsTrigger> | ||
<TabsTrigger value="setup">Setup</TabsTrigger> | ||
<TabsTrigger value="analytics">Analytics</TabsTrigger> | ||
<TabsTrigger value="settings">Settings</TabsTrigger> | ||
</TabsList> | ||
<TabsContent value="submissions" className="my-6"> | ||
<SubmissionsTable submissions={formSubmissions} /> | ||
</TabsContent> | ||
<TabsContent value="setup">Change your password here.</TabsContent> | ||
<TabsContent value="analytics">Look at your analytics here</TabsContent> | ||
<TabsContent value="settings">Edit your form here</TabsContent> | ||
</Tabs> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
/* 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 { 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, | ||
DropdownMenuContent, | ||
DropdownMenuItem, | ||
DropdownMenuLabel, | ||
DropdownMenuSeparator, | ||
DropdownMenuTrigger, | ||
} from "@/components/ui/dropdown-menu"; | ||
import { | ||
Table, | ||
TableBody, | ||
TableCell, | ||
TableHead, | ||
TableHeader, | ||
TableRow, | ||
} from "@/components/ui/table"; | ||
import type { FormData } from "@/server/db/schema"; | ||
|
||
type SubmissionsTableProps = { | ||
submissions: FormData[]; | ||
}; | ||
|
||
export function SubmissionsTable({ submissions }: SubmissionsTableProps) { | ||
const [sorting, setSorting] = React.useState<SortingState>([]); | ||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>( | ||
[], | ||
); | ||
const [columnVisibility, setColumnVisibility] = | ||
React.useState<VisibilityState>({}); | ||
const [rowSelection, setRowSelection] = React.useState({}); | ||
|
||
const columns: ColumnDef<FormData["data"]>[] = [ | ||
{ | ||
id: "select", | ||
header: ({ table }) => { | ||
console.log(table.getIsAllPageRowsSelected()); | ||
|
||
return ( | ||
<Checkbox | ||
checked={ | ||
table.getIsAllPageRowsSelected() || | ||
(table.getIsSomePageRowsSelected() && "indeterminate") | ||
} | ||
onCheckedChange={(value) => | ||
table.toggleAllPageRowsSelected(!!value) | ||
} | ||
aria-label="Select all" | ||
/> | ||
); | ||
}, | ||
cell: ({ row }) => ( | ||
<Checkbox | ||
checked={row.getIsSelected()} | ||
onCheckedChange={(value: any) => row.toggleSelected(!!value)} | ||
aria-label="Select row" | ||
/> | ||
), | ||
enableSorting: false, | ||
enableHiding: false, | ||
}, | ||
|
||
...Object.keys(submissions[0]?.data as object).map((submission: any) => { | ||
return { | ||
accessorKey: submission, | ||
header: () => { | ||
return ( | ||
<Button variant="ghost" className="px-0 py-0 capitalize"> | ||
{submission} | ||
{/* <CaretSortIcon className="ml-2 h-4 w-4" /> */} | ||
</Button> | ||
); | ||
}, | ||
cell: ({ row }: any) => ( | ||
<div className="lowercase">{row.getValue(submission)}</div> | ||
), | ||
}; | ||
}), | ||
|
||
{ | ||
id: "actions", | ||
enableHiding: false, | ||
cell: ({ row }) => { | ||
return ( | ||
<DropdownMenu> | ||
<DropdownMenuTrigger asChild> | ||
<Button variant="ghost" className="h-8 w-8 p-0"> | ||
<span className="sr-only">Open menu</span> | ||
<DotsHorizontalIcon className="h-4 w-4" /> | ||
</Button> | ||
</DropdownMenuTrigger> | ||
<DropdownMenuContent align="end"> | ||
<DropdownMenuLabel>Actions</DropdownMenuLabel> | ||
<DropdownMenuItem>Copy submission ID</DropdownMenuItem> | ||
<DropdownMenuSeparator /> | ||
<DropdownMenuItem>View customer</DropdownMenuItem> | ||
<DropdownMenuItem>View payment details</DropdownMenuItem> | ||
</DropdownMenuContent> | ||
</DropdownMenu> | ||
); | ||
}, | ||
}, | ||
]; | ||
|
||
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 ( | ||
<div className="mt-6 w-full"> | ||
<div className="rounded-md border"> | ||
<Table> | ||
<TableHeader> | ||
{table.getHeaderGroups().map((headerGroup) => ( | ||
<TableRow key={headerGroup.id}> | ||
{headerGroup.headers.map((header) => { | ||
return ( | ||
<TableHead key={header.id}> | ||
{header.isPlaceholder | ||
? null | ||
: flexRender( | ||
header.column.columnDef.header, | ||
header.getContext(), | ||
)} | ||
</TableHead> | ||
); | ||
})} | ||
</TableRow> | ||
))} | ||
</TableHeader> | ||
<TableBody> | ||
{table.getRowModel().rows?.length ? ( | ||
table.getRowModel().rows.map((row) => ( | ||
<TableRow | ||
key={row.id} | ||
data-state={row.getIsSelected() && "selected"} | ||
> | ||
{row.getVisibleCells().map((cell) => ( | ||
<TableCell key={cell.id}> | ||
{flexRender( | ||
cell.column.columnDef.cell, | ||
cell.getContext(), | ||
)} | ||
</TableCell> | ||
))} | ||
</TableRow> | ||
)) | ||
) : ( | ||
<TableRow> | ||
<TableCell | ||
colSpan={columns.length} | ||
className="h-24 text-center" | ||
> | ||
No results. | ||
</TableCell> | ||
</TableRow> | ||
)} | ||
</TableBody> | ||
</Table> | ||
</div> | ||
<div className="flex items-center justify-end space-x-2 py-4"> | ||
<div className="flex-1 text-sm text-muted-foreground"> | ||
{table.getFilteredSelectedRowModel().rows.length} of{" "} | ||
{table.getFilteredRowModel().rows.length} row(s) selected. | ||
</div> | ||
<div className="space-x-2"> | ||
<Button | ||
variant="outline" | ||
size="sm" | ||
onClick={() => table.previousPage()} | ||
disabled={!table.getCanPreviousPage()} | ||
> | ||
Previous | ||
</Button> | ||
<Button | ||
variant="outline" | ||
size="sm" | ||
onClick={() => table.nextPage()} | ||
disabled={!table.getCanNextPage()} | ||
> | ||
Next | ||
</Button> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<typeof CheckboxPrimitive.Root>, | ||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> | ||
>(({ className, ...props }, ref) => ( | ||
<CheckboxPrimitive.Root | ||
ref={ref} | ||
className={cn( | ||
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", | ||
className | ||
)} | ||
{...props} | ||
> | ||
<CheckboxPrimitive.Indicator | ||
className={cn("flex items-center justify-center text-current")} | ||
> | ||
<Check className="h-4 w-4" /> | ||
</CheckboxPrimitive.Indicator> | ||
</CheckboxPrimitive.Root> | ||
)) | ||
Checkbox.displayName = CheckboxPrimitive.Root.displayName | ||
|
||
export { Checkbox } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<typeof PopoverPrimitive.Content>, | ||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> | ||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( | ||
<PopoverPrimitive.Portal> | ||
<PopoverPrimitive.Content | ||
ref={ref} | ||
align={align} | ||
sideOffset={sideOffset} | ||
className={cn( | ||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", | ||
className, | ||
)} | ||
{...props} | ||
/> | ||
</PopoverPrimitive.Portal> | ||
)); | ||
PopoverContent.displayName = PopoverPrimitive.Content.displayName; | ||
|
||
export { Popover, PopoverTrigger, PopoverContent }; |
Oops, something went wrong.