From c21d12b40850a9ce297b63ab0820ca32e7583d03 Mon Sep 17 00:00:00 2001 From: Nick A Date: Sun, 27 Apr 2025 13:04:39 -0400 Subject: [PATCH] added tags filter --- compass/components/Table/ColumnHeader.tsx | 50 +++++++++++++++-------- compass/components/Table/ServiceTable.tsx | 21 +++++++++- compass/components/Table/Table.tsx | 27 +++++++++--- compass/components/Table/UserTable.tsx | 14 ++++++- compass/components/TagsInput/Index.tsx | 46 +++++++++++++++++---- compass/package-lock.json | 16 +++++++- compass/package.json | 1 + 7 files changed, 139 insertions(+), 36 deletions(-) diff --git a/compass/components/Table/ColumnHeader.tsx b/compass/components/Table/ColumnHeader.tsx index 5d8e307..8520297 100644 --- a/compass/components/Table/ColumnHeader.tsx +++ b/compass/components/Table/ColumnHeader.tsx @@ -7,6 +7,15 @@ import { FunnelIcon, XMarkIcon, } from "@heroicons/react/24/outline"; +import { Details } from "../Drawer/Drawer"; +import FilterDropdown, { FilterFn } from "./FilterDropdown"; +import DataPoint from "@/utils/models/DataPoint"; + +interface ColumnHeaderProps { + header: Header; + details: Details | undefined; + setFilterFn?: (field: string, filterFn: FilterFn) => void; +} function DropdownCheckIcon({ className }: { className?: string }) { return ( @@ -18,7 +27,11 @@ function DropdownCheckIcon({ className }: { className?: string }) { * Component for rendering the header of a table column, * as well as the dropdown menu for sorting and filtering. */ -export function ColumnHeader({ header }: { header: Header }) { +export function ColumnHeader({ + header, + details, + setFilterFn, +}: ColumnHeaderProps) { const { column } = header; const [dropdownType, setDropdownType] = useState<"menu" | "filter" | null>( @@ -68,23 +81,33 @@ export function ColumnHeader({ header }: { header: Header }) { } }, [sortDirection, column]); + if (!details) { + return
; + } + return (
{header.isPlaceholder ? null : (
setDropdownType((prev) => prev === null ? "menu" : null ) } > -
+
{flexRender( column.columnDef.header, header.getContext() @@ -166,20 +189,11 @@ export function ColumnHeader({ header }: { header: Header }) { ref={filterRef} className="absolute -top-2 left-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg z-10" > -
- Contains - { - column.setFilterValue(e.target.value); - }} - placeholder="Type a value…" - className="border border-gray-300 rounded p-1" - /> -
+
)}
diff --git a/compass/components/Table/ServiceTable.tsx b/compass/components/Table/ServiceTable.tsx index 237c3e2..e9d50be 100644 --- a/compass/components/Table/ServiceTable.tsx +++ b/compass/components/Table/ServiceTable.tsx @@ -1,11 +1,10 @@ import { - Bars2Icon, CheckCircleIcon, DocumentTextIcon, ListBulletIcon, UserIcon, } from "@heroicons/react/24/solid"; -import { Dispatch, SetStateAction, useState } from "react"; +import { Dispatch, SetStateAction, useMemo, useState } from "react"; import { ColumnDef, createColumnHelper } from "@tanstack/react-table"; import Table from "@/components/Table/Table"; import { RowOpenAction } from "@/components/Table/RowOpenAction"; @@ -13,6 +12,7 @@ import Service from "@/utils/models/Service"; import { Details } from "../Drawer/Drawer"; import { Tag } from "../TagsInput/Tag"; import User from "@/utils/models/User"; +import { FilterFn } from "./FilterDropdown"; type ServiceTableProps = { data: Service[]; @@ -31,6 +31,8 @@ export default function ServiceTable({ user, }: ServiceTableProps) { const columnHelper = createColumnHelper(); + const [requirementsFilterFn, setRequirementsFilterFn] = + useState("arrIncludesSome"); const [programPresets, setProgramPresets] = useState([ "DOMESTIC", @@ -151,6 +153,13 @@ export default function ServiceTable({
), + filterFn: (row, columnId, filterValue) => { + const rowValue = row.getValue(columnId); + if (Array.isArray(filterValue)) { + return filterValue.includes(rowValue); + } + return true; + }, }), columnHelper.accessor("requirements", { header: () => ( @@ -170,6 +179,7 @@ export default function ServiceTable({ )}
), + filterFn: requirementsFilterFn, }), columnHelper.accessor("summary", { header: () => ( @@ -188,11 +198,18 @@ export default function ServiceTable({ }), ]; + const setFilterFn = (field: string, filterFn: FilterFn) => { + if (field === "requirements") { + setRequirementsFilterFn(filterFn); + } + }; + return ( = { data: T[]; setData: Dispatch>; columns: ColumnDef[]; + setFilterFn?: (field: string, filterFn: FilterFn) => void; details: Details[]; createEndpoint: string; isAdmin?: boolean; @@ -74,6 +81,7 @@ export default function Table({ data, setData, columns, + setFilterFn, details, createEndpoint, isAdmin = false, @@ -182,7 +190,14 @@ export default function Table({ {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header, i) => ( - + d.key === header.column.id + )} + setFilterFn={setFilterFn} + key={header.id} + /> ))} ))} @@ -199,9 +214,11 @@ export default function Table({ {row.getVisibleCells().map((cell, i) => (
{flexRender( cell.column.columnDef.cell, diff --git a/compass/components/Table/UserTable.tsx b/compass/components/Table/UserTable.tsx index 3754d83..43798ec 100644 --- a/compass/components/Table/UserTable.tsx +++ b/compass/components/Table/UserTable.tsx @@ -10,6 +10,7 @@ import { RowOpenAction } from "@/components/Table/RowOpenAction"; import User from "@/utils/models/User"; import { Details } from "../Drawer/Drawer"; import { Tag } from "../TagsInput/Tag"; +import { FilterFn } from "./FilterDropdown"; type UserTableProps = { data: User[]; @@ -24,6 +25,8 @@ type UserTableProps = { */ export default function UserTable({ data, setData, user }: UserTableProps) { const columnHelper = createColumnHelper(); + const [programFilterFn, setProgramFilterFn] = + useState("arrIncludesSome"); const [rolePresets, setRolePresets] = useState([ "ADMIN", @@ -103,6 +106,7 @@ export default function UserTable({ data, setData, user }: UserTableProps) { ), + filterFn: "arrIncludesSome", }), columnHelper.accessor("email", { header: () => ( @@ -133,14 +137,22 @@ export default function UserTable({ data, setData, user }: UserTableProps) { )} ), + filterFn: programFilterFn, }), ]; + const setFilterFn = (field: string, filterFn: FilterFn) => { + if (field === "program") { + setProgramFilterFn(filterFn); + } + }; + return ( - + >; onTagsChange?: (tags: Set) => void; singleValue?: boolean; + cellSelectedPreset?: boolean; + filterState?: [FilterFn | null, Dispatch>]; } const TagsInput: React.FC = ({ @@ -18,9 +21,11 @@ const TagsInput: React.FC = ({ setPresetOptions, onTagsChange, singleValue = false, + cellSelectedPreset = false, + filterState, }) => { const [inputValue, setInputValue] = useState(""); - const [cellSelected, setCellSelected] = useState(false); + const [cellSelected, setCellSelected] = useState(cellSelectedPreset); // TODO: Add tags to the database and remove the presetValue and lowercasing const [tags, setTags] = useState>(new Set(presetValue)); @@ -31,7 +36,7 @@ const TagsInput: React.FC = ({ const dropdown = useRef(null); const handleClick = () => { - if (!cellSelected) { + if (!cellSelectedPreset) { setCellSelected(true); setTimeout(() => { window.addEventListener("click", handleOutsideClick); @@ -138,19 +143,42 @@ const TagsInput: React.FC = ({ } }; + const FilterSelect = () => { + const [filter, setFilter] = filterState ?? [null, null]; + return ( + filter != null && + setFilter != null && ( + + ) + ); + }; + return (
- {!cellSelected ? ( - + {!cellSelectedPreset ? ( + <> + + + ) : (
-
+
+ =16.8" } }, - "node_modules/@tanstack/table-core": { + "node_modules/@tanstack/react-table/node_modules/@tanstack/table-core": { "version": "8.21.2", "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.2.tgz", "integrity": "sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==", @@ -668,6 +669,19 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", diff --git a/compass/package.json b/compass/package.json index 9ac0308..cf9d20c 100644 --- a/compass/package.json +++ b/compass/package.json @@ -15,6 +15,7 @@ "@supabase/supabase-js": "^2.42.3", "@tanstack/match-sorter-utils": "^8.15.1", "@tanstack/react-table": "^8.15.0", + "@tanstack/table-core": "^8.21.3", "bufferutil": "^4.0.8", "next": "^13.5.8", "react": "^18",