From b51ffcfbcd14f68273169890761d731961fd36f5 Mon Sep 17 00:00:00 2001 From: Nick A <nicolas.a.asanov@gmail.com> Date: Tue, 22 Oct 2024 15:41:47 -0400 Subject: [PATCH] Refactored user table --- compass/components/Table/Index.tsx | 306 -------------- compass/components/Table/ResourceIndex.tsx | 394 ++++-------------- compass/components/Table/ServiceIndex.tsx | 3 +- .../{ResourceTable.tsx => UserIndex.tsx} | 63 +-- 4 files changed, 126 insertions(+), 640 deletions(-) delete mode 100644 compass/components/Table/Index.tsx rename compass/components/Table/{ResourceTable.tsx => UserIndex.tsx} (63%) diff --git a/compass/components/Table/Index.tsx b/compass/components/Table/Index.tsx deleted file mode 100644 index 57e1a70..0000000 --- a/compass/components/Table/Index.tsx +++ /dev/null @@ -1,306 +0,0 @@ -// for showcasing to compass - -import users from "./users.json"; -import { - Cell, - ColumnDef, - Row, - createColumnHelper, - flexRender, - getCoreRowModel, - getFilteredRowModel, - sortingFns, - useReactTable, -} from "@tanstack/react-table"; -import { - ChangeEvent, - useState, - useEffect, - FunctionComponent, - useRef, - ChangeEventHandler, - Key, -} from "react"; -import { RowOptionMenu } from "./RowOptionMenu"; -import { RowOpenAction } from "./RowOpenAction"; -import { TableAction } from "./TableAction"; -import { - AtSymbolIcon, - Bars2Icon, - ArrowDownCircleIcon, - PlusIcon, -} from "@heroicons/react/24/solid"; -import TagsInput from "../TagsInput/Index"; -import { rankItem } from "@tanstack/match-sorter-utils"; -import User from "@/utils/models/User"; - -// For search -const fuzzyFilter = ( - row: Row<any>, - columnId: string, - value: any, - addMeta: (meta: any) => void -) => { - // Rank the item - const itemRank = rankItem(row.getValue(columnId), value); - - // Store the ranking info - addMeta(itemRank); - - // Return if the item should be filtered in/out - return itemRank.passed; -}; - -export const Table = ({ users }: { users: User[] }) => { - const columnHelper = createColumnHelper<User>(); - - useEffect(() => { - const sortedUsers = [...users].sort((a, b) => - a.visible === b.visible ? 0 : a.visible ? -1 : 1 - ); - setData(sortedUsers); - }, [users]); - - const deleteUser = (userId: number) => { - console.log(data); - setData((currentData) => - currentData.filter((user) => user.id !== userId) - ); - }; - - const hideUser = (userId: number) => { - console.log(`Toggling visibility for user with ID: ${userId}`); - setData((currentData) => { - const newData = currentData - .map((user) => { - if (user.id === userId) { - return { ...user, visible: !user.visible }; - } - return user; - }) - .sort((a, b) => - a.visible === b.visible ? 0 : a.visible ? -1 : 1 - ); - - console.log(newData); - return newData; - }); - }; - const [presetOptions, setPresetOptions] = useState([ - "administrator", - "volunteer", - "employee", - ]); - const [tagColors, setTagColors] = useState(new Map()); - - const getTagColor = (tag: string) => { - if (!tagColors.has(tag)) { - const colors = [ - "bg-cyan-100", - "bg-blue-100", - "bg-green-100", - "bg-yellow-100", - "bg-purple-100", - ]; - const randomColor = - colors[Math.floor(Math.random() * colors.length)]; - setTagColors(new Map(tagColors).set(tag, randomColor)); - } - return tagColors.get(tag); - }; - - const columns = [ - columnHelper.display({ - id: "options", - cell: (props) => ( - <RowOptionMenu - onDelete={() => deleteUser(props.row.original.id)} - onHide={() => hideUser(props.row.original.id)} - /> - ), - }), - columnHelper.accessor("username", { - header: () => ( - <> - <Bars2Icon className="inline align-top h-4" /> Username - </> - ), - cell: (info) => ( - <RowOpenAction - title={info.getValue()} - rowData={info.row.original} - onRowUpdate={handleRowUpdate} - /> - ), - }), - columnHelper.accessor("role", { - header: () => ( - <> - <ArrowDownCircleIcon className="inline align-top h-4" />{" "} - Role - </> - ), - cell: (info) => ( - <TagsInput - presetValue={info.getValue()} - presetOptions={presetOptions} - setPresetOptions={setPresetOptions} - getTagColor={getTagColor} - setTagColors={setTagColors} - /> - ), - }), - columnHelper.accessor("email", { - header: () => ( - <> - <AtSymbolIcon className="inline align-top h-4" /> Email - </> - ), - cell: (info) => ( - <span className="ml-2 text-gray-500 underline hover:text-gray-400"> - {info.getValue()} - </span> - ), - }), - columnHelper.accessor("program", { - header: () => ( - <> - <ArrowDownCircleIcon className="inline align-top h-4" />{" "} - Program - </> - ), - cell: (info) => <TagsInput presetValue={info.getValue()} />, - }), - ]; - - const [data, setData] = useState<User[]>([...users]); - - const addUser = () => { - setData([...data]); - }; - - // Searching - const [query, setQuery] = useState(""); - const handleSearchChange = (e: ChangeEvent) => { - const target = e.target as HTMLInputElement; - setQuery(String(target.value)); - }; - - const handleCellChange = (e: ChangeEvent, key: Key) => { - const target = e.target as HTMLInputElement; - console.log(key); - }; - - // TODO: Filtering - - // TODO: Sorting - - // added this fn for editing rows - const handleRowUpdate = (updatedRow: User) => { - const dataIndex = data.findIndex((row) => row.id === updatedRow.id); - if (dataIndex !== -1) { - const updatedData = [...data]; - updatedData[dataIndex] = updatedRow; - setData(updatedData); - } - }; - - const table = useReactTable({ - columns, - data, - filterFns: { - fuzzy: fuzzyFilter, - }, - state: { - globalFilter: query, - }, - onGlobalFilterChange: setQuery, - globalFilterFn: fuzzyFilter, - getCoreRowModel: getCoreRowModel(), - }); - - const handleRowData = (row: any) => { - const rowData: any = {}; - row.cells.forEach((cell: any) => { - rowData[cell.column.id] = cell.value; - }); - // Use rowData object containing data from all columns for the current row - console.log(rowData); - return rowData; - }; - - return ( - <div className="flex flex-col"> - <div className="flex flex-row justify-end"> - <TableAction query={query} handleChange={handleSearchChange} /> - </div> - <table className="w-full text-xs text-left rtl:text-right"> - <thead className="text-xs text-gray-500 capitalize"> - {table.getHeaderGroups().map((headerGroup) => ( - <tr key={headerGroup.id}> - {headerGroup.headers.map((header, i) => ( - <th - scope="col" - className={ - "p-2 border-gray-200 border-y font-medium " + - (1 < i && i < columns.length - 1 - ? "border-x" - : "") - } - key={header.id} - > - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - </th> - ))} - </tr> - ))} - </thead> - <tbody> - {table.getRowModel().rows.map((row) => { - // Individual row - const isUserVisible = row.original.visible; - const rowClassNames = `text-gray-800 border-y lowercase hover:bg-gray-50 ${ - !isUserVisible ? "bg-gray-200 text-gray-500" : "" - }`; - return ( - <tr className={rowClassNames} key={row.id}> - {row.getVisibleCells().map((cell, i) => ( - <td - key={cell.id} - className={ - "[&:nth-child(n+3)]:border-x relative first:text-left first:px-0 last:border-none" - } - > - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - </td> - ))} - </tr> - ); - })} - </tbody> - <tfoot> - <tr> - <td - className="p-3 border-y border-gray-200 text-gray-600 hover:bg-gray-50" - colSpan={100} - onClick={addUser} - > - <span className="flex ml-1 text-gray-500"> - <PlusIcon className="inline h-4 mr-1" /> - New - </span> - </td> - </tr> - </tfoot> - </table> - </div> - ); -}; diff --git a/compass/components/Table/ResourceIndex.tsx b/compass/components/Table/ResourceIndex.tsx index fbdf621..a4b66e7 100644 --- a/compass/components/Table/ResourceIndex.tsx +++ b/compass/components/Table/ResourceIndex.tsx @@ -1,303 +1,91 @@ -// for showcasing to compass - -import users from "./users.json"; -import { - Cell, - ColumnDef, - Row, - createColumnHelper, - flexRender, - getCoreRowModel, - getFilteredRowModel, - sortingFns, - useReactTable, -} from "@tanstack/react-table"; -import { - ChangeEvent, - useState, - useEffect, - FunctionComponent, - useRef, - ChangeEventHandler, - Key, -} from "react"; -import { RowOptionMenu } from "./RowOptionMenu"; -import { RowOpenAction } from "./RowOpenAction"; -import { TableAction } from "./TableAction"; -import { - AtSymbolIcon, - Bars2Icon, - ArrowDownCircleIcon, - PlusIcon, -} from "@heroicons/react/24/solid"; -import TagsInput from "../TagsInput/Index"; -import { rankItem } from "@tanstack/match-sorter-utils"; -import Resource from "@/utils/models/Resource"; - -// For search -const fuzzyFilter = ( - row: Row<any>, - columnId: string, - value: any, - addMeta: (meta: any) => void -) => { - // Rank the item - const itemRank = rankItem(row.getValue(columnId), value); - - // Store the ranking info - addMeta(itemRank); - - // Return if the item should be filtered in/out - return itemRank.passed; -}; - -// TODO: Rename everything to resources -export const ResourceTable = ({ users }: { users: Resource[] }) => { - const columnHelper = createColumnHelper<Resource>(); - - useEffect(() => { - const sortedUsers = [...users].sort((a, b) => - a.visible === b.visible ? 0 : a.visible ? -1 : 1 - ); - setData(sortedUsers); - }, [users]); - - const [presetOptions, setPresetOptions] = useState([ - "administrator", - "volunteer", - "employee", - ]); - - const deleteUser = (userId: number) => { - console.log(data); - setData((currentData) => - currentData.filter((user) => user.id !== userId) - ); - }; - - const hideUser = (userId: number) => { - console.log(`Toggling visibility for user with ID: ${userId}`); - setData((currentData) => { - const newData = currentData - .map((user) => { - if (user.id === userId) { - return { ...user, visible: !user.visible }; - } - return user; - }) - .sort((a, b) => - a.visible === b.visible ? 0 : a.visible ? -1 : 1 - ); - - console.log(newData); - return newData; - }); - }; - const [tagColors, setTagColors] = useState(new Map()); - - const getTagColor = (tag: string) => { - if (!tagColors.has(tag)) { - const colors = [ - "bg-cyan-100", - "bg-blue-100", - "bg-green-100", - "bg-yellow-100", - "bg-purple-100", - ]; - const randomColor = - colors[Math.floor(Math.random() * colors.length)]; - setTagColors(new Map(tagColors).set(tag, randomColor)); - } - return tagColors.get(tag); - }; - - const columns = [ - columnHelper.accessor("name", { - header: () => ( - <> - <Bars2Icon className="inline align-top h-4" /> Name - </> - ), - cell: (info) => ( - <RowOpenAction - title={info.getValue()} - rowData={info.row.original} - onRowUpdate={handleRowUpdate} - /> - ), - }), - columnHelper.accessor("link", { - header: () => ( - <> - <Bars2Icon className="inline align-top h-4" /> Link - </> - ), - cell: (info) => ( - <a - href={info.getValue()} - target={"_blank"} - className="ml-2 text-gray-500 underline hover:text-gray-400" - > - {info.getValue()} - </a> - ), - }), - columnHelper.accessor("program", { - header: () => ( - <> - <Bars2Icon className="inline align-top h-4" /> Program - </> - ), - cell: (info) => ( - <TagsInput - presetValue={info.getValue()} - presetOptions={presetOptions} - setPresetOptions={setPresetOptions} - getTagColor={getTagColor} - /> - ), - }), - - columnHelper.accessor("summary", { - header: () => ( - <> - <Bars2Icon className="inline align-top h-4" /> Summary - </> - ), - cell: (info) => ( - <span className="ml-2 text-gray-500">{info.getValue()}</span> - ), - }), - ]; - - const [data, setData] = useState<Resource[]>([...users]); - - const addUser = () => { - setData([...data]); - }; - - // Searching - const [query, setQuery] = useState(""); - const handleSearchChange = (e: ChangeEvent) => { - const target = e.target as HTMLInputElement; - setQuery(String(target.value)); - }; - - const handleCellChange = (e: ChangeEvent, key: Key) => { - const target = e.target as HTMLInputElement; - console.log(key); - }; - - // TODO: Filtering - - // TODO: Sorting - - // added this fn for editing rows - const handleRowUpdate = (updatedRow: Resource) => { - const dataIndex = data.findIndex((row) => row.id === updatedRow.id); - if (dataIndex !== -1) { - const updatedData = [...data]; - updatedData[dataIndex] = updatedRow; - setData(updatedData); - } - }; - - const table = useReactTable({ - columns, - data, - filterFns: { - fuzzy: fuzzyFilter, - }, - state: { - globalFilter: query, - }, - onGlobalFilterChange: setQuery, - globalFilterFn: fuzzyFilter, - getCoreRowModel: getCoreRowModel(), - }); - - const handleRowData = (row: any) => { - const rowData: any = {}; - row.cells.forEach((cell: any) => { - rowData[cell.column.id] = cell.value; - }); - // Use rowData object containing data from all columns for the current row - console.log(rowData); - return rowData; - }; - - return ( - <div className="flex flex-col"> - <div className="flex flex-row justify-end"> - <TableAction query={query} handleChange={handleSearchChange} /> - </div> - <table className="w-full text-xs text-left rtl:text-right"> - <thead className="text-xs text-gray-500 capitalize"> - {table.getHeaderGroups().map((headerGroup) => ( - <tr key={headerGroup.id}> - {headerGroup.headers.map((header, i) => ( - <th - scope="col" - className={ - "p-2 border-gray-200 border-y font-medium " + - (1 < i && i < columns.length - 1 - ? "border-x" - : "") - } - key={header.id} - > - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - </th> - ))} - </tr> - ))} - </thead> - <tbody> - {table.getRowModel().rows.map((row) => { - // Individual row - const isUserVisible = row.original.visible; - const rowClassNames = `text-gray-800 border-y lowercase hover:bg-gray-50 ${ - !isUserVisible ? "bg-gray-200 text-gray-500" : "" - }`; - return ( - <tr className={rowClassNames} key={row.id}> - {row.getVisibleCells().map((cell, i) => ( - <td - key={cell.id} - className={ - "[&:nth-child(n+3)]:border-x relative first:text-left first:px-0 last:border-none" - } - > - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - </td> - ))} - </tr> - ); - })} - </tbody> - <tfoot> - <tr> - <td - className="p-3 border-y border-gray-200 text-gray-600 hover:bg-gray-50" - colSpan={100} - onClick={addUser} - > - <span className="flex ml-1 text-gray-500"> - <PlusIcon className="inline h-4 mr-1" /> - New - </span> - </td> - </tr> - </tfoot> - </table> - </div> - ); -}; +import { Bars2Icon } from "@heroicons/react/24/solid"; +import { useState } from "react"; +import useTagsHandler from "@/components/TagsInput/TagsHandler"; +import { ColumnDef, createColumnHelper } from "@tanstack/react-table"; +import { Table } from "@/components/Table/Table"; +import { RowOpenAction } from "@/components/Table/RowOpenAction"; +import TagsInput from "@/components/TagsInput/Index"; +import Resource from "@/utils/models/Resource"; +import { DataPoint } from "@/components/Table/Table"; + +export function ResourceTable({ resources }: { resources: Resource[] }) { + const columnHelper = createColumnHelper<Resource>(); + const [data, setData] = useState<DataPoint[]>([...resources]); + + // TODO: Update preset options for resources + const { presetOptions, setPresetOptions, getTagColor } = useTagsHandler([ + "administrator", + "volunteer", + "employee", + ]) + + const handleRowUpdate = (updatedRow: DataPoint) => { + const dataIndex = data.findIndex((row) => row.id === updatedRow.id); + if (dataIndex !== -1) { + const updatedData = [...data]; + updatedData[dataIndex] = updatedRow; + setData(updatedData); + } + }; + + const columns: ColumnDef<Resource, any>[] = [ + columnHelper.accessor("name", { + header: () => ( + <> + <Bars2Icon className="inline align-top h-4" /> Name + </> + ), + cell: (info) => ( + <RowOpenAction + title={info.getValue()} + rowData={info.row.original} + onRowUpdate={handleRowUpdate} + /> + ), + }), + columnHelper.accessor("link", { + header: () => ( + <> + <Bars2Icon className="inline align-top h-4" /> Link + </> + ), + cell: (info) => ( + <a + href={info.getValue()} + target={"_blank"} + className="ml-2 text-gray-500 underline hover:text-gray-400" + > + {info.getValue()} + </a> + ), + }), + columnHelper.accessor("program", { + header: () => ( + <> + <Bars2Icon className="inline align-top h-4" /> Program + </> + ), + cell: (info) => ( + <TagsInput + presetValue={info.getValue()} + presetOptions={presetOptions} + setPresetOptions={setPresetOptions} + getTagColor={getTagColor} + /> + ), + }), + + columnHelper.accessor("summary", { + header: () => ( + <> + <Bars2Icon className="inline align-top h-4" /> Summary + </> + ), + cell: (info) => ( + <span className="ml-2 text-gray-500">{info.getValue()}</span> + ), + }), + ]; + + return <Table data={data} setData={setData} columns={columns}/> +} diff --git a/compass/components/Table/ServiceIndex.tsx b/compass/components/Table/ServiceIndex.tsx index 1405881..5447e01 100644 --- a/compass/components/Table/ServiceIndex.tsx +++ b/compass/components/Table/ServiceIndex.tsx @@ -1,5 +1,5 @@ import { Bars2Icon } from "@heroicons/react/24/solid"; -import { SetStateAction, useState } from "react"; +import { useState } from "react"; import useTagsHandler from "@/components/TagsInput/TagsHandler"; import { ColumnDef, createColumnHelper } from "@tanstack/react-table"; import { Table } from "@/components/Table/Table"; @@ -12,6 +12,7 @@ export const ServiceTable = ({ services }: { services: Service[] }) => { const columnHelper = createColumnHelper<Service>(); const [data, setData] = useState<DataPoint[]>([...services]); + // TODO: Update preset options for services const { presetOptions, setPresetOptions, getTagColor } = useTagsHandler([ "administrator", "volunteer", diff --git a/compass/components/Table/ResourceTable.tsx b/compass/components/Table/UserIndex.tsx similarity index 63% rename from compass/components/Table/ResourceTable.tsx rename to compass/components/Table/UserIndex.tsx index a134431..bc93f68 100644 --- a/compass/components/Table/ResourceTable.tsx +++ b/compass/components/Table/UserIndex.tsx @@ -1,16 +1,16 @@ -import { Bars2Icon } from "@heroicons/react/24/solid"; +import { ArrowDownCircleIcon, AtSymbolIcon, Bars2Icon } from "@heroicons/react/24/solid"; import { useState } from "react"; import useTagsHandler from "@/components/TagsInput/TagsHandler"; import { ColumnDef, createColumnHelper } from "@tanstack/react-table"; import { Table } from "@/components/Table/Table"; import { RowOpenAction } from "@/components/Table/RowOpenAction"; import TagsInput from "@/components/TagsInput/Index"; -import Resource from "@/utils/models/Resource"; +import User from "@/utils/models/User"; import { DataPoint } from "@/components/Table/Table"; -export function ResourceTable({ resources }: { resources: Resource[] }) { - const columnHelper = createColumnHelper<Resource>(); - const [data, setData] = useState<DataPoint[]>([...resources]); +export function UserTable({ users }: { users: User[] }) { + const columnHelper = createColumnHelper<User>(); + const [data, setData] = useState<DataPoint[]>([...users]); const { presetOptions, setPresetOptions, getTagColor } = useTagsHandler([ "administrator", @@ -27,11 +27,11 @@ export function ResourceTable({ resources }: { resources: Resource[] }) { } }; - const columns: ColumnDef<Resource, any>[] = [ - columnHelper.accessor("name", { + const columns: ColumnDef<User, any>[] = [ + columnHelper.accessor("username", { header: () => ( <> - <Bars2Icon className="inline align-top h-4" /> Name + <Bars2Icon className="inline align-top h-4" /> Username </> ), cell: (info) => ( @@ -42,26 +42,11 @@ export function ResourceTable({ resources }: { resources: Resource[] }) { /> ), }), - columnHelper.accessor("link", { + columnHelper.accessor("role", { header: () => ( <> - <Bars2Icon className="inline align-top h-4" /> Link - </> - ), - cell: (info) => ( - <a - href={info.getValue()} - target={"_blank"} - className="ml-2 text-gray-500 underline hover:text-gray-400" - > - {info.getValue()} - </a> - ), - }), - columnHelper.accessor("program", { - header: () => ( - <> - <Bars2Icon className="inline align-top h-4" /> Program + <ArrowDownCircleIcon className="inline align-top h-4" />{" "} + Role </> ), cell: (info) => ( @@ -73,15 +58,33 @@ export function ResourceTable({ resources }: { resources: Resource[] }) { /> ), }), - - columnHelper.accessor("summary", { + columnHelper.accessor("email", { header: () => ( <> - <Bars2Icon className="inline align-top h-4" /> Summary + <AtSymbolIcon className="inline align-top h-4" /> Email </> ), cell: (info) => ( - <span className="ml-2 text-gray-500">{info.getValue()}</span> + <span className="ml-2 text-gray-500 underline hover:text-gray-400"> + {info.getValue()} + </span> + ), + }), + columnHelper.accessor("program", { + header: () => ( + <> + <ArrowDownCircleIcon className="inline align-top h-4" />{" "} + Program + </> + ), + // TODO: Setup different tags handler for program + cell: (info) => ( + <TagsInput + presetValue={info.getValue()} + presetOptions={presetOptions} + setPresetOptions={setPresetOptions} + getTagColor={getTagColor} + /> ), }), ];