From 8b81f6cb4ff2a1e9d6103a507d97adcd9b8b95d3 Mon Sep 17 00:00:00 2001 From: Advik Arora Date: Tue, 2 Apr 2024 18:42:02 -0400 Subject: [PATCH 1/9] added delete and RowOption component --- compass/components/Table/Index.tsx | 10 +++++++--- compass/components/Table/RowOption.tsx | 10 ++++++++++ compass/components/Table/RowOptionMenu.tsx | 19 ++++++++++--------- compass/package-lock.json | 1 - 4 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 compass/components/Table/RowOption.tsx diff --git a/compass/components/Table/Index.tsx b/compass/components/Table/Index.tsx index d7db0d2..ce36256 100644 --- a/compass/components/Table/Index.tsx +++ b/compass/components/Table/Index.tsx @@ -31,13 +31,17 @@ type User = { export const Table = () => { const columnHelper = createColumnHelper(); + const deleteUser = (userId) => { + setData(currentData => currentData.filter(user => user.id !== userId)); + }; + const columns = [ columnHelper.display({ id: "options", - cell: props => + cell: props => deleteUser(props.row.original.id)} /> }), columnHelper.accessor("username", { - header: () => <> Username, + header: () => <> Username, cell: (info) => , }), columnHelper.accessor("role", { @@ -99,7 +103,7 @@ export const Table = () => { className={ "p-2 " + ((1 < i && i < columns.length - 1) ? "border-x" : "") - + ((i === 0) ? "text-center px-0" : "") + + ((i === 0) ? "text-left px-0" : "") } key={cell.id}> {flexRender(cell.column.columnDef.cell, cell.getContext())} diff --git a/compass/components/Table/RowOption.tsx b/compass/components/Table/RowOption.tsx new file mode 100644 index 0000000..0514c64 --- /dev/null +++ b/compass/components/Table/RowOption.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { TrashIcon, DocumentDuplicateIcon, ArrowUpRightIcon, EyeSlashIcon } from "@heroicons/react/24/solid"; + +export const RowOption = ({ icon: Icon, label, onClick }) => { + return ( + + ); +}; \ No newline at end of file diff --git a/compass/components/Table/RowOptionMenu.tsx b/compass/components/Table/RowOptionMenu.tsx index f7a5c92..5830fb5 100644 --- a/compass/components/Table/RowOptionMenu.tsx +++ b/compass/components/Table/RowOptionMenu.tsx @@ -1,9 +1,10 @@ //delete, duplicate, open -import { TrashIcon, DocumentDuplicateIcon, ArrowUpRightIcon, EllipsisVerticalIcon } from "@heroicons/react/24/solid"; +import { TrashIcon, DocumentDuplicateIcon, ArrowUpRightIcon, EllipsisVerticalIcon, EyeSlashIcon } from "@heroicons/react/24/solid"; +import Button from "../Button"; import { useState, useEffect, useRef } from "react"; +import { RowOption } from "./RowOption"; - -export const RowOptionMenu = () => { +export const RowOptionMenu = ( { onDelete } ) => { const [menuOpen, setMenuOpen] = useState(false); const openMenu = () => setMenuOpen(true); const closeMenu = () => setMenuOpen(false); @@ -15,12 +16,12 @@ export const RowOptionMenu = () => { <>
*]:p-1 [&>*]:px-5 [&>*]:rounded" + (!menuOpen ? " invisible" : "")} + className={"justify-start border border-gray-200 shadow-lg flex flex-col absolute bg-white w-auto p-2 rounded [&>*]:rounded" + (!menuOpen ? " invisible" : "")} > -
Delete
-
Duplicate
-
Open
-
- + + { /* handle open */ }} /> + { /* handle hide */ }} /> + + ); } diff --git a/compass/package-lock.json b/compass/package-lock.json index 1c26973..fd7ab14 100644 --- a/compass/package-lock.json +++ b/compass/package-lock.json @@ -4395,4 +4395,3 @@ } } } - From 8436709e37e4aa2e2e6147a1becacd56a33d5b4a Mon Sep 17 00:00:00 2001 From: Advik Arora Date: Wed, 3 Apr 2024 15:27:48 -0400 Subject: [PATCH 2/9] added filtering for hide feature, also added visible attribute to users --- compass/components/Table/Index.tsx | 23 ++++++++++++++++++++-- compass/components/Table/RowOptionMenu.tsx | 4 ++-- compass/components/Table/users.json | 2 +- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/compass/components/Table/Index.tsx b/compass/components/Table/Index.tsx index ce36256..c245143 100644 --- a/compass/components/Table/Index.tsx +++ b/compass/components/Table/Index.tsx @@ -8,7 +8,7 @@ import { getCoreRowModel, useReactTable, } from "@tanstack/react-table"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { RowOptionMenu } from "./RowOptionMenu"; import { RowOpenAction } from "./RowOpenAction"; import { TableAction } from "./TableAction"; @@ -25,20 +25,39 @@ type User = { program: "DOMESTIC" | "ECONOMIC" | "COMMUNITY"; experience: number; group?: string; + visible: boolean; }; export const Table = () => { const columnHelper = createColumnHelper(); + useEffect(() => { + const sortedUsers = [...usersExample].sort((a, b) => (a.visible === b.visible ? 0 : a.visible ? -1 : 1)); + setData(sortedUsers); + }, []); + const deleteUser = (userId) => { + console.log(data); setData(currentData => currentData.filter(user => user.id !== userId)); }; + const hideUser = (userId: number) => { + console.log(`Hiding user with ID: ${userId}`); // Confirm the ID being processed + setData(currentData => { + const newData = currentData.map(user => { + console.log(`Checking user with ID: ${user.id}`); // Confirm each user's ID being checked + return user.id === userId ? { ...user, visible: false } : user; + }).sort((a, b) => a.visible === b.visible ? 0 : a.visible ? -1 : 1); + + console.log(newData); // Check the sorted data + return newData; + }); + }; const columns = [ columnHelper.display({ id: "options", - cell: props => deleteUser(props.row.original.id)} /> + cell: props => deleteUser(props.row.original.id)} onHide={() => hideUser(props.row.original.id)} /> }), columnHelper.accessor("username", { header: () => <> Username, diff --git a/compass/components/Table/RowOptionMenu.tsx b/compass/components/Table/RowOptionMenu.tsx index 5830fb5..58c5284 100644 --- a/compass/components/Table/RowOptionMenu.tsx +++ b/compass/components/Table/RowOptionMenu.tsx @@ -4,7 +4,7 @@ import Button from "../Button"; import { useState, useEffect, useRef } from "react"; import { RowOption } from "./RowOption"; -export const RowOptionMenu = ( { onDelete } ) => { +export const RowOptionMenu = ( { onDelete, onHide } ) => { const [menuOpen, setMenuOpen] = useState(false); const openMenu = () => setMenuOpen(true); const closeMenu = () => setMenuOpen(false); @@ -20,7 +20,7 @@ export const RowOptionMenu = ( { onDelete } ) => { > { /* handle open */ }} /> - { /* handle hide */ }} /> + ); diff --git a/compass/components/Table/users.json b/compass/components/Table/users.json index 20caf99..7073ce3 100644 --- a/compass/components/Table/users.json +++ b/compass/components/Table/users.json @@ -1 +1 @@ -[{"id":0,"created_at":1711482132230,"username":"Bo_Pfeffer","role":"ADMIN","email":"Bo.Pfeffer@gmail.com","program":"DOMESTIC","experience":2,"group":""},{"id":1,"created_at":1711482132231,"username":"Marianna_Heathcote76","role":"ADMIN","email":"Marianna_Heathcote14@yahoo.com","program":"DOMESTIC","experience":1,"group":""},{"id":2,"created_at":1711482132231,"username":"Queenie_Schroeder","role":"VOLUNTEER","email":"Queenie_Schroeder@yahoo.com","program":"COMMUNITY","experience":5,"group":""},{"id":3,"created_at":1711482132231,"username":"Arne.Bode","role":"VOLUNTEER","email":"Arne.Bode@hotmail.com","program":"DOMESTIC","experience":3,"group":""},{"id":4,"created_at":1711482132231,"username":"Maia.Zulauf9","role":"ADMIN","email":"Maia_Zulauf@gmail.com","program":"DOMESTIC","experience":5,"group":""},{"id":5,"created_at":1711482132231,"username":"River_Bauch","role":"EMPLOYEE","email":"River.Bauch@yahoo.com","program":"ECONOMIC","experience":2,"group":""},{"id":6,"created_at":1711482132231,"username":"Virgil.Hilll","role":"VOLUNTEER","email":"Virgil.Hilll@yahoo.com","program":"ECONOMIC","experience":3,"group":""},{"id":7,"created_at":1711482132231,"username":"Bridget_Cartwright","role":"ADMIN","email":"Bridget_Cartwright@yahoo.com","program":"ECONOMIC","experience":3,"group":""},{"id":8,"created_at":1711482132231,"username":"Glennie_Keebler64","role":"EMPLOYEE","email":"Glennie_Keebler60@yahoo.com","program":"DOMESTIC","experience":2,"group":""},{"id":9,"created_at":1711482132232,"username":"Orin.Jenkins53","role":"EMPLOYEE","email":"Orin.Jenkins@gmail.com","program":"ECONOMIC","experience":1,"group":""},{"id":10,"created_at":1711482132232,"username":"Zachery.Rosenbaum","role":"ADMIN","email":"Zachery.Rosenbaum@hotmail.com","program":"COMMUNITY","experience":3,"group":""},{"id":11,"created_at":1711482132232,"username":"Phoebe.Ziemann","role":"EMPLOYEE","email":"Phoebe_Ziemann92@gmail.com","program":"COMMUNITY","experience":2,"group":""},{"id":12,"created_at":1711482132232,"username":"Bradford_Conroy53","role":"VOLUNTEER","email":"Bradford_Conroy94@hotmail.com","program":"COMMUNITY","experience":2,"group":""},{"id":13,"created_at":1711482132232,"username":"Florine_Strosin55","role":"VOLUNTEER","email":"Florine.Strosin29@hotmail.com","program":"ECONOMIC","experience":1,"group":""},{"id":14,"created_at":1711482132232,"username":"Constance.Doyle59","role":"EMPLOYEE","email":"Constance_Doyle@hotmail.com","program":"DOMESTIC","experience":3,"group":""},{"id":15,"created_at":1711482132232,"username":"Chauncey_Lockman","role":"ADMIN","email":"Chauncey_Lockman@yahoo.com","program":"DOMESTIC","experience":5,"group":""},{"id":16,"created_at":1711482132232,"username":"Esther_Wuckert-Larson26","role":"EMPLOYEE","email":"Esther_Wuckert-Larson@gmail.com","program":"ECONOMIC","experience":0,"group":""},{"id":17,"created_at":1711482132232,"username":"Jewel.Kunde","role":"VOLUNTEER","email":"Jewel_Kunde29@gmail.com","program":"ECONOMIC","experience":5,"group":""},{"id":18,"created_at":1711482132232,"username":"Hildegard_Parker92","role":"ADMIN","email":"Hildegard_Parker74@yahoo.com","program":"ECONOMIC","experience":2,"group":""},{"id":19,"created_at":1711482132232,"username":"Jordane.Lakin2","role":"ADMIN","email":"Jordane_Lakin@hotmail.com","program":"COMMUNITY","experience":1,"group":""}] \ No newline at end of file +[{"id":0,"created_at":1711482132230,"username":"Bo_Pfeffer","role":"ADMIN","email":"Bo.Pfeffer@gmail.com","program":"DOMESTIC","experience":2,"group":"", "visible": true},{"id":1,"created_at":1711482132231,"username":"Marianna_Heathcote76","role":"ADMIN","email":"Marianna_Heathcote14@yahoo.com","program":"DOMESTIC","experience":1,"group":"", "visible": true},{"id":2,"created_at":1711482132231,"username":"Queenie_Schroeder","role":"VOLUNTEER","email":"Queenie_Schroeder@yahoo.com","program":"COMMUNITY","experience":5,"group":"", "visible": true},{"id":3,"created_at":1711482132231,"username":"Arne.Bode","role":"VOLUNTEER","email":"Arne.Bode@hotmail.com","program":"DOMESTIC","experience":3,"group":"", "visible": true},{"id":4,"created_at":1711482132231,"username":"Maia.Zulauf9","role":"ADMIN","email":"Maia_Zulauf@gmail.com","program":"DOMESTIC","experience":5,"group":"", "visible": true},{"id":5,"created_at":1711482132231,"username":"River_Bauch","role":"EMPLOYEE","email":"River.Bauch@yahoo.com","program":"ECONOMIC","experience":2,"group":"", "visible": true},{"id":6,"created_at":1711482132231,"username":"Virgil.Hilll","role":"VOLUNTEER","email":"Virgil.Hilll@yahoo.com","program":"ECONOMIC","experience":3,"group":"", "visible": true},{"id":7,"created_at":1711482132231,"username":"Bridget_Cartwright","role":"ADMIN","email":"Bridget_Cartwright@yahoo.com","program":"ECONOMIC","experience":3,"group":"", "visible": true},{"id":8,"created_at":1711482132231,"username":"Glennie_Keebler64","role":"EMPLOYEE","email":"Glennie_Keebler60@yahoo.com","program":"DOMESTIC","experience":2,"group":"", "visible": true},{"id":9,"created_at":1711482132232,"username":"Orin.Jenkins53","role":"EMPLOYEE","email":"Orin.Jenkins@gmail.com","program":"ECONOMIC","experience":1,"group":"", "visible": true},{"id":10,"created_at":1711482132232,"username":"Zachery.Rosenbaum","role":"ADMIN","email":"Zachery.Rosenbaum@hotmail.com","program":"COMMUNITY","experience":3,"group":"", "visible": true},{"id":11,"created_at":1711482132232,"username":"Phoebe.Ziemann","role":"EMPLOYEE","email":"Phoebe_Ziemann92@gmail.com","program":"COMMUNITY","experience":2,"group":"", "visible": true},{"id":12,"created_at":1711482132232,"username":"Bradford_Conroy53","role":"VOLUNTEER","email":"Bradford_Conroy94@hotmail.com","program":"COMMUNITY","experience":2,"group":"", "visible": true},{"id":13,"created_at":1711482132232,"username":"Florine_Strosin55","role":"VOLUNTEER","email":"Florine.Strosin29@hotmail.com","program":"ECONOMIC","experience":1,"group":"", "visible": true},{"id":14,"created_at":1711482132232,"username":"Constance.Doyle59","role":"EMPLOYEE","email":"Constance_Doyle@hotmail.com","program":"DOMESTIC","experience":3,"group":"", "visible": true},{"id":15,"created_at":1711482132232,"username":"Chauncey_Lockman","role":"ADMIN","email":"Chauncey_Lockman@yahoo.com","program":"DOMESTIC","experience":5,"group":"", "visible": true},{"id":16,"created_at":1711482132232,"username":"Esther_Wuckert-Larson26","role":"EMPLOYEE","email":"Esther_Wuckert-Larson@gmail.com","program":"ECONOMIC","experience":0,"group":"", "visible": true},{"id":17,"created_at":1711482132232,"username":"Jewel.Kunde","role":"VOLUNTEER","email":"Jewel_Kunde29@gmail.com","program":"ECONOMIC","experience":5,"group":"", "visible": true},{"id":18,"created_at":1711482132232,"username":"Hildegard_Parker92","role":"ADMIN","email":"Hildegard_Parker74@yahoo.com","program":"ECONOMIC","experience":2,"group":"", "visible": true},{"id":19,"created_at":1711482132232,"username":"Jordane.Lakin2","role":"ADMIN","email":"Jordane_Lakin@hotmail.com","program":"COMMUNITY","experience":1,"group":"", "visible": true}] \ No newline at end of file From 97fd4703ba5ae92c04d125d4815a5fa48d73b5d7 Mon Sep 17 00:00:00 2001 From: Advik Arora Date: Wed, 3 Apr 2024 15:40:10 -0400 Subject: [PATCH 3/9] added graying out of rows that are hidden, and allowed for hide to be a toggle --- compass/components/Table/Index.tsx | 51 ++++++++++++++++++------------ 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/compass/components/Table/Index.tsx b/compass/components/Table/Index.tsx index c245143..2b02c16 100644 --- a/compass/components/Table/Index.tsx +++ b/compass/components/Table/Index.tsx @@ -43,17 +43,20 @@ export const Table = () => { }; const hideUser = (userId: number) => { - console.log(`Hiding user with ID: ${userId}`); // Confirm the ID being processed + console.log(`Toggling visibility for user with ID: ${userId}`); setData(currentData => { const newData = currentData.map(user => { - console.log(`Checking user with ID: ${user.id}`); // Confirm each user's ID being checked - return user.id === userId ? { ...user, visible: false } : 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); // Check the sorted data + console.log(newData); return newData; }); }; + const columns = [ columnHelper.display({ id: "options", @@ -114,22 +117,30 @@ export const Table = () => { ))} - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell, i) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - ))} + + {table.getRowModel().rows.map((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 ( + + {row.getVisibleCells().map((cell, i) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ); + })} From 2f44ff5d2d7e1b56edda21814773afe53fdd4ace Mon Sep 17 00:00:00 2001 From: "Andy Chan (12beesinatrenchcoat)" Date: Fri, 5 Apr 2024 10:44:12 -0400 Subject: [PATCH 4/9] Initial implementation of search This is a big one... --- compass/components/Table/Index.tsx | 67 +++++++++++++++++++++++- compass/components/Table/TableAction.tsx | 16 ++++-- compass/package-lock.json | 22 +++++++- compass/package.json | 2 +- 4 files changed, 100 insertions(+), 7 deletions(-) diff --git a/compass/components/Table/Index.tsx b/compass/components/Table/Index.tsx index d7db0d2..364e78e 100644 --- a/compass/components/Table/Index.tsx +++ b/compass/components/Table/Index.tsx @@ -3,16 +3,23 @@ import usersImport from "./users.json"; import { ColumnDef, + ColumnFiltersState, + GlobalFilterTableState, + Row, createColumnHelper, flexRender, getCoreRowModel, + getFilteredRowModel, + getSortedRowModel, + sortingFns, useReactTable, } from "@tanstack/react-table"; -import { useState } from "react"; +import { ChangeEvent, useState } from "react"; import { RowOptionMenu } from "./RowOptionMenu"; import { RowOpenAction } from "./RowOpenAction"; import { TableAction } from "./TableAction"; import { Bars2Icon, AtSymbolIcon, HashtagIcon, ArrowDownCircleIcon } from "@heroicons/react/24/solid"; +import { compareItems, rankItem } from "@tanstack/match-sorter-utils"; const usersExample = usersImport as unknown as User[]; @@ -27,6 +34,31 @@ type User = { group?: string; }; +const fuzzyFilter = (row: Row, 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 +} + +const fuzzySort = (rowA, rowB, columnId) => { + let dir = 0 + + // Only sort by rank if the column has ranking information + if (rowA.columnFiltersMeta[columnId]) { + dir = compareItems( + rowA.columnFiltersMeta[columnId]!, + rowB.columnFiltersMeta[columnId]! + ) + } + // Provide an alphanumeric fallback for when the item ranks are equal + return dir === 0 ? sortingFns.alphanumeric(rowA, rowB, columnId) : dir +} + export const Table = () => { const columnHelper = createColumnHelper(); @@ -39,6 +71,7 @@ export const Table = () => { columnHelper.accessor("username", { header: () => <> Username, cell: (info) => , + sortingFn: fuzzySort, }), columnHelper.accessor("role", { header: () => <> Role, @@ -55,17 +88,47 @@ export const Table = () => { ]; const [data, setData] = useState([...usersExample]); + // const [globalFilter, setGlobalFilter] = useState(); + + // Searching + const [query, setQuery] = useState(""); + const handleSearchChange = (e: ChangeEvent) => { + const target = e.target as HTMLInputElement; + setQuery(String(target.value)); + console.log(query); + + // setColumnFilters([ + // { + // id: "username", + // value: query + // } + // ]) + } + + // TODO: Filtering + // TODO: Sorting const table = useReactTable({ columns, data, + filterFns: { + fuzzy: fuzzyFilter + }, + state: { + globalFilter: query, + }, + onGlobalFilterChange: setQuery, + globalFilterFn: fuzzyFilter, getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getSortedRowModel: getSortedRowModel(), + // onColumnFiltersChange: setColumnFilters, }); return (
- +
diff --git a/compass/components/Table/TableAction.tsx b/compass/components/Table/TableAction.tsx index f2a72a0..47b3680 100644 --- a/compass/components/Table/TableAction.tsx +++ b/compass/components/Table/TableAction.tsx @@ -1,9 +1,16 @@ +/** The actions (Filter, Sort, Search) at the top of the table. */ import { MagnifyingGlassIcon } from "@heroicons/react/24/solid"; -import { useRef, useState } from "react"; +import { ChangeEventHandler, Dispatch, FunctionComponent, SetStateAction, useRef, useState } from "react"; -export const TableAction = () => { +type TableActionProps = { + query: string + handleChange: ChangeEventHandler +} + +export const TableAction: FunctionComponent = ({query, handleChange}) => { const searchInput = useRef(null); const [searchActive, setSearchActive] = useState(false); + const activateSearch = () => { setSearchActive(true); if (searchInput.current === null) { return; } @@ -30,7 +37,10 @@ export const TableAction = () => { className={"outline-none transition-all duration-300 " + (searchActive ? "w-48" : "w-0")} type="text" name="search" - placeholder="Type to search..." /> + placeholder="Type to search..." + value={query} + onChange={handleChange} + /> ); }; diff --git a/compass/package-lock.json b/compass/package-lock.json index 1c26973..f37a923 100644 --- a/compass/package-lock.json +++ b/compass/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@heroicons/react": "^2.1.1", + "@tanstack/match-sorter-utils": "^8.15.1", "@tanstack/react-table": "^8.15.0", "next": "13.5.6", "react": "^18", @@ -402,6 +403,21 @@ "tslib": "^2.4.0" } }, + "node_modules/@tanstack/match-sorter-utils": { + "version": "8.15.1", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.15.1.tgz", + "integrity": "sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw==", + "dependencies": { + "remove-accents": "0.5.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/react-table": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.15.0.tgz", @@ -3556,6 +3572,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -4395,4 +4416,3 @@ } } } - diff --git a/compass/package.json b/compass/package.json index 4036d60..10ba9d0 100644 --- a/compass/package.json +++ b/compass/package.json @@ -1,4 +1,3 @@ - { "name": "compass", "version": "0.1.0", @@ -11,6 +10,7 @@ }, "dependencies": { "@heroicons/react": "^2.1.1", + "@tanstack/match-sorter-utils": "^8.15.1", "@tanstack/react-table": "^8.15.0", "next": "13.5.6", "react": "^18", From ca9234afa129422aaebf346a893ede97083d4dcf Mon Sep 17 00:00:00 2001 From: "Andy Chan (12beesinatrenchcoat)" Date: Fri, 5 Apr 2024 12:23:57 -0400 Subject: [PATCH 5/9] Clean up search code --- compass/components/Table/Index.tsx | 33 ++---------------------- compass/components/Table/TableAction.tsx | 2 +- 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/compass/components/Table/Index.tsx b/compass/components/Table/Index.tsx index 364e78e..365efee 100644 --- a/compass/components/Table/Index.tsx +++ b/compass/components/Table/Index.tsx @@ -3,14 +3,11 @@ import usersImport from "./users.json"; import { ColumnDef, - ColumnFiltersState, - GlobalFilterTableState, Row, createColumnHelper, flexRender, getCoreRowModel, getFilteredRowModel, - getSortedRowModel, sortingFns, useReactTable, } from "@tanstack/react-table"; @@ -19,7 +16,7 @@ import { RowOptionMenu } from "./RowOptionMenu"; import { RowOpenAction } from "./RowOpenAction"; import { TableAction } from "./TableAction"; import { Bars2Icon, AtSymbolIcon, HashtagIcon, ArrowDownCircleIcon } from "@heroicons/react/24/solid"; -import { compareItems, rankItem } from "@tanstack/match-sorter-utils"; +import { rankItem } from "@tanstack/match-sorter-utils"; const usersExample = usersImport as unknown as User[]; @@ -34,6 +31,7 @@ type User = { group?: string; }; +// For search const fuzzyFilter = (row: Row, columnId: string, value: any, addMeta: (meta: any) => void) => { // Rank the item const itemRank = rankItem(row.getValue(columnId), value) @@ -45,21 +43,6 @@ const fuzzyFilter = (row: Row, columnId: string, value: any, addMeta: (meta return itemRank.passed } -const fuzzySort = (rowA, rowB, columnId) => { - let dir = 0 - - // Only sort by rank if the column has ranking information - if (rowA.columnFiltersMeta[columnId]) { - dir = compareItems( - rowA.columnFiltersMeta[columnId]!, - rowB.columnFiltersMeta[columnId]! - ) - } - // Provide an alphanumeric fallback for when the item ranks are equal - return dir === 0 ? sortingFns.alphanumeric(rowA, rowB, columnId) : dir -} - - export const Table = () => { const columnHelper = createColumnHelper(); @@ -71,7 +54,6 @@ export const Table = () => { columnHelper.accessor("username", { header: () => <> Username, cell: (info) => , - sortingFn: fuzzySort, }), columnHelper.accessor("role", { header: () => <> Role, @@ -88,21 +70,12 @@ export const Table = () => { ]; const [data, setData] = useState([...usersExample]); - // const [globalFilter, setGlobalFilter] = useState(); // Searching const [query, setQuery] = useState(""); const handleSearchChange = (e: ChangeEvent) => { const target = e.target as HTMLInputElement; setQuery(String(target.value)); - console.log(query); - - // setColumnFilters([ - // { - // id: "username", - // value: query - // } - // ]) } // TODO: Filtering @@ -121,8 +94,6 @@ export const Table = () => { globalFilterFn: fuzzyFilter, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), - getSortedRowModel: getSortedRowModel(), - // onColumnFiltersChange: setColumnFilters, }); return ( diff --git a/compass/components/Table/TableAction.tsx b/compass/components/Table/TableAction.tsx index 47b3680..e58e110 100644 --- a/compass/components/Table/TableAction.tsx +++ b/compass/components/Table/TableAction.tsx @@ -38,7 +38,7 @@ export const TableAction: FunctionComponent = ({query, handleC type="text" name="search" placeholder="Type to search..." - value={query} + value={query ?? ""} onChange={handleChange} /> From b188c783ce75eefc9a3e0aab51e0a4460ea5f38f Mon Sep 17 00:00:00 2001 From: Andy Chan Date: Fri, 12 Apr 2024 18:21:19 -0400 Subject: [PATCH 6/9] Move Table cell into its own component --- compass/components/Table/Index.tsx | 33 ++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/compass/components/Table/Index.tsx b/compass/components/Table/Index.tsx index 10f81ed..d498ed8 100644 --- a/compass/components/Table/Index.tsx +++ b/compass/components/Table/Index.tsx @@ -2,6 +2,7 @@ import usersImport from "./users.json"; import { + Cell, ColumnDef, Row, createColumnHelper, @@ -11,7 +12,7 @@ import { sortingFns, useReactTable, } from "@tanstack/react-table"; -import { ChangeEvent, useState, useEffect } from "react"; +import { ChangeEvent, useState, useEffect, FunctionComponent } from "react"; import { RowOptionMenu } from "./RowOptionMenu"; import { RowOpenAction } from "./RowOpenAction"; import { TableAction } from "./TableAction"; @@ -153,6 +154,7 @@ export const Table = () => { {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 ( @@ -161,16 +163,7 @@ export const Table = () => { key={row.id} > {row.getVisibleCells().map((cell, i) => ( - + ))} ); @@ -180,3 +173,21 @@ export const Table = () => { ) } + +type TableCellProps = { + cell: Cell; + //i: number; +} + +const TableCell: FunctionComponent = ({ cell }) => { + return ( + ) +} From d689e22dd81312f1eb28c38f70a2e99ae133076d Mon Sep 17 00:00:00 2001 From: Andy Chan Date: Sat, 13 Apr 2024 12:56:00 -0400 Subject: [PATCH 7/9] Add editing of table fields, significant refactoring This makes RowOpenAction obsolete with PrimaryTableCell. Dropdowns to be implemented at a later point. Lots of typing and further styling needed. --- compass/components/Table/Index.tsx | 56 +++++++++++-------- compass/components/Table/PrimaryTableCell.tsx | 22 ++++++++ compass/components/Table/RowOptionMenu.tsx | 2 +- compass/components/Table/TableCell.tsx | 17 ++++++ compass/components/page/Drawer.tsx | 2 +- 5 files changed, 73 insertions(+), 26 deletions(-) create mode 100644 compass/components/Table/PrimaryTableCell.tsx create mode 100644 compass/components/Table/TableCell.tsx diff --git a/compass/components/Table/Index.tsx b/compass/components/Table/Index.tsx index d498ed8..9c7329b 100644 --- a/compass/components/Table/Index.tsx +++ b/compass/components/Table/Index.tsx @@ -12,12 +12,14 @@ import { sortingFns, useReactTable, } from "@tanstack/react-table"; -import { ChangeEvent, useState, useEffect, FunctionComponent } from "react"; +import { ChangeEvent, useState, useEffect, FunctionComponent, useRef, ChangeEventHandler, Key } from "react"; import { RowOptionMenu } from "./RowOptionMenu"; import { RowOpenAction } from "./RowOpenAction"; import { TableAction } from "./TableAction"; import { Bars2Icon, AtSymbolIcon, HashtagIcon, ArrowDownCircleIcon } from "@heroicons/react/24/solid"; import { rankItem } from "@tanstack/match-sorter-utils"; +import { TableCell } from "./TableCell"; +import { PrimaryTableCell } from "./PrimaryTableCell"; const usersExample = usersImport as unknown as User[]; @@ -80,19 +82,19 @@ export const Table = () => { }), columnHelper.accessor("username", { header: () => <> Username, - cell: (info) => , + cell: PrimaryTableCell, }), columnHelper.accessor("role", { header: () => <> Role, - cell: (info) => info.renderValue(), + cell: TableCell, }), columnHelper.accessor("email", { header: () => <> Email, - cell: (info) => info.renderValue(), + cell: TableCell, }), columnHelper.accessor("program", { header: () => <> Program, - cell: (info) => info.renderValue(), + cell: TableCell, }), ]; @@ -105,6 +107,11 @@ export const Table = () => { setQuery(String(target.value)); } + const handleCellChange = (e: ChangeEvent, key: Key) => { + const target = e.target as HTMLInputElement; + console.log(key); + } + // TODO: Filtering // TODO: Sorting @@ -121,6 +128,21 @@ export const Table = () => { globalFilterFn: fuzzyFilter, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), + meta: { + updateData: (rowIndex: number, columnId: string, value: string) => { + setData(old => + old.map((row, index) => { + if (index === rowIndex) { + return { + ...old[rowIndex], + [columnId]: value, + }; + } + return row; + }) + ); + } + } }); return ( @@ -163,7 +185,11 @@ export const Table = () => { key={row.id} > {row.getVisibleCells().map((cell, i) => ( - + ))} ); @@ -173,21 +199,3 @@ export const Table = () => { ) } - -type TableCellProps = { - cell: Cell; - //i: number; -} - -const TableCell: FunctionComponent = ({ cell }) => { - return ( - ) -} diff --git a/compass/components/Table/PrimaryTableCell.tsx b/compass/components/Table/PrimaryTableCell.tsx new file mode 100644 index 0000000..47e87e0 --- /dev/null +++ b/compass/components/Table/PrimaryTableCell.tsx @@ -0,0 +1,22 @@ +/* An extension of TableCell.tsx that includes an "open" button and the drawer. +For cells in the "primary" (or first) column of the table. */ +import Drawer from "@/components/page/Drawer"; +import { TableCell } from "./TableCell"; +import { SetStateAction, useState } from "react"; + +export const PrimaryTableCell = ({ getValue, row, column, table }) => { + const [pageContent, setPageContent] = useState("") + + const handleDrawerContentChange = (newContent: SetStateAction) => { + setPageContent(newContent); + }; + + return ( +
+ + + {pageContent} + +
+ ); +}; diff --git a/compass/components/Table/RowOptionMenu.tsx b/compass/components/Table/RowOptionMenu.tsx index 58c5284..d383b20 100644 --- a/compass/components/Table/RowOptionMenu.tsx +++ b/compass/components/Table/RowOptionMenu.tsx @@ -16,7 +16,7 @@ export const RowOptionMenu = ( { onDelete, onHide } ) => { <>
*]:rounded" + (!menuOpen ? " invisible" : "")} + className={"justify-start border border-gray-200 shadow-lg flex flex-col absolute bg-white w-auto p-2 rounded [&>*]:rounded z-10" + (!menuOpen ? " invisible" : "")} > { /* handle open */ }} /> diff --git a/compass/components/Table/TableCell.tsx b/compass/components/Table/TableCell.tsx new file mode 100644 index 0000000..a84e40f --- /dev/null +++ b/compass/components/Table/TableCell.tsx @@ -0,0 +1,17 @@ +/* A lone table cell. Passed in for "cell" for a TanStack Table. */ +import { useState, useEffect } from "react"; + +export const TableCell = ({ getValue, row, column, table }) => { + const initialValue = getValue(); + const [value, setValue] = useState(initialValue); + + useEffect(() => { + setValue(initialValue); + }, [initialValue]); + + const onBlur = () => { + table.options.meta?.updateData(row.index, column.id, value); + }; + + return setValue(e.target.value)} onBlur={onBlur} />; +}; diff --git a/compass/components/page/Drawer.tsx b/compass/components/page/Drawer.tsx index 91118f3..a60c447 100644 --- a/compass/components/page/Drawer.tsx +++ b/compass/components/page/Drawer.tsx @@ -26,7 +26,7 @@ const Drawer: FunctionComponent = ({ title, children, onSave, edita setEditContent(event.target.value); }; - const drawerClassName = `fixed top-0 right-0 w-1/2 h-full bg-white transform ease-in-out duration-300 ${ + const drawerClassName = `fixed top-0 right-0 w-1/2 h-full bg-white transform ease-in-out duration-300 z-20 ${ isOpen ? "translate-x-0 shadow-xl" : "translate-x-full" }`; From ad25d5fe3fa33966712738a43b01f3b31dc8862a Mon Sep 17 00:00:00 2001 From: Andy Chan Date: Fri, 19 Apr 2024 16:36:16 -0400 Subject: [PATCH 8/9] TableCell styling changes i love `border` --- compass/components/Table/Index.tsx | 2 +- compass/components/Table/PrimaryTableCell.tsx | 4 ++-- compass/components/Table/TableCell.tsx | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/compass/components/Table/Index.tsx b/compass/components/Table/Index.tsx index 9c7329b..d27110f 100644 --- a/compass/components/Table/Index.tsx +++ b/compass/components/Table/Index.tsx @@ -186,7 +186,7 @@ export const Table = () => { > {row.getVisibleCells().map((cell, i) => (
diff --git a/compass/components/Table/PrimaryTableCell.tsx b/compass/components/Table/PrimaryTableCell.tsx index 47e87e0..a6e265f 100644 --- a/compass/components/Table/PrimaryTableCell.tsx +++ b/compass/components/Table/PrimaryTableCell.tsx @@ -12,9 +12,9 @@ export const PrimaryTableCell = ({ getValue, row, column, table }) => { }; return ( -
+
- + {pageContent}
diff --git a/compass/components/Table/TableCell.tsx b/compass/components/Table/TableCell.tsx index a84e40f..a6dfc02 100644 --- a/compass/components/Table/TableCell.tsx +++ b/compass/components/Table/TableCell.tsx @@ -12,6 +12,10 @@ export const TableCell = ({ getValue, row, column, table }) => { const onBlur = () => { table.options.meta?.updateData(row.index, column.id, value); }; +// focus:border focus:border-gray-200 + const className = "w-full p-3 bg-inherit rounded-md outline-none border border-transparent relative " + + "focus:shadow-md focus:border-gray-200 focus:bg-white focus:z-20 focus:p-4 focus:-m-1 " + + "focus:w-[calc(100%+0.5rem)]"; - return setValue(e.target.value)} onBlur={onBlur} />; + return setValue(e.target.value)} onBlur={onBlur} />; }; From 08313879050d20c385f704b707696c4b480cf6e2 Mon Sep 17 00:00:00 2001 From: Andy Chan Date: Fri, 19 Apr 2024 18:20:08 -0400 Subject: [PATCH 9/9] Implement UI of new row button Functionality is extremely janky. Does not work well. --- compass/components/Table/Index.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/compass/components/Table/Index.tsx b/compass/components/Table/Index.tsx index d27110f..5699fb1 100644 --- a/compass/components/Table/Index.tsx +++ b/compass/components/Table/Index.tsx @@ -16,7 +16,7 @@ import { ChangeEvent, useState, useEffect, FunctionComponent, useRef, ChangeEven import { RowOptionMenu } from "./RowOptionMenu"; import { RowOpenAction } from "./RowOpenAction"; import { TableAction } from "./TableAction"; -import { Bars2Icon, AtSymbolIcon, HashtagIcon, ArrowDownCircleIcon } from "@heroicons/react/24/solid"; +import { Bars2Icon, AtSymbolIcon, HashtagIcon, ArrowDownCircleIcon, PlusIcon } from "@heroicons/react/24/solid"; import { rankItem } from "@tanstack/match-sorter-utils"; import { TableCell } from "./TableCell"; import { PrimaryTableCell } from "./PrimaryTableCell"; @@ -100,6 +100,10 @@ export const Table = () => { const [data, setData] = useState([...usersExample]); + const addUser = () => { + setData([...data, {}]); + } + // Searching const [query, setQuery] = useState(""); const handleSearchChange = (e: ChangeEvent) => { @@ -195,7 +199,19 @@ export const Table = () => { ); })}
+ + + + + +
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} + + {flexRender(cell.column.columnDef.cell, cell.getContext())} +
- {flexRender(cell.column.columnDef.cell, cell.getContext())} - {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ + + New + +
) } +