From 6eaf88c9e77b64023d6b6d57d9122b3a5cf0b123 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Mon, 2 Dec 2024 14:45:58 -0600 Subject: [PATCH 1/8] first init --- apps/web/src/app/admin/users/page.tsx | 48 +---- .../admin/users/DefaultPagination.tsx | 73 -------- .../src/components/admin/users/SearchBar.tsx | 45 ----- .../components/admin/users/UserColumns.tsx | 132 +++++++++++-- .../components/admin/users/UserDataTable.tsx | 174 ++++++++++++------ packages/db/functions/user.ts | 10 +- 6 files changed, 255 insertions(+), 227 deletions(-) delete mode 100644 apps/web/src/components/admin/users/DefaultPagination.tsx delete mode 100644 apps/web/src/components/admin/users/SearchBar.tsx diff --git a/apps/web/src/app/admin/users/page.tsx b/apps/web/src/app/admin/users/page.tsx index 5ea82481..97f7689e 100644 --- a/apps/web/src/app/admin/users/page.tsx +++ b/apps/web/src/app/admin/users/page.tsx @@ -3,42 +3,18 @@ import { DataTable } from "@/components/admin/users/UserDataTable"; import { columns } from "@/components/admin/users/UserColumns"; import { Button } from "@/components/shadcn/ui/button"; import { FolderInput } from "lucide-react"; -import { DefaultPagination } from "@/components/admin/users/DefaultPagination"; -import SearchBar from "@/components/admin/users/SearchBar"; +import { getAllUsers } from "db/functions"; import { userCommonData } from "db/schema"; -export default async function Page({ - searchParams, -}: { - searchParams: { [key: string]: string | undefined }; -}) { - // COME BACK AND CHANGE - const maxPerPage = 30; +// This begs a question where we might want to have an option later on to sort by the role as we might want different things +export default async function Page() { - let page = +(searchParams["page"] ?? "1"); - let user = searchParams["user"] ?? ""; - const checkedBoxes = searchParams["checkedBoxes"] ?? ""; - - console.log(checkedBoxes); - - const start = maxPerPage * (page - 1); - const end = maxPerPage + start; - - // Might want to work with cache in prod to see if this will be plausible to do - const userData = await db.query.userCommonData.findMany({ - with: { hackerData: true }, - where: and( - or( - ilike(userCommonData.firstName, `%${user}%`), - ilike(userCommonData.lastName, `%${user}%`), - ), - ), - }); + const userData = await getAllUsers(); return ( -
-
-
+
+
+

Users @@ -48,7 +24,6 @@ export default async function Page({

-
- {/* TODO: Would very much like to not have "as any" here in the future */} -
+
{userData && userData.length > 0 ? ( <> ) : ( @@ -72,11 +46,7 @@ export default async function Page({

No Results :(

)} - {/* */}
-
); } diff --git a/apps/web/src/components/admin/users/DefaultPagination.tsx b/apps/web/src/components/admin/users/DefaultPagination.tsx deleted file mode 100644 index a0d5771c..00000000 --- a/apps/web/src/components/admin/users/DefaultPagination.tsx +++ /dev/null @@ -1,73 +0,0 @@ -"use client"; - -import { - Pagination, - PaginationContent, - PaginationEllipsis, - PaginationItem, - PaginationLink, - PaginationNext, - PaginationPrevious, -} from "@/components/shadcn/ui/pagination"; -import { useEffect, useRef, useState } from "react"; -import { usePathname, useSearchParams } from "next/navigation"; -import { createPath } from "@/lib/utils/shared/pageParams"; - -export function DefaultPagination({ maxPages }: { maxPages: number }) { - // FIXME: Come back and change after done testing - - const path = usePathname(); - const params = useSearchParams(); - - const page = params.get("page") ?? "1"; - - const [currPage, setCurrPage] = useState(+page); - const pageRef = useRef(1); - - function incPage() { - pageRef.current = Math.min(maxPages, pageRef.current + 1); - setCurrPage(Math.min(maxPages, currPage + 1)); - } - - function decPage() { - pageRef.current = Math.max(1, pageRef.current - 1); - setCurrPage(Math.max(1, currPage - 1)); - } - - function createPaginationPath(reqPage: string) { - const url = `${path}?${reqPage}&user=${ - params.get("user") ?? "" - }&checkedBoxes=${params.get("checkedBoxes") ?? ""}`; - console.log("Pagination", url); - return url; - } - - return ( - - - - { - decPage(); - }} - /> - - {currPage} - - { - incPage(); - }} - /> - - - - ); -} diff --git a/apps/web/src/components/admin/users/SearchBar.tsx b/apps/web/src/components/admin/users/SearchBar.tsx deleted file mode 100644 index 2af198de..00000000 --- a/apps/web/src/components/admin/users/SearchBar.tsx +++ /dev/null @@ -1,45 +0,0 @@ -"use client"; - -import { Input } from "@/components/shadcn/ui/input"; -import { useRouter, useSearchParams, usePathname } from "next/navigation"; -import { useRef, useState } from "react"; -import { useDebouncedCallback } from "use-debounce"; - -import { X } from "lucide-react"; -export default function SearchBar() { - const searchParams = useSearchParams(); - const { replace } = useRouter(); - const pathname = usePathname(); - - // We use a debouncing strategy to prevent the search from querying every single keystroke and instead will run a time after the user completes typing - const handleSearch = useDebouncedCallback((term) => { - // @ts-ignore Works perfectly fine and is apprporiate accoriding to the docs. Might be a version issue? - const params = new URLSearchParams(searchParams); - if (term) { - params.set("user", term); - } else { - params.delete("user"); - } - replace(`${pathname}?${params.toString()}`); - }, 100); - - return ( -
- {/* Needs to clear text */} - handleSearch(e.target.value)} - /> - -
- ); -} diff --git a/apps/web/src/components/admin/users/UserColumns.tsx b/apps/web/src/components/admin/users/UserColumns.tsx index a65e1ec8..f67a2cc4 100644 --- a/apps/web/src/components/admin/users/UserColumns.tsx +++ b/apps/web/src/components/admin/users/UserColumns.tsx @@ -6,29 +6,56 @@ import { createSelectSchema } from "drizzle-zod"; import { userCommonData } from "db/schema"; import Link from "next/link"; import { Button } from "@/components/shadcn/ui/button"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "../../shadcn/ui/dropdown-menu"; +import { MoreHorizontal,ArrowUpDown } from "lucide-react"; +import type { Column,Row } from "@tanstack/react-table"; const userValidator = createSelectSchema(userCommonData); +// default fuzzy search and add filters by each column if possible export type userValidatorType = Pick< z.infer, | "clerkID" | "signupTime" | "firstName" | "lastName" - | "hackerTag" | "email" | "role" + | 'isRSVPed' + | 'hackerTag' + | 'checkinTimestamp' >; +type UserColumnType = Column; + export const columns: ColumnDef[] = [ { accessorKey: "firstName", - header: "Name", + header: ({column}) => { + return ( + + ) + }, cell: ({ row }) => `${row.original.firstName} ${row.original.lastName}`, + filterFn: (row, _columnId, filterValue) => { + return row.original.firstName.toLocaleLowerCase().includes(filterValue.toLocaleLowerCase()) || row.original.lastName.toLocaleLowerCase().includes(filterValue.toLocaleLowerCase()); + } }, { accessorKey: "email", - header: "Email", + header: ({ column }) => { + return ( + + ) + }, }, { accessorKey: "hackerTag", @@ -36,8 +63,33 @@ export const columns: ColumnDef[] = [ cell: ({ row }) => `@${row.original.hackerTag}`, }, { - accessorKey: "clerkID", - header: "Account ID", + accessorKey: "isRSVPed", + header: "RSVP Status", + cell: ({ row }) => (row.original.isRSVPed ? "RSVPed" : "Not RSVPed"), + }, + { + accessorKey: "checkinTimestamp", + header: ({ column }) => { + return ( + + ) + }, + cell: ({ row }) => ( + + {row.original.checkinTimestamp + ? new Date( + row.original.checkinTimestamp, + ).toLocaleDateString() + + " " + + new Date( + row.original.checkinTimestamp, + ).toLocaleTimeString("en-US", { + hour: "2-digit", + minute: "2-digit", + }) + : "Not Checked In"} + + ), }, { accessorKey: "role", @@ -45,7 +97,11 @@ export const columns: ColumnDef[] = [ }, { accessorKey: "signupTime", - header: "Signup Date", + header: ({ column }) => { + return ( + + ) + }, cell: ({ row }) => ( {new Date(row.original.signupTime).toLocaleDateString() + " "} @@ -57,12 +113,62 @@ export const columns: ColumnDef[] = [ ), }, { - accessorKey: "clerkID2", - header: "View", - cell: ({ row }) => ( - - - - ), + id: "actions", + enableHiding: false, + cell: ({ row }) => { + return ; + }, }, ]; + +function UserDropDownActions({row}:{row:Row}) { + const user = row.original; + + return ( + + + + + + + + View User + + + + navigator.clipboard.writeText(user.clerkID) + } + className="cursor-pointer" + > + Copy Clerk ID + + + + Email User + + + + + ); + } + +function SortColumnButton({name,column}:{name:string,column:UserColumnType}) { + return ( + + ) +} + diff --git a/apps/web/src/components/admin/users/UserDataTable.tsx b/apps/web/src/components/admin/users/UserDataTable.tsx index 5f4191b9..a111f238 100644 --- a/apps/web/src/components/admin/users/UserDataTable.tsx +++ b/apps/web/src/components/admin/users/UserDataTable.tsx @@ -5,6 +5,11 @@ import { flexRender, getCoreRowModel, useReactTable, + SortingState, + getSortedRowModel, + getPaginationRowModel, + ColumnFiltersState, + getFilteredRowModel } from "@tanstack/react-table"; import { @@ -15,9 +20,9 @@ import { TableHeader, TableRow, } from "@/components/shadcn/ui/table"; - -import { useCallback } from "react"; -import { useRouter } from "next/navigation"; +import { Input } from "@/components/shadcn/ui/input"; +import { Button } from "@/components/shadcn/ui/button"; +import { useEffect, useState } from "react"; interface DataTableProps { columns: ColumnDef[]; @@ -28,68 +33,125 @@ export function DataTable({ columns, data, }: DataTableProps) { - const formatTrProps = (state = {}) => { - console.log("qua"); - return { onClick: () => console.log("state", state) }; - }; + + const [sorting,setSorting] = useState([]); + const [columnFilters, setColumnFilters] = + useState([]); const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onSortingChange:setSorting, + getSortedRowModel: getSortedRowModel(), + onColumnFiltersChange: setColumnFilters, + getFilteredRowModel: getFilteredRowModel(), + state:{ + sorting, + columnFilters + } }); + useEffect(()=>{ + console.log("column filters",columnFilters); + },[columnFilters]); + 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(), - )} - - ))} +
+
+ { + table + .getColumn("firstName") + ?.setFilterValue(event.target.value); + table + .getColumn("email") + ?.setFilterValue(event.target.value); + // table + // .getColumn("hackerTag") + // ?.setFilterValue(event.target.value); + } + + } + className="max-w-sm" + /> +
+ + {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. + - )) - ) : ( - - - No results. - - - )} - -
+ )} + + +
+
+ + +
); } diff --git a/packages/db/functions/user.ts b/packages/db/functions/user.ts index de507ac4..c72d68ae 100644 --- a/packages/db/functions/user.ts +++ b/packages/db/functions/user.ts @@ -4,11 +4,19 @@ import { HackerData, User } from "../types"; // const _getAllUsers = db.query.userCommonData.findMany().prepare("getAllUsers"); -export function getAllUsers(): Promise { +export function getAllUsers() { // return _getAllUsers.execute(); return db.query.userCommonData.findMany(); } +export async function getAllUsersWithHackerData(){ + return db.query.userCommonData.findMany({ + with:{ + hackerData:true + } + }) +} + // ID // const _getUser = db.query.userCommonData From cc35e548f1a1e2f573e036dab4aa5b357107794e Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Sat, 7 Dec 2024 00:29:16 -0600 Subject: [PATCH 2/8] commits --- apps/web/package.json | 1 + .../components/admin/users/UserColumns.tsx | 34 ++++++++----------- .../components/admin/users/UserDataTable.tsx | 27 +++++++++++---- pnpm-lock.yaml | 29 ++++++++++++---- 4 files changed, 59 insertions(+), 32 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index d2c3ed25..ce4b3a4a 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -28,6 +28,7 @@ "@radix-ui/react-switch": "^1.0.3", "@react-email/components": "^0.0.21", "@t3-oss/env-nextjs": "^0.10.1", + "@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/react-query": "^5.51.11", "@tanstack/react-table": "^8.19.3", "@trpc/client": "11.0.0-rc.466", diff --git a/apps/web/src/components/admin/users/UserColumns.tsx b/apps/web/src/components/admin/users/UserColumns.tsx index f67a2cc4..855b10cf 100644 --- a/apps/web/src/components/admin/users/UserColumns.tsx +++ b/apps/web/src/components/admin/users/UserColumns.tsx @@ -39,28 +39,28 @@ type UserColumnType = Column; export const columns: ColumnDef[] = [ { accessorKey: "firstName", - header: ({column}) => { - return ( - - ) + header: ({ column }) => { + return ; }, cell: ({ row }) => `${row.original.firstName} ${row.original.lastName}`, - filterFn: (row, _columnId, filterValue) => { - return row.original.firstName.toLocaleLowerCase().includes(filterValue.toLocaleLowerCase()) || row.original.lastName.toLocaleLowerCase().includes(filterValue.toLocaleLowerCase()); - } + // filterFn: (row, _columnId, filterValue) => { + // return row.original.firstName.toLocaleLowerCase().includes(filterValue.toLocaleLowerCase()) || row.original.lastName.toLocaleLowerCase().includes(filterValue.toLocaleLowerCase()); + // }, + filterFn: "fuzzy", + }, { accessorKey: "email", header: ({ column }) => { - return ( - - ) - }, + return ; + }, + filterFn: "fuzzy", }, { accessorKey: "hackerTag", header: "Hacker Tag", cell: ({ row }) => `@${row.original.hackerTag}`, + filterFn: "fuzzy", }, { accessorKey: "isRSVPed", @@ -70,10 +70,8 @@ export const columns: ColumnDef[] = [ { accessorKey: "checkinTimestamp", header: ({ column }) => { - return ( - - ) - }, + return ; + }, cell: ({ row }) => ( {row.original.checkinTimestamp @@ -98,10 +96,8 @@ export const columns: ColumnDef[] = [ { accessorKey: "signupTime", header: ({ column }) => { - return ( - - ) - }, + return ; + }, cell: ({ row }) => ( {new Date(row.original.signupTime).toLocaleDateString() + " "} diff --git a/apps/web/src/components/admin/users/UserDataTable.tsx b/apps/web/src/components/admin/users/UserDataTable.tsx index a111f238..a5319650 100644 --- a/apps/web/src/components/admin/users/UserDataTable.tsx +++ b/apps/web/src/components/admin/users/UserDataTable.tsx @@ -9,9 +9,9 @@ import { getSortedRowModel, getPaginationRowModel, ColumnFiltersState, - getFilteredRowModel + getFilteredRowModel, + FilterFn } from "@tanstack/react-table"; - import { Table, TableBody, @@ -23,12 +23,24 @@ import { import { Input } from "@/components/shadcn/ui/input"; import { Button } from "@/components/shadcn/ui/button"; import { useEffect, useState } from "react"; +import { rankItem} from "@tanstack/match-sorter-utils" interface DataTableProps { columns: ColumnDef[]; data: TData[]; } +const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value); + + // Store the itemRank info + addMeta({ itemRank }); + + // Return if the item should be filtered in/out + return itemRank.passed; +}; + export function DataTable({ columns, data, @@ -41,16 +53,19 @@ export function DataTable({ const table = useReactTable({ data, columns, + filterFns:{ + fuzzy:fuzzyFilter + }, + globalFilterFn:'fuzzy', getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), onSortingChange:setSorting, getSortedRowModel: getSortedRowModel(), - onColumnFiltersChange: setColumnFilters, getFilteredRowModel: getFilteredRowModel(), state:{ sorting, - columnFilters - } + }, + }); useEffect(()=>{ @@ -59,7 +74,7 @@ export function DataTable({ return (
-
+
=12'} + dependencies: + remove-accents: 0.5.0 + dev: false + /@tanstack/query-core@5.51.9: resolution: {integrity: sha512-HsAwaY5J19MD18ykZDS3aVVh+bAt0i7m6uQlFC2b77DLV9djo+xEN7MWQAQQTR8IM+7r/zbozTQ7P0xr0bHuew==} dev: false @@ -11379,6 +11385,10 @@ packages: rc: 1.2.8 dev: true + /remove-accents@0.5.0: + resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==} + dev: false + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -11959,7 +11969,7 @@ packages: engines: {node: '>=6'} dev: false - /terser-webpack-plugin@5.3.10(esbuild@0.24.0)(webpack@5.93.0): + /terser-webpack-plugin@5.3.10(@swc/core@1.3.101)(esbuild@0.19.11)(webpack@5.93.0): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -11976,7 +11986,8 @@ packages: optional: true dependencies: '@jridgewell/trace-mapping': 0.3.25 - esbuild: 0.24.0 + '@swc/core': 1.3.101 + esbuild: 0.19.11 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 @@ -12578,7 +12589,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(esbuild@0.24.0)(webpack@5.93.0) + terser-webpack-plugin: 5.3.10(@swc/core@1.3.101)(esbuild@0.19.11)(webpack@5.93.0) watchpack: 2.4.1 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -12746,3 +12757,7 @@ packages: /zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} dev: false + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false From f9701456a71b2c06d3821c4ce127b505be2acd59 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Wed, 18 Dec 2024 12:34:13 -0600 Subject: [PATCH 3/8] Refactors client Logout --- apps/web/src/components/dash/shared/ProfileButton.tsx | 2 +- apps/web/src/components/shared/ProfileButton.tsx | 2 +- apps/web/src/lib/utils/server/user.ts | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/lib/utils/server/user.ts diff --git a/apps/web/src/components/dash/shared/ProfileButton.tsx b/apps/web/src/components/dash/shared/ProfileButton.tsx index 5f2b8d13..f13cfcdd 100644 --- a/apps/web/src/components/dash/shared/ProfileButton.tsx +++ b/apps/web/src/components/dash/shared/ProfileButton.tsx @@ -17,7 +17,7 @@ import { auth, SignOutButton } from "@clerk/nextjs"; import Link from "next/link"; import { DropdownSwitcher } from "@/components/shared/ThemeSwitcher"; import { getUser } from "db/functions"; -import { clientLogOut } from "@/lib/utils/client/shared"; +import { clientLogOut } from "@/lib/utils/server/user"; export default async function ProfileButton() { const clerkUser = auth(); diff --git a/apps/web/src/components/shared/ProfileButton.tsx b/apps/web/src/components/shared/ProfileButton.tsx index 555924e7..f833ad99 100644 --- a/apps/web/src/components/shared/ProfileButton.tsx +++ b/apps/web/src/components/shared/ProfileButton.tsx @@ -19,7 +19,7 @@ import { DropdownSwitcher } from "@/components/shared/ThemeSwitcher"; import DefaultDropdownTrigger from "../dash/shared/DefaultDropDownTrigger"; import MobileNavBarLinks from "./MobileNavBarLinks"; import { getUser } from "db/functions"; -import { clientLogOut } from "@/lib/utils/client/shared"; +import { clientLogOut } from "@/lib/utils/server/user"; export default async function ProfileButton() { const clerkUser = await auth(); diff --git a/apps/web/src/lib/utils/server/user.ts b/apps/web/src/lib/utils/server/user.ts new file mode 100644 index 00000000..4789dea3 --- /dev/null +++ b/apps/web/src/lib/utils/server/user.ts @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; +export async function clientLogOut() { + "use server"; + redirect("/"); +} From ca02089b3dcd895c1ee4af8122771054013de7e9 Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Wed, 18 Dec 2024 12:37:02 -0600 Subject: [PATCH 4/8] prelimernary fuzzy filter --- .../components/admin/users/UserColumns.tsx | 9 ++-- .../components/admin/users/UserDataTable.tsx | 48 ++++++------------- apps/web/src/lib/utils/client/shared.ts | 19 ++++++-- 3 files changed, 33 insertions(+), 43 deletions(-) diff --git a/apps/web/src/components/admin/users/UserColumns.tsx b/apps/web/src/components/admin/users/UserColumns.tsx index 855b10cf..cb34c548 100644 --- a/apps/web/src/components/admin/users/UserColumns.tsx +++ b/apps/web/src/components/admin/users/UserColumns.tsx @@ -17,6 +17,7 @@ import { } from "../../shadcn/ui/dropdown-menu"; import { MoreHorizontal,ArrowUpDown } from "lucide-react"; import type { Column,Row } from "@tanstack/react-table"; +import { dataTableFuzzyFilter } from "@/lib/utils/client/shared"; const userValidator = createSelectSchema(userCommonData); @@ -46,7 +47,7 @@ export const columns: ColumnDef[] = [ // filterFn: (row, _columnId, filterValue) => { // return row.original.firstName.toLocaleLowerCase().includes(filterValue.toLocaleLowerCase()) || row.original.lastName.toLocaleLowerCase().includes(filterValue.toLocaleLowerCase()); // }, - filterFn: "fuzzy", + filterFn: "includesString", }, { @@ -54,13 +55,13 @@ export const columns: ColumnDef[] = [ header: ({ column }) => { return ; }, - filterFn: "fuzzy", + filterFn: dataTableFuzzyFilter, }, { accessorKey: "hackerTag", header: "Hacker Tag", cell: ({ row }) => `@${row.original.hackerTag}`, - filterFn: "fuzzy", + filterFn: dataTableFuzzyFilter, }, { accessorKey: "isRSVPed", @@ -92,6 +93,7 @@ export const columns: ColumnDef[] = [ { accessorKey: "role", header: "Role", + filterFn:"includesString" }, { accessorKey: "signupTime", @@ -119,7 +121,6 @@ export const columns: ColumnDef[] = [ function UserDropDownActions({row}:{row:Row}) { const user = row.original; - return ( diff --git a/apps/web/src/components/admin/users/UserDataTable.tsx b/apps/web/src/components/admin/users/UserDataTable.tsx index a5319650..0d72bca4 100644 --- a/apps/web/src/components/admin/users/UserDataTable.tsx +++ b/apps/web/src/components/admin/users/UserDataTable.tsx @@ -10,7 +10,6 @@ import { getPaginationRowModel, ColumnFiltersState, getFilteredRowModel, - FilterFn } from "@tanstack/react-table"; import { Table, @@ -23,24 +22,12 @@ import { import { Input } from "@/components/shadcn/ui/input"; import { Button } from "@/components/shadcn/ui/button"; import { useEffect, useState } from "react"; -import { rankItem} from "@tanstack/match-sorter-utils" - +import { dataTableFuzzyFilter } from "@/lib/utils/client/shared"; interface DataTableProps { columns: ColumnDef[]; data: TData[]; } -const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { - // Rank the item - const itemRank = rankItem(row.getValue(columnId), value); - - // Store the itemRank info - addMeta({ itemRank }); - - // Return if the item should be filtered in/out - return itemRank.passed; -}; - export function DataTable({ columns, data, @@ -49,23 +36,25 @@ export function DataTable({ const [sorting,setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); + const [globalFilter, setGlobalFilter] = useState(""); const table = useReactTable({ data, columns, - filterFns:{ - fuzzy:fuzzyFilter + filterFns: { + fuzzy: dataTableFuzzyFilter, }, - globalFilterFn:'fuzzy', + state: { + sorting, + columnFilters, + globalFilter, + }, + globalFilterFn: dataTableFuzzyFilter, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), - onSortingChange:setSorting, + onSortingChange: setSorting, getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), - state:{ - sorting, - }, - }); useEffect(()=>{ @@ -78,20 +67,11 @@ export function DataTable({ { - table - .getColumn("firstName") - ?.setFilterValue(event.target.value); - table - .getColumn("email") - ?.setFilterValue(event.target.value); - // table - // .getColumn("hackerTag") - // ?.setFilterValue(event.target.value); + // we want to set our global filter + setGlobalFilter(event.target.value); } } diff --git a/apps/web/src/lib/utils/client/shared.ts b/apps/web/src/lib/utils/client/shared.ts index 440c8eac..b23eab33 100644 --- a/apps/web/src/lib/utils/client/shared.ts +++ b/apps/web/src/lib/utils/client/shared.ts @@ -1,8 +1,17 @@ -import { redirect } from "next/navigation"; +import { rankItem } from "@tanstack/match-sorter-utils"; +import { FilterFn } from "@tanstack/react-table"; + export function getClientTimeZone(vercelIPTimeZone: string | null) { return vercelIPTimeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone; } -export async function clientLogOut(){ - "use server"; - redirect("/"); -}; + +export const dataTableFuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value); + + // Store the itemRank info + addMeta({ itemRank }); + + // Return if the item should be filtered in/out + return itemRank.passed; +}; \ No newline at end of file From 4ff04317997da2d5233986e9d90f34bd24a7a00c Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Fri, 20 Dec 2024 14:51:33 -0600 Subject: [PATCH 5/8] starts UI touch ups --- .../components/admin/users/UserColumns.tsx | 94 ++++++++++++++----- .../components/admin/users/UserDataTable.tsx | 3 +- apps/web/src/lib/utils/client/shared.ts | 5 +- 3 files changed, 75 insertions(+), 27 deletions(-) diff --git a/apps/web/src/components/admin/users/UserColumns.tsx b/apps/web/src/components/admin/users/UserColumns.tsx index cb34c548..fa5f2300 100644 --- a/apps/web/src/components/admin/users/UserColumns.tsx +++ b/apps/web/src/components/admin/users/UserColumns.tsx @@ -15,7 +15,8 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "../../shadcn/ui/dropdown-menu"; -import { MoreHorizontal,ArrowUpDown } from "lucide-react"; +import { Input } from "@/components/shadcn/ui/input"; +import { MoreHorizontal,ArrowUpDown, User } from "lucide-react"; import type { Column,Row } from "@tanstack/react-table"; import { dataTableFuzzyFilter } from "@/lib/utils/client/shared"; @@ -39,40 +40,53 @@ type UserColumnType = Column; export const columns: ColumnDef[] = [ { - accessorKey: "firstName", - header: ({ column }) => { - return ; - }, - cell: ({ row }) => `${row.original.firstName} ${row.original.lastName}`, + + accessorFn: row => `${row.firstName} ${row.lastName}`, + id:"name", + header: ({ column }) => ( + + ), + cell: info => info.getValue(), // filterFn: (row, _columnId, filterValue) => { // return row.original.firstName.toLocaleLowerCase().includes(filterValue.toLocaleLowerCase()) || row.original.lastName.toLocaleLowerCase().includes(filterValue.toLocaleLowerCase()); // }, - filterFn: "includesString", + filterFn: dataTableFuzzyFilter, }, { accessorKey: "email", - header: ({ column }) => { - return ; - }, - filterFn: dataTableFuzzyFilter, + header: ({ column }) => ( + + ), + filterFn: "includesString", + cell:info => info.getValue(), }, { accessorKey: "hackerTag", - header: "Hacker Tag", + header:({column}) =>( + + ), cell: ({ row }) => `@${row.original.hackerTag}`, filterFn: dataTableFuzzyFilter, }, { accessorKey: "isRSVPed", - header: "RSVP Status", + header: ({ column }) => ( +
+ RSVP Status + +
+ ), cell: ({ row }) => (row.original.isRSVPed ? "RSVPed" : "Not RSVPed"), }, { accessorKey: "checkinTimestamp", - header: ({ column }) => { - return ; - }, + header: ({ column }) => ( +
+ Checkin Time + +
+ ), cell: ({ row }) => ( {row.original.checkinTimestamp @@ -92,14 +106,19 @@ export const columns: ColumnDef[] = [ }, { accessorKey: "role", - header: "Role", + header: ({ column }) => ( + + ), filterFn:"includesString" }, { accessorKey: "signupTime", - header: ({ column }) => { - return ; - }, + header: ({ column }) => ( +
+ Signup Time + +
+ ), cell: ({ row }) => ( {new Date(row.original.signupTime).toLocaleDateString() + " "} @@ -155,17 +174,42 @@ function UserDropDownActions({row}:{row:Row}) {
); - } +} + + +function UserTableHeader({ + name, + column, + hasFilter, +}: { + name: string; + column: UserColumnType; + hasFilter: boolean; +}) { + return ( +
+
+ {name} + {hasFilter && } +
+ column.setFilterValue(e.target.value)} + placeholder="search..." + /> +
+ ); +} function SortColumnButton({name,column}:{name:string,column:UserColumnType}) { return ( ) } + + diff --git a/apps/web/src/components/admin/users/UserDataTable.tsx b/apps/web/src/components/admin/users/UserDataTable.tsx index 0d72bca4..576198d5 100644 --- a/apps/web/src/components/admin/users/UserDataTable.tsx +++ b/apps/web/src/components/admin/users/UserDataTable.tsx @@ -49,6 +49,7 @@ export function DataTable({ columnFilters, globalFilter, }, + onColumnFiltersChange: setColumnFilters, globalFilterFn: dataTableFuzzyFilter, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), @@ -80,7 +81,7 @@ export function DataTable({ {table.getHeaderGroups().map((headerGroup) => ( - + {headerGroup.headers.map((header) => { return ( diff --git a/apps/web/src/lib/utils/client/shared.ts b/apps/web/src/lib/utils/client/shared.ts index b23eab33..27d2ee8e 100644 --- a/apps/web/src/lib/utils/client/shared.ts +++ b/apps/web/src/lib/utils/client/shared.ts @@ -6,12 +6,15 @@ export function getClientTimeZone(vercelIPTimeZone: string | null) { } export const dataTableFuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { + // Rank the item const itemRank = rankItem(row.getValue(columnId), value); // Store the itemRank info addMeta({ itemRank }); - // Return if the item should be filtered in/out + if (columnId === "name"){ + console.log(`row:, ${row.getValue(columnId)} value: ${value} itemRank: ${itemRank.passed}`); + } return itemRank.passed; }; \ No newline at end of file From 84e5fee01d3fbd85417a48e1a5d5efb09bd2e6fc Mon Sep 17 00:00:00 2001 From: meisullum Date: Sat, 21 Dec 2024 02:15:50 -0600 Subject: [PATCH 6/8] restyle table + rsvp --- apps/web/src/app/admin/layout.tsx | 5 +- apps/web/src/app/admin/users/page.tsx | 8 +- .../components/admin/users/UserColumns.tsx | 144 ++++++++++-------- .../components/admin/users/UserDataTable.tsx | 48 +++--- apps/web/src/lib/utils/client/shared.ts | 16 +- packages/db/functions/user.ts | 10 +- 6 files changed, 121 insertions(+), 110 deletions(-) diff --git a/apps/web/src/app/admin/layout.tsx b/apps/web/src/app/admin/layout.tsx index f48e973e..a573520b 100644 --- a/apps/web/src/app/admin/layout.tsx +++ b/apps/web/src/app/admin/layout.tsx @@ -39,10 +39,7 @@ export default async function AdminLayout({ children }: AdminLayoutProps) {
- +
-
+

Users @@ -36,10 +35,7 @@ export default async function Page() {
{userData && userData.length > 0 ? ( <> - + ) : (
diff --git a/apps/web/src/components/admin/users/UserColumns.tsx b/apps/web/src/components/admin/users/UserColumns.tsx index fa5f2300..dcae64bd 100644 --- a/apps/web/src/components/admin/users/UserColumns.tsx +++ b/apps/web/src/components/admin/users/UserColumns.tsx @@ -16,13 +16,14 @@ import { DropdownMenuTrigger, } from "../../shadcn/ui/dropdown-menu"; import { Input } from "@/components/shadcn/ui/input"; -import { MoreHorizontal,ArrowUpDown, User } from "lucide-react"; -import type { Column,Row } from "@tanstack/react-table"; +import { MoreHorizontal, ArrowUpDown, User } from "lucide-react"; +import type { Column, Row } from "@tanstack/react-table"; import { dataTableFuzzyFilter } from "@/lib/utils/client/shared"; +import { Badge } from "@/components/shadcn/ui/badge"; const userValidator = createSelectSchema(userCommonData); -// default fuzzy search and add filters by each column if possible +// default fuzzy search and add filters by each column if possible export type userValidatorType = Pick< z.infer, | "clerkID" @@ -31,27 +32,25 @@ export type userValidatorType = Pick< | "lastName" | "email" | "role" - | 'isRSVPed' - | 'hackerTag' - | 'checkinTimestamp' + | "isRSVPed" + | "hackerTag" + | "checkinTimestamp" >; type UserColumnType = Column; export const columns: ColumnDef[] = [ { - - accessorFn: row => `${row.firstName} ${row.lastName}`, - id:"name", + accessorFn: (row) => `${row.firstName} ${row.lastName}`, + id: "name", header: ({ column }) => ( ), - cell: info => info.getValue(), + cell: (info) => info.getValue(), // filterFn: (row, _columnId, filterValue) => { // return row.original.firstName.toLocaleLowerCase().includes(filterValue.toLocaleLowerCase()) || row.original.lastName.toLocaleLowerCase().includes(filterValue.toLocaleLowerCase()); // }, filterFn: dataTableFuzzyFilter, - }, { accessorKey: "email", @@ -59,12 +58,16 @@ export const columns: ColumnDef[] = [ ), filterFn: "includesString", - cell:info => info.getValue(), + cell: (info) => info.getValue(), }, { accessorKey: "hackerTag", - header:({column}) =>( - + header: ({ column }) => ( + ), cell: ({ row }) => `@${row.original.hackerTag}`, filterFn: dataTableFuzzyFilter, @@ -72,18 +75,26 @@ export const columns: ColumnDef[] = [ { accessorKey: "isRSVPed", header: ({ column }) => ( -
- RSVP Status +
+ RSVP Status
), - cell: ({ row }) => (row.original.isRSVPed ? "RSVPed" : "Not RSVPed"), + // row.original.isRSVPed ? + cell: ({ row }) => ( + +
+ isRSVP + + ), }, { accessorKey: "checkinTimestamp", header: ({ column }) => ( -
- Checkin Time +
+ Checkin Time
), @@ -108,14 +119,14 @@ export const columns: ColumnDef[] = [ accessorKey: "role", header: ({ column }) => ( - ), - filterFn:"includesString" + ), + filterFn: "includesString", }, { accessorKey: "signupTime", header: ({ column }) => ( -
- Signup Time +
+ Signup Time
), @@ -138,45 +149,40 @@ export const columns: ColumnDef[] = [ }, ]; -function UserDropDownActions({row}:{row:Row}) { +function UserDropDownActions({ row }: { row: Row }) { const user = row.original; - return ( - - - - - - - - View User - - - - navigator.clipboard.writeText(user.clerkID) - } - className="cursor-pointer" - > - Copy Clerk ID - - - - Email User - - - - - ); + return ( + + + + + + + View User + + navigator.clipboard.writeText(user.clerkID)} + className="cursor-pointer" + > + Copy Clerk ID + + + + Email User + + + + + ); } - function UserTableHeader({ name, column, @@ -188,12 +194,12 @@ function UserTableHeader({ }) { return (
-
+
{name} {hasFilter && }
column.setFilterValue(e.target.value)} placeholder="search..." /> @@ -201,15 +207,19 @@ function UserTableHeader({ ); } -function SortColumnButton({name,column}:{name:string,column:UserColumnType}) { +function SortColumnButton({ + name, + column, +}: { + name: string; + column: UserColumnType; +}) { return ( - ) + ); } - - - diff --git a/apps/web/src/components/admin/users/UserDataTable.tsx b/apps/web/src/components/admin/users/UserDataTable.tsx index 576198d5..a9a0aa55 100644 --- a/apps/web/src/components/admin/users/UserDataTable.tsx +++ b/apps/web/src/components/admin/users/UserDataTable.tsx @@ -32,10 +32,8 @@ export function DataTable({ columns, data, }: DataTableProps) { - - const [sorting,setSorting] = useState([]); - const [columnFilters, setColumnFilters] = - useState([]); + const [sorting, setSorting] = useState([]); + const [columnFilters, setColumnFilters] = useState([]); const [globalFilter, setGlobalFilter] = useState(""); const table = useReactTable({ @@ -58,33 +56,37 @@ export function DataTable({ getFilteredRowModel: getFilteredRowModel(), }); - useEffect(()=>{ - console.log("column filters",columnFilters); - },[columnFilters]); + useEffect(() => { + console.log("column filters", columnFilters); + }, [columnFilters]); return (
-
- { - // we want to set our global filter - setGlobalFilter(event.target.value); - } - - } - className="max-w-sm" - /> +
+
+ { + // we want to set our global filter + setGlobalFilter(event.target.value); + }} + className="max-w-sm" + /> +

{table.getHeaderGroups().map((headerGroup) => ( - + {headerGroup.headers.map((header) => { return ( - + {header.isPlaceholder ? null : flexRender( diff --git a/apps/web/src/lib/utils/client/shared.ts b/apps/web/src/lib/utils/client/shared.ts index 27d2ee8e..2c549f3f 100644 --- a/apps/web/src/lib/utils/client/shared.ts +++ b/apps/web/src/lib/utils/client/shared.ts @@ -5,16 +5,22 @@ export function getClientTimeZone(vercelIPTimeZone: string | null) { return vercelIPTimeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone; } -export const dataTableFuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { - +export const dataTableFuzzyFilter: FilterFn = ( + row, + columnId, + value, + addMeta, +) => { // Rank the item const itemRank = rankItem(row.getValue(columnId), value); // Store the itemRank info addMeta({ itemRank }); // Return if the item should be filtered in/out - if (columnId === "name"){ - console.log(`row:, ${row.getValue(columnId)} value: ${value} itemRank: ${itemRank.passed}`); + if (columnId === "name") { + console.log( + `row:, ${row.getValue(columnId)} value: ${value} itemRank: ${itemRank.passed}`, + ); } return itemRank.passed; -}; \ No newline at end of file +}; diff --git a/packages/db/functions/user.ts b/packages/db/functions/user.ts index c72d68ae..6d6a8783 100644 --- a/packages/db/functions/user.ts +++ b/packages/db/functions/user.ts @@ -9,12 +9,12 @@ export function getAllUsers() { return db.query.userCommonData.findMany(); } -export async function getAllUsersWithHackerData(){ +export async function getAllUsersWithHackerData() { return db.query.userCommonData.findMany({ - with:{ - hackerData:true - } - }) + with: { + hackerData: true, + }, + }); } // ID From ad872102e4ef0eb499eb6105a38adb42c6191c5b Mon Sep 17 00:00:00 2001 From: meisullum Date: Wed, 25 Dec 2024 00:13:31 -0600 Subject: [PATCH 7/8] restyled that bruh --- .../components/admin/users/UserColumns.tsx | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/web/src/components/admin/users/UserColumns.tsx b/apps/web/src/components/admin/users/UserColumns.tsx index dcae64bd..63ecc115 100644 --- a/apps/web/src/components/admin/users/UserColumns.tsx +++ b/apps/web/src/components/admin/users/UserColumns.tsx @@ -72,6 +72,13 @@ export const columns: ColumnDef[] = [ cell: ({ row }) => `@${row.original.hackerTag}`, filterFn: dataTableFuzzyFilter, }, + { + accessorKey: "role", + header: ({ column }) => ( + + ), + filterFn: "includesString", + }, { accessorKey: "isRSVPed", header: ({ column }) => ( @@ -86,7 +93,9 @@ export const columns: ColumnDef[] = [
- isRSVP + + RSVP: {row.original.isRSVPed ? "Yes" : "No"} + ), }, @@ -115,13 +124,13 @@ export const columns: ColumnDef[] = [ ), }, - { - accessorKey: "role", - header: ({ column }) => ( - - ), - filterFn: "includesString", - }, + // { + // accessorKey: "role", + // header: ({ column }) => ( + // + // ), + // filterFn: "includesString", + // }, { accessorKey: "signupTime", header: ({ column }) => ( From 256c8ecc6e88ac3c94411c8aaf28673eb9cd170d Mon Sep 17 00:00:00 2001 From: Christian Walker Date: Wed, 25 Dec 2024 01:03:17 -0600 Subject: [PATCH 8/8] fixes merge --- apps/web/src/lib/utils/client/shared.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/web/src/lib/utils/client/shared.ts b/apps/web/src/lib/utils/client/shared.ts index f49501ce..8030090f 100644 --- a/apps/web/src/lib/utils/client/shared.ts +++ b/apps/web/src/lib/utils/client/shared.ts @@ -24,7 +24,3 @@ export const dataTableFuzzyFilter: FilterFn = ( } return itemRank.passed; }; -export async function clientLogOut() { - "use server"; - redirect("/"); -}