Compare commits

..

No commits in common. "04c7fbe2747e9d822e1c30476c7f44bd818f950d" and "cb54c9829d39874ba4aabefebd9ef15087f7ffc1" have entirely different histories.

15 changed files with 1210 additions and 864 deletions

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import { PageLayout } from "@/components/PageLayout"; import { PageLayout } from "@/components/PageLayout";
import UserTable from "@/components/Table/UserTable"; import { Table } from "@/components/Table/Index";
import User from "@/utils/models/User"; import User from "@/utils/models/User";
import { createClient } from "@/utils/supabase/client"; import { createClient } from "@/utils/supabase/client";
@ -38,7 +38,7 @@ export default function Page() {
<div className="min-h-screen flex flex-col"> <div className="min-h-screen flex flex-col">
{/* icon + title */} {/* icon + title */}
<PageLayout title="Users" icon={<UsersIcon />}> <PageLayout title="Users" icon={<UsersIcon />}>
<UserTable data={users} setData={setUsers} /> <Table users={users} />
</PageLayout> </PageLayout>
</div> </div>
); );

View File

@ -1,8 +1,8 @@
"use client"; "use client";
import { PageLayout } from "@/components/PageLayout"; import { PageLayout } from "@/components/PageLayout";
import { ResourceTable } from "@/components/Table/ResourceIndex";
import Resource from "@/utils/models/Resource"; import Resource from "@/utils/models/Resource";
import ResourceTable from "@/components/Table/ResourceTable";
import { createClient } from "@/utils/supabase/client"; import { createClient } from "@/utils/supabase/client";
import { BookmarkIcon } from "@heroicons/react/24/solid"; import { BookmarkIcon } from "@heroicons/react/24/solid";
@ -27,7 +27,7 @@ export default function Page() {
); );
const resourcesAPI: Resource[] = await userListData.json(); const resourcesAPI: Resource[] = await userListData.json();
setResources(resourcesAPI); setResources(resourcesAPI);
} }
@ -38,7 +38,7 @@ export default function Page() {
<div className="min-h-screen flex flex-col"> <div className="min-h-screen flex flex-col">
{/* icon + title */} {/* icon + title */}
<PageLayout title="Resources" icon={<BookmarkIcon />}> <PageLayout title="Resources" icon={<BookmarkIcon />}>
<ResourceTable data={resources} setData={setResources} /> <ResourceTable users={resources} />
</PageLayout> </PageLayout>
</div> </div>
); );

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import { PageLayout } from "@/components/PageLayout"; import { PageLayout } from "@/components/PageLayout";
import ServiceTable from "@/components/Table/ServiceTable"; import { ServiceTable } from "@/components/Table/ServiceIndex";
import Service from "@/utils/models/Service"; import Service from "@/utils/models/Service";
import { createClient } from "@/utils/supabase/client"; import { createClient } from "@/utils/supabase/client";
@ -9,7 +9,7 @@ import { ClipboardIcon } from "@heroicons/react/24/solid";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
export default function Page() { export default function Page() {
const [services, setServices] = useState<Service[]>([]); const [services, setUsers] = useState<Service[]>([]);
useEffect(() => { useEffect(() => {
async function getServices() { async function getServices() {
@ -27,7 +27,7 @@ export default function Page() {
); );
const servicesAPI: Service[] = await serviceListData.json(); const servicesAPI: Service[] = await serviceListData.json();
setServices(servicesAPI); setUsers(servicesAPI);
} }
getServices(); getServices();
@ -37,7 +37,7 @@ export default function Page() {
<div className="min-h-screen flex flex-col"> <div className="min-h-screen flex flex-col">
{/* icon + title */} {/* icon + title */}
<PageLayout title="Services" icon={<ClipboardIcon />}> <PageLayout title="Services" icon={<ClipboardIcon />}>
<ServiceTable data={services} setData={setServices} /> <ServiceTable users={services} />
</PageLayout> </PageLayout>
</div> </div>
); );

View File

@ -1,257 +1,247 @@
import { Dispatch, FunctionComponent, ReactNode, SetStateAction } from "react"; import { FunctionComponent, ReactNode } from "react";
import React, { useState } from "react"; import React, { useState } from "react";
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/solid"; import { ChevronDoubleLeftIcon } from "@heroicons/react/24/solid";
import { import {
StarIcon as SolidStarIcon, StarIcon as SolidStarIcon,
EnvelopeIcon, EnvelopeIcon,
UserIcon, UserIcon,
} from "@heroicons/react/24/solid"; } from "@heroicons/react/24/solid";
import { import {
ArrowsPointingOutIcon, ArrowsPointingOutIcon,
ArrowsPointingInIcon, ArrowsPointingInIcon,
StarIcon as OutlineStarIcon, StarIcon as OutlineStarIcon,
ListBulletIcon, ListBulletIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import TagsInput from "../TagsInput/Index"; import TagsInput from "../TagsInput/Index";
type DrawerProps = { type DrawerProps = {
title: string; title: string;
children: ReactNode; children: ReactNode;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void; onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
type?: "button" | "submit" | "reset"; // specify possible values for type type?: "button" | "submit" | "reset"; // specify possible values for type
disabled?: boolean; disabled?: boolean;
editableContent?: any; editableContent?: any;
onSave?: (content: any) => void; onSave?: (content: any) => void;
rowContent?: any; rowContent?: any;
setData: Dispatch<SetStateAction<any>>; onRowUpdate?: (content: any) => void;
}; };
interface EditContent { interface EditContent {
content: string; content: string;
isEditing: boolean; isEditing: boolean;
} }
const Drawer: FunctionComponent<DrawerProps> = ({ const Drawer: FunctionComponent<DrawerProps> = ({
title, title,
children, children,
onSave, onSave,
editableContent, editableContent,
rowContent, rowContent,
setData, onRowUpdate,
}) => { }) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isFull, setIsFull] = useState(false); const [isFull, setIsFull] = useState(false);
const [isFavorite, setIsFavorite] = useState(false); const [isFavorite, setIsFavorite] = useState(false);
const [tempRowContent, setTempRowContent] = useState(rowContent); const [tempRowContent, setTempRowContent] = useState(rowContent);
const onRowUpdate = (updatedRow: any) => { const handleTempRowContentChange = (e) => {
setData((prevData: any) => ( const { name, value } = e.target;
prevData.map((row: any) => ( console.log(name);
row.id === updatedRow.id console.log(value);
? updatedRow setTempRowContent((prevContent) => ({
: row ...prevContent,
)) [name]: value,
)) }));
}; };
const handleTempRowContentChange = (e) => { const handleEnterPress = (e) => {
const { name, value } = e.target; if (e.key === "Enter") {
console.log(name); e.preventDefault();
console.log(value); // Update the rowContent with the temporaryRowContent
setTempRowContent((prevContent) => ({ if (onRowUpdate) {
...prevContent, onRowUpdate(tempRowContent);
[name]: value, }
})); }
}; };
const handleEnterPress = (e) => { const toggleDrawer = () => {
if (e.key === "Enter") { setIsOpen(!isOpen);
e.preventDefault(); if (isFull) {
// Update the rowContent with the temporaryRowContent setIsFull(!isFull);
if (onRowUpdate) { }
onRowUpdate(tempRowContent); };
}
} const toggleDrawerFullScreen = () => setIsFull(!isFull);
};
const toggleFavorite = () => setIsFavorite(!isFavorite);
const toggleDrawer = () => {
setIsOpen(!isOpen); const drawerClassName = `fixed top-0 right-0 w-1/2 h-full bg-white transform ease-in-out duration-300 z-20 ${
if (isFull) { isOpen ? "translate-x-0 shadow-xl" : "translate-x-full"
setIsFull(!isFull); } ${isFull ? "w-full" : "w-1/2"}`;
}
}; const iconComponent = isFull ? (
<ArrowsPointingInIcon className="h-5 w-5" />
const toggleDrawerFullScreen = () => setIsFull(!isFull); ) : (
<ArrowsPointingOutIcon className="h-5 w-5" />
const toggleFavorite = () => setIsFavorite(!isFavorite); );
const drawerClassName = `fixed top-0 right-0 w-1/2 h-full bg-white transform ease-in-out duration-300 z-20 ${ const favoriteIcon = isFavorite ? (
isOpen ? "translate-x-0 shadow-xl" : "translate-x-full" <SolidStarIcon className="h-5 w-5" />
} ${isFull ? "w-full" : "w-1/2"}`; ) : (
<OutlineStarIcon className="h-5 w-5" />
const iconComponent = isFull ? ( );
<ArrowsPointingInIcon className="h-5 w-5" />
) : ( const [presetOptions, setPresetOptions] = useState([
<ArrowsPointingOutIcon className="h-5 w-5" /> "administrator",
); "volunteer",
"employee",
const favoriteIcon = isFavorite ? ( ]);
<SolidStarIcon className="h-5 w-5" /> const [rolePresetOptions, setRolePresetOptions] = useState([
) : ( "domestic",
<OutlineStarIcon className="h-5 w-5" /> "community",
); "economic",
]);
const [presetOptions, setPresetOptions] = useState([ const [tagColors, setTagColors] = useState(new Map());
"administrator",
"volunteer", const getTagColor = (tag: string) => {
"employee", if (!tagColors.has(tag)) {
]); const colors = [
const [rolePresetOptions, setRolePresetOptions] = useState([ "bg-cyan-100",
"domestic", "bg-blue-100",
"community", "bg-green-100",
"economic", "bg-yellow-100",
]); "bg-purple-100",
const [tagColors, setTagColors] = useState(new Map()); ];
const randomColor =
const getTagColor = (tag: string) => { colors[Math.floor(Math.random() * colors.length)];
if (!tagColors.has(tag)) { setTagColors(new Map(tagColors).set(tag, randomColor));
const colors = [ }
"bg-cyan-100", return tagColors.get(tag);
"bg-blue-100", };
"bg-green-100",
"bg-yellow-100", return (
"bg-purple-100", <div>
]; <button
const randomColor = className={
colors[Math.floor(Math.random() * colors.length)]; "ml-2 text-xs uppercase opacity-0 group-hover:opacity-100 text-gray-500 font-medium border border-gray-200 bg-white shadow hover:bg-gray-50 p-2 rounded-md"
setTagColors(new Map(tagColors).set(tag, randomColor)); }
} onClick={toggleDrawer}
return tagColors.get(tag); >
}; Open
</button>
return ( <div className={drawerClassName}></div>
<div> <div className={drawerClassName}>
<button <div className="flex items-center justify-between p-4">
className={ <div className="flex flex-row items-center justify-between space-x-2">
"ml-2 text-xs uppercase opacity-0 group-hover:opacity-100 text-gray-500 font-medium border border-gray-200 bg-white shadow hover:bg-gray-50 p-2 rounded-md" <span className="h-5 text-purple-200 w-5">
} <UserIcon />
onClick={toggleDrawer} </span>
> <h2 className="text-lg text-gray-800 font-semibold">
Open {rowContent.username}
</button> </h2>
<div className={drawerClassName}></div> </div>
<div className={drawerClassName}> <div>
<div className="flex items-center justify-between p-4"> <button
<div className="flex flex-row items-center justify-between space-x-2"> onClick={toggleFavorite}
<span className="h-5 text-purple-200 w-5"> className="py-2 text-gray-500 hover:text-gray-800 mr-2"
<UserIcon /> >
</span> {favoriteIcon}
<h2 className="text-lg text-gray-800 font-semibold"> </button>
{rowContent.username} <button
</h2> onClick={toggleDrawerFullScreen}
</div> className="py-2 text-gray-500 hover:text-gray-800 mr-2"
<div> >
<button {iconComponent}
onClick={toggleFavorite} </button>
className="py-2 text-gray-500 hover:text-gray-800 mr-2" <button
> onClick={toggleDrawer}
{favoriteIcon} className="py-2 text-gray-500 hover:text-gray-800"
</button> >
<button <ChevronDoubleLeftIcon className="h-5 w-5" />
onClick={toggleDrawerFullScreen} </button>
className="py-2 text-gray-500 hover:text-gray-800 mr-2" </div>
> </div>
{iconComponent} <div className="p-4">
</button> <table className="p-4">
<button <tbody className="items-center">
onClick={toggleDrawer} <tr className="w-full text-xs items-center flex flex-row justify-between">
className="py-2 text-gray-500 hover:text-gray-800" <div className="flex flex-row space-x-2 text-gray-500 items-center">
> <td>
<ChevronDoubleLeftIcon className="h-5 w-5" /> <UserIcon className="h-4 w-4" />
</button> </td>
</div> <td className="w-32">Username</td>
</div> </div>
<div className="p-4"> <td className="w-3/4 w-3/4 p-2 pl-0">
<table className="p-4"> <input
<tbody className="items-center"> type="text"
<tr className="w-full text-xs items-center flex flex-row justify-between"> name="username"
<div className="flex flex-row space-x-2 text-gray-500 items-center"> value={tempRowContent.username}
<td> onChange={handleTempRowContentChange}
<UserIcon className="h-4 w-4" /> onKeyDown={handleEnterPress}
</td> className="ml-2 w-full p-1 focus:outline-gray-200 hover:bg-gray-50"
<td className="w-32">Username</td> />
</div> </td>
<td className="w-3/4 w-3/4 p-2 pl-0"> </tr>
<input <tr className="w-full text-xs items-center flex flex-row justify-between">
type="text" <div className="flex flex-row space-x-2 text-gray-500 items-center">
name="username" <td>
value={tempRowContent.username} <ListBulletIcon className="h-4 w-4" />
onChange={handleTempRowContentChange} </td>
onKeyDown={handleEnterPress} <td className="w-32">Role</td>
className="ml-2 w-full p-1 focus:outline-gray-200 hover:bg-gray-50" </div>
/> <td className="w-3/4 hover:bg-gray-50">
</td> <TagsInput
</tr> presetValue={tempRowContent.role}
<tr className="w-full text-xs items-center flex flex-row justify-between"> presetOptions={presetOptions}
<div className="flex flex-row space-x-2 text-gray-500 items-center"> setPresetOptions={setPresetOptions}
<td> getTagColor={getTagColor}
<ListBulletIcon className="h-4 w-4" /> setTagColors={setTagColors}
</td> />
<td className="w-32">Role</td> </td>
</div> </tr>
<td className="w-3/4 hover:bg-gray-50"> <tr className="w-full text-xs items-center flex flex-row justify-between">
<TagsInput <div className="flex flex-row space-x-2 text-gray-500 items-center">
presetValue={tempRowContent.role} <td>
presetOptions={presetOptions} <EnvelopeIcon className="h-4 w-4" />
setPresetOptions={setPresetOptions} </td>
getTagColor={getTagColor} <td className="w-32">Email</td>
setTagColors={setTagColors} </div>
/> <td className="w-3/4 p-2 pl-0">
</td> <input
</tr> type="text"
<tr className="w-full text-xs items-center flex flex-row justify-between"> name="email"
<div className="flex flex-row space-x-2 text-gray-500 items-center"> value={tempRowContent.email}
<td> onChange={handleTempRowContentChange}
<EnvelopeIcon className="h-4 w-4" /> onKeyDown={handleEnterPress}
</td> className="ml-2 w-80 p-1 font-normal hover:text-gray-400 focus:outline-gray-200 hover:bg-gray-50 underline text-gray-500"
<td className="w-32">Email</td> />
</div> </td>
<td className="w-3/4 p-2 pl-0"> </tr>
<input <tr className="w-full text-xs items-center flex flex-row justify-between">
type="text" <div className="flex flex-row space-x-2 text-gray-500 items-center">
name="email" <td>
value={tempRowContent.email} <ListBulletIcon className="h-4 w-4" />
onChange={handleTempRowContentChange} </td>
onKeyDown={handleEnterPress} <td className="w-32">Type of Program</td>
className="ml-2 w-80 p-1 font-normal hover:text-gray-400 focus:outline-gray-200 hover:bg-gray-50 underline text-gray-500" </div>
/> <td className="w-3/4 hover:bg-gray-50">
</td> {/* {rowContent.program} */}
</tr> <TagsInput
<tr className="w-full text-xs items-center flex flex-row justify-between"> presetValue={tempRowContent.program}
<div className="flex flex-row space-x-2 text-gray-500 items-center"> presetOptions={rolePresetOptions}
<td> setPresetOptions={setRolePresetOptions}
<ListBulletIcon className="h-4 w-4" /> getTagColor={getTagColor}
</td> setTagColors={setTagColors}
<td className="w-32">Type of Program</td> />
</div> </td>
<td className="w-3/4 hover:bg-gray-50"> </tr>
{/* {rowContent.program} */} </tbody>
<TagsInput </table>
presetValue={tempRowContent.program} <br />
presetOptions={rolePresetOptions} </div>
setPresetOptions={setRolePresetOptions} </div>
getTagColor={getTagColor} </div>
setTagColors={setTagColors} );
/> };
</td>
</tr> export default Drawer;
</tbody>
</table>
<br />
</div>
</div>
</div>
);
};
export default Drawer;

View File

@ -0,0 +1,306 @@
// 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>
);
};

View File

@ -1,224 +1,304 @@
import { // for showcasing to compass
Row,
ColumnDef, import users from "./users.json";
useReactTable, import {
getCoreRowModel, Cell,
flexRender, ColumnDef,
createColumnHelper Row,
} from "@tanstack/react-table"; createColumnHelper,
import { flexRender,
ChangeEvent, getCoreRowModel,
useState, getFilteredRowModel,
useEffect, sortingFns,
Key, useReactTable,
Dispatch, } from "@tanstack/react-table";
SetStateAction import {
} from "react"; ChangeEvent,
import { TableAction } from "./TableAction"; useState,
import { PlusIcon } from "@heroicons/react/24/solid"; useEffect,
import { rankItem } from "@tanstack/match-sorter-utils"; FunctionComponent,
import { RowOptionMenu } from "./RowOptionMenu"; useRef,
import DataPoint from "@/utils/models/DataPoint"; ChangeEventHandler,
Key,
type TableProps<T extends DataPoint> = { } from "react";
data: T[], import { RowOptionMenu } from "./RowOptionMenu";
setData: Dispatch<SetStateAction<T[]>>, import { RowOpenAction } from "./RowOpenAction";
columns: ColumnDef<T, any>[] import { TableAction } from "./TableAction";
}; import {
AtSymbolIcon,
/** Fuzzy search function */ Bars2Icon,
const fuzzyFilter = ( ArrowDownCircleIcon,
row: Row<any>, PlusIcon,
columnId: string, } from "@heroicons/react/24/solid";
value: any, import TagsInput from "../TagsInput/Index";
addMeta: (meta: any) => void import { rankItem } from "@tanstack/match-sorter-utils";
) => { import Resource from "@/utils/models/Resource";
// Rank the item
const itemRank = rankItem(row.getValue(columnId), value); // For search
const fuzzyFilter = (
// Store the ranking info row: Row<any>,
addMeta(itemRank); columnId: string,
value: any,
// Return if the item should be filtered in/out addMeta: (meta: any) => void
return itemRank.passed; ) => {
}; // Rank the item
const itemRank = rankItem(row.getValue(columnId), value);
/**
* General componenet that holds shared functionality for any data table component // Store the ranking info
* @param props.data Stateful list of data to be held in the table addMeta(itemRank);
* @param props.setData State setter for the list of data
* @param props.columns Column definitions made with Tanstack columnHelper // Return if the item should be filtered in/out
*/ return itemRank.passed;
export default function Table<T extends DataPoint>({ data, setData, columns }: TableProps<T>) { };
const columnHelper = createColumnHelper<T>();
// TODO: Rename everything to resources
/** Sorting function based on visibility */ export const ResourceTable = ({ users }: { users: Resource[] }) => {
const visibilitySort = (a: T, b: T) => ( const columnHelper = createColumnHelper<Resource>();
a.visible === b.visible
? 0 useEffect(() => {
: a.visible ? -1 : 1 const sortedUsers = [...users].sort((a, b) =>
) a.visible === b.visible ? 0 : a.visible ? -1 : 1
);
// Sort data on load setData(sortedUsers);
useEffect(() => { }, [users]);
setData(prevData => prevData.sort(visibilitySort))
}, [setData]); const deleteUser = (userId: number) => {
console.log(data);
// Data manipulation methods setData((currentData) =>
// TODO: Connect data manipulation methods to the database (deleteData, hideData, addData) currentData.filter((user) => user.id !== userId)
const deleteData = (dataId: number) => { );
console.log(data); };
setData((currentData) =>
currentData.filter((data) => data.id !== dataId) const hideUser = (userId: number) => {
); console.log(`Toggling visibility for user with ID: ${userId}`);
}; setData((currentData) => {
const newData = currentData
const hideData = (dataId: number) => { .map((user) => {
console.log(`Toggling visibility for data with ID: ${dataId}`); if (user.id === userId) {
setData(currentData => { return { ...user, visible: !user.visible };
const newData = currentData }
.map(data => ( return user;
data.id === dataId })
? { ...data, visible: !data.visible } .sort((a, b) =>
: data a.visible === b.visible ? 0 : a.visible ? -1 : 1
)) );
.sort(visibilitySort);
console.log(newData);
console.log(newData); return newData;
return newData; });
}); };
}; const [presetOptions, setPresetOptions] = useState([
"administrator",
const addData = () => { "volunteer",
setData([...data]); "employee",
}; ]);
const [tagColors, setTagColors] = useState(new Map());
// Add data manipulation options to the first column
columns.unshift( const getTagColor = (tag: string) => {
columnHelper.display({ if (!tagColors.has(tag)) {
id: "options", const colors = [
cell: (props) => ( "bg-cyan-100",
<RowOptionMenu "bg-blue-100",
onDelete={() => deleteData(props.row.original.id)} "bg-green-100",
onHide={() => hideData(props.row.original.id)} "bg-yellow-100",
/> "bg-purple-100",
), ];
}) const randomColor =
) colors[Math.floor(Math.random() * colors.length)];
setTagColors(new Map(tagColors).set(tag, randomColor));
// Searching }
const [query, setQuery] = useState(""); return tagColors.get(tag);
const handleSearchChange = (e: ChangeEvent) => { };
const target = e.target as HTMLInputElement;
setQuery(String(target.value)); const columns = [
}; columnHelper.display({
id: "options",
const handleCellChange = (e: ChangeEvent, key: Key) => { cell: (props) => (
const target = e.target as HTMLInputElement; <RowOptionMenu
console.log(key); onDelete={() => {}}
}; onHide={() => hideUser(props.row.original.id)}
/>
// TODO: Filtering ),
}),
// TODO: Sorting columnHelper.accessor("name", {
header: () => (
// Define Tanstack table <>
const table = useReactTable({ <Bars2Icon className="inline align-top h-4" /> Name
columns, </>
data, ),
filterFns: { cell: (info) => (
fuzzy: fuzzyFilter, <RowOpenAction
}, title={info.getValue()}
state: { rowData={info.row.original}
globalFilter: query, onRowUpdate={handleRowUpdate}
}, />
onGlobalFilterChange: setQuery, ),
globalFilterFn: fuzzyFilter, }),
getCoreRowModel: getCoreRowModel(), columnHelper.accessor("link", {
}); header: () => (
<>
const handleRowData = (row: any) => { <Bars2Icon className="inline align-top h-4" /> Link
const rowData: any = {}; </>
row.cells.forEach((cell: any) => { ),
rowData[cell.column.id] = cell.value; cell: (info) => (
}); <a
// Use rowData object containing data from all columns for the current row href={info.getValue()}
console.log(rowData); target={"_blank"}
return rowData; className="ml-2 text-gray-500 underline hover:text-gray-400"
}; >
{info.getValue()}
return ( </a>
<div className="flex flex-col"> ),
<div className="flex flex-row justify-end"> }),
<TableAction query={query} handleChange={handleSearchChange} /> columnHelper.accessor("program", {
</div> header: () => (
<table className="w-full text-xs text-left rtl:text-right"> <>
<thead className="text-xs text-gray-500 capitalize"> <Bars2Icon className="inline align-top h-4" /> Program
{table.getHeaderGroups().map((headerGroup) => ( </>
<tr key={headerGroup.id}> ),
{headerGroup.headers.map((header, i) => ( cell: (info) => <TagsInput presetValue={info.getValue()} />,
<th }),
scope="col"
className={ columnHelper.accessor("summary", {
"p-2 border-gray-200 border-y font-medium " + header: () => (
(1 < i && i < columns.length - 1 <>
? "border-x" <Bars2Icon className="inline align-top h-4" /> Summary
: "") </>
} ),
key={header.id} cell: (info) => (
> <span className="ml-2 text-gray-500">{info.getValue()}</span>
{header.isPlaceholder ),
? null }),
: flexRender( ];
header.column.columnDef.header,
header.getContext() const [data, setData] = useState<Resource[]>([...users]);
)}
</th> const addUser = () => {
))} setData([...data]);
</tr> };
))}
</thead> // Searching
<tbody> const [query, setQuery] = useState("");
{table.getRowModel().rows.map((row) => { const handleSearchChange = (e: ChangeEvent) => {
// Individual row const target = e.target as HTMLInputElement;
const isDataVisible = row.original.visible; setQuery(String(target.value));
const rowClassNames = `text-gray-800 border-y lowercase hover:bg-gray-50 ${ };
!isDataVisible ? "bg-gray-200 text-gray-500" : ""
}`; const handleCellChange = (e: ChangeEvent, key: Key) => {
return ( const target = e.target as HTMLInputElement;
<tr className={rowClassNames} key={row.id}> console.log(key);
{row.getVisibleCells().map((cell, i) => ( };
<td
key={cell.id} // TODO: Filtering
className={
"[&:nth-child(n+3)]:border-x relative first:text-left first:px-0 last:border-none" // TODO: Sorting
}
> // added this fn for editing rows
{flexRender( const handleRowUpdate = (updatedRow: Resource) => {
cell.column.columnDef.cell, const dataIndex = data.findIndex((row) => row.id === updatedRow.id);
cell.getContext() if (dataIndex !== -1) {
)} const updatedData = [...data];
</td> updatedData[dataIndex] = updatedRow;
))} setData(updatedData);
</tr> }
); };
})}
</tbody> const table = useReactTable({
<tfoot> columns,
<tr> data,
<td filterFns: {
className="p-3 border-y border-gray-200 text-gray-600 hover:bg-gray-50" fuzzy: fuzzyFilter,
colSpan={100} },
onClick={addData} state: {
> globalFilter: query,
<span className="flex ml-1 text-gray-500"> },
<PlusIcon className="inline h-4 mr-1" /> onGlobalFilterChange: setQuery,
New globalFilterFn: fuzzyFilter,
</span> getCoreRowModel: getCoreRowModel(),
</td> });
</tr>
</tfoot> const handleRowData = (row: any) => {
</table> const rowData: any = {};
</div> 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>
);
};

View File

@ -1,89 +0,0 @@
import { Bars2Icon } from "@heroicons/react/24/solid";
import { Dispatch, SetStateAction, useState } from "react";
import useTagsHandler from "@/components/TagsInput/TagsHandler";
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
import { RowOpenAction } from "@/components/Table/RowOpenAction";
import Table from "@/components/Table/Table";
import TagsInput from "@/components/TagsInput/Index";
import Resource from "@/utils/models/Resource";
type ResourceTableProps = {
data: Resource[],
setData: Dispatch<SetStateAction<Resource[]>>
}
/**
* Table componenet used for displaying resources
* @param props.data Stateful list of resources to be displayed by the table
* @param props.setData State setter for the list of resources
*/
export default function ResourceTable({ data, setData }: ResourceTableProps ) {
const columnHelper = createColumnHelper<Resource>();
// Set up tag handling
const programProps = useTagsHandler([
"community",
"domestic",
"economic",
])
// Define Tanstack columns
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}
setData={setData}
/>
),
}),
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()}
{...programProps}
/>
),
}),
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}/>
}

View File

@ -1,34 +1,28 @@
import Drawer from "@/components/Drawer/Drawer"; import Drawer from "@/components/Drawer/Drawer";
import DataPoint from "@/utils/models/DataPoint"; import { ChangeEvent, useState } from "react";
import { Dispatch, SetStateAction, useState } from "react";
export const RowOpenAction = ({ title, rowData, onRowUpdate }) => {
type RowOpenActionProps<T extends DataPoint> = { const [pageContent, setPageContent] = useState("");
title: string,
rowData: T, const handleDrawerContentChange = (newContent) => {
setData: Dispatch<SetStateAction<T[]>> setPageContent(newContent);
} };
export function RowOpenAction<T extends DataPoint>({ title, rowData, setData }: RowOpenActionProps<T>) { return (
const [pageContent, setPageContent] = useState(""); <div className="font-semibold group flex flex-row items-center justify-between pr-2">
{title}
const handleDrawerContentChange = (newContent: string) => { <span>
setPageContent(newContent); {/* Added OnRowUpdate to drawer */}
}; <Drawer
title="My Drawer Title"
return ( editableContent={pageContent}
<div className="font-semibold group flex flex-row items-center justify-between pr-2"> rowContent={rowData}
{title} onSave={handleDrawerContentChange}
<span> onRowUpdate={onRowUpdate}
<Drawer >
title="My Drawer Title" {pageContent}
editableContent={pageContent} </Drawer>
rowContent={rowData} </span>
onSave={handleDrawerContentChange} </div>
setData={setData} );
> };
{pageContent}
</Drawer>
</span>
</div>
);
};

View File

@ -0,0 +1,312 @@
// 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 Service from "@/utils/models/Service";
// 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 service
export const ServiceTable = ({ users }: { users: Service[] }) => {
const columnHelper = createColumnHelper<Service>();
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={() => {}}
onHide={() => hideUser(props.row.original.id)}
/>
),
}),
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("status", {
header: () => (
<>
<Bars2Icon className="inline align-top h-4" /> Status
</>
),
cell: (info) => (
<span className="ml-2 text-gray-500">{info.getValue()}</span>
),
}),
columnHelper.accessor("program", {
header: () => (
<>
<Bars2Icon className="inline align-top h-4" /> Program
</>
),
cell: (info) => <TagsInput presetValue={info.getValue()} />,
}),
columnHelper.accessor("requirements", {
header: () => (
<>
<Bars2Icon className="inline align-top h-4" /> Requirements
</>
),
cell: (info) => (
<TagsInput
presetValue={
info.getValue()[0] !== "" ? info.getValue() : ["N/A"]
}
/>
),
}),
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<Service[]>([...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: Service) => {
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>
);
};

View File

@ -1,108 +0,0 @@
import { Bars2Icon } from "@heroicons/react/24/solid";
import { Dispatch, SetStateAction } 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 Service from "@/utils/models/Service";
type ServiceTableProps = {
data: Service[],
setData: Dispatch<SetStateAction<Service[]>>
}
/**
* Table componenet used for displaying services
* @param props.data Stateful list of services to be displayed by the table
* @param props.setData State setter for the list of services
*/
export default function ServiceTable({ data, setData }: ServiceTableProps ) {
const columnHelper = createColumnHelper<Service>();
// Set up tag handling
const programProps = useTagsHandler([
"community",
"domestic",
"economic",
])
// TODO: Dynamically or statically get full list of preset requirement tag options
const requirementProps = useTagsHandler([
'anonymous',
'confidential',
'referral required',
'safety assessment',
'intake required',
'income eligibility',
'initial assessment',
])
// Define Tanstack columns
const columns: ColumnDef<Service, any>[] = [
columnHelper.accessor("name", {
header: () => (
<>
<Bars2Icon className="inline align-top h-4" /> Name
</>
),
cell: (info) => (
<RowOpenAction
title={info.getValue()}
rowData={info.row.original}
setData={setData}
/>
),
}),
columnHelper.accessor("status", {
header: () => (
<>
<Bars2Icon className="inline align-top h-4" /> Status
</>
),
cell: (info) => (
<span className="ml-2 text-gray-500">{info.getValue()}</span>
),
}),
columnHelper.accessor("program", {
header: () => (
<>
<Bars2Icon className="inline align-top h-4" /> Program
</>
),
cell: (info) => (
<TagsInput
presetValue={info.getValue()}
{...programProps}
/>
),
}),
columnHelper.accessor("requirements", {
header: () => (
<>
<Bars2Icon className="inline align-top h-4" /> Requirements
</>
),
cell: (info) => (
// TODO: Setup different tag handler for requirements
<TagsInput
presetValue={info.getValue()[0] !== "" ? info.getValue() : ["N/A"]}
{...requirementProps}
/>
),
}),
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} />
};

View File

@ -1,95 +0,0 @@
import { ArrowDownCircleIcon, AtSymbolIcon, Bars2Icon } from "@heroicons/react/24/solid";
import { Dispatch, SetStateAction } 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 User from "@/utils/models/User";
type UserTableProps = {
data: User[],
setData: Dispatch<SetStateAction<User[]>>
}
/**
* Table componenet used for displaying users
* @param props.data Stateful list of users to be displayed by the table
* @param props.setData State setter for the list of users
*/
export default function UserTable({ data, setData }: UserTableProps ) {
const columnHelper = createColumnHelper<User>();
// Set up tag handling
const roleProps = useTagsHandler([
"administrator",
"volunteer",
"employee",
])
const programProps = useTagsHandler([
"community",
"domestic",
"economic",
])
// Define Tanstack columns
const columns: ColumnDef<User, any>[] = [
columnHelper.accessor("username", {
header: () => (
<>
<Bars2Icon className="inline align-top h-4" /> Username
</>
),
cell: (info) => (
<RowOpenAction
title={info.getValue()}
rowData={info.row.original}
setData={setData}
/>
),
}),
columnHelper.accessor("role", {
header: () => (
<>
<ArrowDownCircleIcon className="inline align-top h-4" />{" "}
Role
</>
),
cell: (info) => (
<TagsInput
presetValue={info.getValue()}
{...roleProps}
/>
),
}),
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()}
{...programProps}
/>
),
}),
];
return <Table<User> data={data} setData={setData} columns={columns}/>
}

View File

@ -1,4 +1,4 @@
import React, { useState, useRef, Dispatch, SetStateAction } from "react"; import React, { useState, useRef } from "react";
import "tailwindcss/tailwind.css"; import "tailwindcss/tailwind.css";
import { TagsArray } from "./TagsArray"; import { TagsArray } from "./TagsArray";
import { TagDropdown } from "./TagDropdown"; import { TagDropdown } from "./TagDropdown";
@ -7,8 +7,8 @@ import { CreateNewTagAction } from "./CreateNewTagAction";
interface TagsInputProps { interface TagsInputProps {
presetOptions: string[]; presetOptions: string[];
presetValue: string | string[]; presetValue: string | string[];
setPresetOptions: Dispatch<SetStateAction<string[]>>; setPresetOptions: () => {};
getTagColor(tag: string): string; getTagColor: () => {};
} }
const TagsInput: React.FC<TagsInputProps> = ({ const TagsInput: React.FC<TagsInputProps> = ({

View File

@ -7,7 +7,7 @@ export interface Tags {
} }
export const TagsArray = ({ tags, handleDelete, active = false }: Tags) => { export const TagsArray = ({ tags, handleDelete, active = false }: Tags) => {
// console.log(tags); console.log(tags);
return ( return (
<div className="flex ml-2 flex-wrap gap-2 items-center"> <div className="flex ml-2 flex-wrap gap-2 items-center">

View File

@ -1,35 +0,0 @@
import { useState } from 'react';
/**
* Custom hook used to handle the state of tag options and colors
* @param initialOptions Initial value for preset options
* @returns An object with three fields intended to be passed into a `TagsInput` component:
* - `presetOptions` - the current state of tag options
* - `setPresetOptions` - the state setter for presetOptions
* - `getTagColor` - function that retrieves the color for the given tag
*/
export default function useTagsHandler(initialOptions: string[]) {
const [presetOptions, setPresetOptions] = useState(initialOptions);
const [tagColors, setTagColors] = useState(new Map<string, string>());
const getTagColor = (tag: string): 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 randomColor;
}
// Since we populate any missing keys, .get will never return undefined,
// so we are safe to typecast to prevent a type error
return tagColors.get(tag) as string;
};
return { presetOptions, setPresetOptions, getTagColor }
}

View File

@ -1,9 +0,0 @@
/**
* Represents metadata of the Resource, Service, and User models to be used in a table
*/
interface DataPoint {
id: number;
visible: boolean;
}
export default DataPoint;