mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-23 03:29:49 -04:00
Compare commits
10 Commits
2766ad8eee
...
476016117b
Author | SHA1 | Date | |
---|---|---|---|
|
476016117b | ||
|
bcfca9e169 | ||
|
8f41d5e116 | ||
|
b51ffcfbcd | ||
|
f3878f45e6 | ||
|
966834d34d | ||
|
598b9dd784 | ||
|
3a2dd69332 | ||
|
77075c55d8 | ||
|
5d06760c41 |
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { PageLayout } from "@/components/PageLayout";
|
||||
import { Table } from "@/components/Table/Index";
|
||||
import UserTable from "@/components/Table/UserIndex";
|
||||
import User from "@/utils/models/User";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
|
||||
|
@ -38,7 +38,7 @@ export default function Page() {
|
|||
<div className="min-h-screen flex flex-col">
|
||||
{/* icon + title */}
|
||||
<PageLayout title="Users" icon={<UsersIcon />}>
|
||||
<Table users={users} />
|
||||
<UserTable users={users} />
|
||||
</PageLayout>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { PageLayout } from "@/components/PageLayout";
|
||||
import { ResourceTable } from "@/components/Table/ResourceIndex";
|
||||
import Resource from "@/utils/models/Resource";
|
||||
import ResourceTable from "@/components/Table/ResourceIndex";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
|
||||
import { BookmarkIcon } from "@heroicons/react/24/solid";
|
||||
|
@ -38,7 +38,7 @@ export default function Page() {
|
|||
<div className="min-h-screen flex flex-col">
|
||||
{/* icon + title */}
|
||||
<PageLayout title="Resources" icon={<BookmarkIcon />}>
|
||||
<ResourceTable users={resources} />
|
||||
<ResourceTable resources={resources} />
|
||||
</PageLayout>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import Sidebar from "@/components/Sidebar/Sidebar";
|
||||
import React, { useState } from "react";
|
||||
import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import User, { Role } from "@/utils/models/User";
|
||||
import Loading from "@/components/auth/Loading";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
const [user, setUser] = useState<User>();
|
||||
|
||||
useEffect(() => {
|
||||
async function getUser() {
|
||||
const supabase = createClient();
|
||||
|
||||
const { data, error } = await supabase.auth.getUser();
|
||||
|
||||
console.log(data, error);
|
||||
|
||||
if (error) {
|
||||
console.log("Accessed resource page but not logged in");
|
||||
router.push("/auth/login");
|
||||
return;
|
||||
}
|
||||
|
||||
const userData = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_HOST}/api/user?uuid=${data.user.id}`
|
||||
);
|
||||
|
||||
const user: User = await userData.json();
|
||||
|
||||
setUser(user);
|
||||
}
|
||||
|
||||
getUser();
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<div className="flex-row">
|
||||
{user ? (
|
||||
<div>
|
||||
{/* button to open sidebar */}
|
||||
<button
|
||||
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||
className={`fixed z-20 p-2 text-gray-500 hover:text-gray-800 left-0`}
|
||||
aria-label={"Open sidebar"}
|
||||
>
|
||||
{
|
||||
!isSidebarOpen && (
|
||||
<ChevronDoubleRightIcon className="h-5 w-5" />
|
||||
) // Icon for closing the sidebar
|
||||
}
|
||||
</button>
|
||||
{/* sidebar */}
|
||||
<div
|
||||
className={`absolute inset-y-0 left-0 transform ${
|
||||
isSidebarOpen
|
||||
? "translate-x-0"
|
||||
: "-translate-x-full"
|
||||
} w-64 transition duration-300 ease-in-out`}
|
||||
>
|
||||
<Sidebar
|
||||
setIsSidebarOpen={setIsSidebarOpen}
|
||||
name={user.username}
|
||||
email={user.email}
|
||||
isAdmin={user.role === Role.ADMIN}
|
||||
/>
|
||||
</div>
|
||||
{/* page ui */}
|
||||
<div
|
||||
className={`flex-1 transition duration-300 ease-in-out ${
|
||||
isSidebarOpen ? "ml-64" : "ml-0"
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { PageLayout } from "@/components/PageLayout";
|
||||
import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
||||
import { RowOptionMenu } from "@/components/Table/RowOptionMenu";
|
||||
import { TestTable } from "@/components/Table/TestIndex";
|
||||
import TagsInput from "@/components/TagsInput/Index";
|
||||
import Resource from "@/utils/models/Resource";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
|
||||
import { Bars2Icon, BookmarkIcon } from "@heroicons/react/24/solid";
|
||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function Page() {
|
||||
const [resources, setResources] = useState<Resource[]>([]);
|
||||
const columnHelper = createColumnHelper<Resource>();
|
||||
|
||||
useEffect(() => {
|
||||
async function getResources() {
|
||||
const supabase = createClient();
|
||||
|
||||
const { data, error } = await supabase.auth.getUser();
|
||||
|
||||
if (error) {
|
||||
console.log("Accessed admin page but not logged in");
|
||||
return;
|
||||
}
|
||||
|
||||
const userListData = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_HOST}/api/resource/all?uuid=${data.user.id}`
|
||||
);
|
||||
|
||||
const resourcesAPI: Resource[] = await userListData.json();
|
||||
|
||||
setResources(resourcesAPI);
|
||||
}
|
||||
|
||||
getResources();
|
||||
}, []);
|
||||
|
||||
const [data, setData] = useState<Resource[]>([]);
|
||||
|
||||
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 addUser = () => {
|
||||
setData([...data]);
|
||||
};
|
||||
|
||||
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 handleRowUpdate = (updatedRow: Resource) => {
|
||||
const dataIndex = data.findIndex((row) => row.id === updatedRow.id);
|
||||
if (dataIndex !== -1) {
|
||||
const updatedData = [...data];
|
||||
updatedData[dataIndex] = updatedRow;
|
||||
setData(updatedData);
|
||||
}
|
||||
};
|
||||
|
||||
const columns: ColumnDef<Resource, any>[] = [
|
||||
columnHelper.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("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()} />
|
||||
),
|
||||
}),
|
||||
|
||||
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 (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
{/* icon + title */}
|
||||
<PageLayout title="Resources" icon={<BookmarkIcon />}>
|
||||
<TestTable initialData={resources} columns={columns}/>
|
||||
</PageLayout>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { PageLayout } from "@/components/PageLayout";
|
||||
import { ServiceTable } from "@/components/Table/ServiceIndex";
|
||||
import ServiceTable from "@/components/Table/ServiceIndex";
|
||||
import Service from "@/utils/models/Service";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
|
||||
|
@ -37,7 +37,7 @@ export default function Page() {
|
|||
<div className="min-h-screen flex flex-col">
|
||||
{/* icon + title */}
|
||||
<PageLayout title="Services" icon={<ClipboardIcon />}>
|
||||
<ServiceTable users={services} />
|
||||
<ServiceTable services={services} />
|
||||
</PageLayout>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,306 +0,0 @@
|
|||
// for showcasing to compass
|
||||
|
||||
import users from "./users.json";
|
||||
import {
|
||||
Cell,
|
||||
ColumnDef,
|
||||
Row,
|
||||
createColumnHelper,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
sortingFns,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import {
|
||||
ChangeEvent,
|
||||
useState,
|
||||
useEffect,
|
||||
FunctionComponent,
|
||||
useRef,
|
||||
ChangeEventHandler,
|
||||
Key,
|
||||
} from "react";
|
||||
import { RowOptionMenu } from "./RowOptionMenu";
|
||||
import { RowOpenAction } from "./RowOpenAction";
|
||||
import { TableAction } from "./TableAction";
|
||||
import {
|
||||
AtSymbolIcon,
|
||||
Bars2Icon,
|
||||
ArrowDownCircleIcon,
|
||||
PlusIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import TagsInput from "../TagsInput/Index";
|
||||
import { rankItem } from "@tanstack/match-sorter-utils";
|
||||
import User from "@/utils/models/User";
|
||||
|
||||
// For search
|
||||
const fuzzyFilter = (
|
||||
row: Row<any>,
|
||||
columnId: string,
|
||||
value: any,
|
||||
addMeta: (meta: any) => void
|
||||
) => {
|
||||
// Rank the item
|
||||
const itemRank = rankItem(row.getValue(columnId), value);
|
||||
|
||||
// Store the ranking info
|
||||
addMeta(itemRank);
|
||||
|
||||
// Return if the item should be filtered in/out
|
||||
return itemRank.passed;
|
||||
};
|
||||
|
||||
export const Table = ({ users }: { users: User[] }) => {
|
||||
const columnHelper = createColumnHelper<User>();
|
||||
|
||||
useEffect(() => {
|
||||
const sortedUsers = [...users].sort((a, b) =>
|
||||
a.visible === b.visible ? 0 : a.visible ? -1 : 1
|
||||
);
|
||||
setData(sortedUsers);
|
||||
}, [users]);
|
||||
|
||||
const deleteUser = (userId: number) => {
|
||||
console.log(data);
|
||||
setData((currentData) =>
|
||||
currentData.filter((user) => user.id !== userId)
|
||||
);
|
||||
};
|
||||
|
||||
const hideUser = (userId: number) => {
|
||||
console.log(`Toggling visibility for user with ID: ${userId}`);
|
||||
setData((currentData) => {
|
||||
const newData = currentData
|
||||
.map((user) => {
|
||||
if (user.id === userId) {
|
||||
return { ...user, visible: !user.visible };
|
||||
}
|
||||
return user;
|
||||
})
|
||||
.sort((a, b) =>
|
||||
a.visible === b.visible ? 0 : a.visible ? -1 : 1
|
||||
);
|
||||
|
||||
console.log(newData);
|
||||
return newData;
|
||||
});
|
||||
};
|
||||
const [presetOptions, setPresetOptions] = useState([
|
||||
"administrator",
|
||||
"volunteer",
|
||||
"employee",
|
||||
]);
|
||||
const [tagColors, setTagColors] = useState(new Map());
|
||||
|
||||
const getTagColor = (tag: string) => {
|
||||
if (!tagColors.has(tag)) {
|
||||
const colors = [
|
||||
"bg-cyan-100",
|
||||
"bg-blue-100",
|
||||
"bg-green-100",
|
||||
"bg-yellow-100",
|
||||
"bg-purple-100",
|
||||
];
|
||||
const randomColor =
|
||||
colors[Math.floor(Math.random() * colors.length)];
|
||||
setTagColors(new Map(tagColors).set(tag, randomColor));
|
||||
}
|
||||
return tagColors.get(tag);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
columnHelper.display({
|
||||
id: "options",
|
||||
cell: (props) => (
|
||||
<RowOptionMenu
|
||||
onDelete={() => deleteUser(props.row.original.id)}
|
||||
onHide={() => hideUser(props.row.original.id)}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("username", {
|
||||
header: () => (
|
||||
<>
|
||||
<Bars2Icon className="inline align-top h-4" /> Username
|
||||
</>
|
||||
),
|
||||
cell: (info) => (
|
||||
<RowOpenAction
|
||||
title={info.getValue()}
|
||||
rowData={info.row.original}
|
||||
onRowUpdate={handleRowUpdate}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("role", {
|
||||
header: () => (
|
||||
<>
|
||||
<ArrowDownCircleIcon className="inline align-top h-4" />{" "}
|
||||
Role
|
||||
</>
|
||||
),
|
||||
cell: (info) => (
|
||||
<TagsInput
|
||||
presetValue={info.getValue()}
|
||||
presetOptions={presetOptions}
|
||||
setPresetOptions={setPresetOptions}
|
||||
getTagColor={getTagColor}
|
||||
setTagColors={setTagColors}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("email", {
|
||||
header: () => (
|
||||
<>
|
||||
<AtSymbolIcon className="inline align-top h-4" /> Email
|
||||
</>
|
||||
),
|
||||
cell: (info) => (
|
||||
<span className="ml-2 text-gray-500 underline hover:text-gray-400">
|
||||
{info.getValue()}
|
||||
</span>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("program", {
|
||||
header: () => (
|
||||
<>
|
||||
<ArrowDownCircleIcon className="inline align-top h-4" />{" "}
|
||||
Program
|
||||
</>
|
||||
),
|
||||
cell: (info) => <TagsInput presetValue={info.getValue()} />,
|
||||
}),
|
||||
];
|
||||
|
||||
const [data, setData] = useState<User[]>([...users]);
|
||||
|
||||
const addUser = () => {
|
||||
setData([...data]);
|
||||
};
|
||||
|
||||
// Searching
|
||||
const [query, setQuery] = useState("");
|
||||
const handleSearchChange = (e: ChangeEvent) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
setQuery(String(target.value));
|
||||
};
|
||||
|
||||
const handleCellChange = (e: ChangeEvent, key: Key) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
console.log(key);
|
||||
};
|
||||
|
||||
// TODO: Filtering
|
||||
|
||||
// TODO: Sorting
|
||||
|
||||
// added this fn for editing rows
|
||||
const handleRowUpdate = (updatedRow: User) => {
|
||||
const dataIndex = data.findIndex((row) => row.id === updatedRow.id);
|
||||
if (dataIndex !== -1) {
|
||||
const updatedData = [...data];
|
||||
updatedData[dataIndex] = updatedRow;
|
||||
setData(updatedData);
|
||||
}
|
||||
};
|
||||
|
||||
const table = useReactTable({
|
||||
columns,
|
||||
data,
|
||||
filterFns: {
|
||||
fuzzy: fuzzyFilter,
|
||||
},
|
||||
state: {
|
||||
globalFilter: query,
|
||||
},
|
||||
onGlobalFilterChange: setQuery,
|
||||
globalFilterFn: fuzzyFilter,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
});
|
||||
|
||||
const handleRowData = (row: any) => {
|
||||
const rowData: any = {};
|
||||
row.cells.forEach((cell: any) => {
|
||||
rowData[cell.column.id] = cell.value;
|
||||
});
|
||||
// Use rowData object containing data from all columns for the current row
|
||||
console.log(rowData);
|
||||
return rowData;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row justify-end">
|
||||
<TableAction query={query} handleChange={handleSearchChange} />
|
||||
</div>
|
||||
<table className="w-full text-xs text-left rtl:text-right">
|
||||
<thead className="text-xs text-gray-500 capitalize">
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header, i) => (
|
||||
<th
|
||||
scope="col"
|
||||
className={
|
||||
"p-2 border-gray-200 border-y font-medium " +
|
||||
(1 < i && i < columns.length - 1
|
||||
? "border-x"
|
||||
: "")
|
||||
}
|
||||
key={header.id}
|
||||
>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody>
|
||||
{table.getRowModel().rows.map((row) => {
|
||||
// Individual row
|
||||
const isUserVisible = row.original.visible;
|
||||
const rowClassNames = `text-gray-800 border-y lowercase hover:bg-gray-50 ${
|
||||
!isUserVisible ? "bg-gray-200 text-gray-500" : ""
|
||||
}`;
|
||||
return (
|
||||
<tr className={rowClassNames} key={row.id}>
|
||||
{row.getVisibleCells().map((cell, i) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className={
|
||||
"[&:nth-child(n+3)]:border-x relative first:text-left first:px-0 last:border-none"
|
||||
}
|
||||
>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td
|
||||
className="p-3 border-y border-gray-200 text-gray-600 hover:bg-gray-50"
|
||||
colSpan={100}
|
||||
onClick={addUser}
|
||||
>
|
||||
<span className="flex ml-1 text-gray-500">
|
||||
<PlusIcon className="inline h-4 mr-1" />
|
||||
New
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,305 +1,95 @@
|
|||
// for showcasing to compass
|
||||
|
||||
import users from "./users.json";
|
||||
import {
|
||||
Cell,
|
||||
ColumnDef,
|
||||
Row,
|
||||
createColumnHelper,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
sortingFns,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import {
|
||||
ChangeEvent,
|
||||
useState,
|
||||
useEffect,
|
||||
FunctionComponent,
|
||||
useRef,
|
||||
ChangeEventHandler,
|
||||
Key,
|
||||
} from "react";
|
||||
import { RowOptionMenu } from "./RowOptionMenu";
|
||||
import { RowOpenAction } from "./RowOpenAction";
|
||||
import { TableAction } from "./TableAction";
|
||||
import {
|
||||
AtSymbolIcon,
|
||||
Bars2Icon,
|
||||
ArrowDownCircleIcon,
|
||||
PlusIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import TagsInput from "../TagsInput/Index";
|
||||
import { rankItem } from "@tanstack/match-sorter-utils";
|
||||
import Resource from "@/utils/models/Resource";
|
||||
|
||||
// For search
|
||||
const fuzzyFilter = (
|
||||
row: Row<any>,
|
||||
columnId: string,
|
||||
value: any,
|
||||
addMeta: (meta: any) => void
|
||||
) => {
|
||||
// Rank the item
|
||||
const itemRank = rankItem(row.getValue(columnId), value);
|
||||
|
||||
// Store the ranking info
|
||||
addMeta(itemRank);
|
||||
|
||||
// Return if the item should be filtered in/out
|
||||
return itemRank.passed;
|
||||
};
|
||||
|
||||
// TODO: Rename everything to resources
|
||||
export const ResourceTable = ({ users }: { users: Resource[] }) => {
|
||||
const columnHelper = createColumnHelper<Resource>();
|
||||
|
||||
useEffect(() => {
|
||||
const sortedUsers = [...users].sort((a, b) =>
|
||||
a.visible === b.visible ? 0 : a.visible ? -1 : 1
|
||||
);
|
||||
setData(sortedUsers);
|
||||
}, [users]);
|
||||
|
||||
const [presetOptions, setPresetOptions] = useState([
|
||||
"administrator",
|
||||
"volunteer",
|
||||
"employee",
|
||||
]);
|
||||
|
||||
const deleteUser = (userId: number) => {
|
||||
console.log(data);
|
||||
setData((currentData) =>
|
||||
currentData.filter((user) => user.id !== userId)
|
||||
);
|
||||
};
|
||||
|
||||
const hideUser = (userId: number) => {
|
||||
console.log(`Toggling visibility for user with ID: ${userId}`);
|
||||
setData((currentData) => {
|
||||
const newData = currentData
|
||||
.map((user) => {
|
||||
if (user.id === userId) {
|
||||
return { ...user, visible: !user.visible };
|
||||
}
|
||||
return user;
|
||||
})
|
||||
.sort((a, b) =>
|
||||
a.visible === b.visible ? 0 : a.visible ? -1 : 1
|
||||
);
|
||||
|
||||
console.log(newData);
|
||||
return newData;
|
||||
});
|
||||
};
|
||||
const [tagColors, setTagColors] = useState(new Map());
|
||||
|
||||
const getTagColor = (tag: string) => {
|
||||
if (!tagColors.has(tag)) {
|
||||
const colors = [
|
||||
"bg-cyan-100",
|
||||
"bg-blue-100",
|
||||
"bg-green-100",
|
||||
"bg-yellow-100",
|
||||
"bg-purple-100",
|
||||
];
|
||||
const randomColor =
|
||||
colors[Math.floor(Math.random() * colors.length)];
|
||||
setTagColors(new Map(tagColors).set(tag, randomColor));
|
||||
}
|
||||
return tagColors.get(tag);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
columnHelper.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("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()} />,
|
||||
}),
|
||||
|
||||
columnHelper.accessor("summary", {
|
||||
header: () => (
|
||||
<>
|
||||
<Bars2Icon className="inline align-top h-4" /> Summary
|
||||
</>
|
||||
),
|
||||
cell: (info) => (
|
||||
<span className="ml-2 text-gray-500">{info.getValue()}</span>
|
||||
),
|
||||
}),
|
||||
];
|
||||
|
||||
const [data, setData] = useState<Resource[]>([...users]);
|
||||
|
||||
const addUser = () => {
|
||||
setData([...data]);
|
||||
};
|
||||
|
||||
// Searching
|
||||
const [query, setQuery] = useState("");
|
||||
const handleSearchChange = (e: ChangeEvent) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
setQuery(String(target.value));
|
||||
};
|
||||
|
||||
const handleCellChange = (e: ChangeEvent, key: Key) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
console.log(key);
|
||||
};
|
||||
|
||||
// TODO: Filtering
|
||||
|
||||
// TODO: Sorting
|
||||
|
||||
// added this fn for editing rows
|
||||
const handleRowUpdate = (updatedRow: Resource) => {
|
||||
const dataIndex = data.findIndex((row) => row.id === updatedRow.id);
|
||||
if (dataIndex !== -1) {
|
||||
const updatedData = [...data];
|
||||
updatedData[dataIndex] = updatedRow;
|
||||
setData(updatedData);
|
||||
}
|
||||
};
|
||||
|
||||
const table = useReactTable({
|
||||
columns,
|
||||
data,
|
||||
filterFns: {
|
||||
fuzzy: fuzzyFilter,
|
||||
},
|
||||
state: {
|
||||
globalFilter: query,
|
||||
},
|
||||
onGlobalFilterChange: setQuery,
|
||||
globalFilterFn: fuzzyFilter,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
});
|
||||
|
||||
const handleRowData = (row: any) => {
|
||||
const rowData: any = {};
|
||||
row.cells.forEach((cell: any) => {
|
||||
rowData[cell.column.id] = cell.value;
|
||||
});
|
||||
// Use rowData object containing data from all columns for the current row
|
||||
console.log(rowData);
|
||||
return rowData;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row justify-end">
|
||||
<TableAction query={query} handleChange={handleSearchChange} />
|
||||
</div>
|
||||
<table className="w-full text-xs text-left rtl:text-right">
|
||||
<thead className="text-xs text-gray-500 capitalize">
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header, i) => (
|
||||
<th
|
||||
scope="col"
|
||||
className={
|
||||
"p-2 border-gray-200 border-y font-medium " +
|
||||
(1 < i && i < columns.length - 1
|
||||
? "border-x"
|
||||
: "")
|
||||
}
|
||||
key={header.id}
|
||||
>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody>
|
||||
{table.getRowModel().rows.map((row) => {
|
||||
// Individual row
|
||||
const isUserVisible = row.original.visible;
|
||||
const rowClassNames = `text-gray-800 border-y lowercase hover:bg-gray-50 ${
|
||||
!isUserVisible ? "bg-gray-200 text-gray-500" : ""
|
||||
}`;
|
||||
return (
|
||||
<tr className={rowClassNames} key={row.id}>
|
||||
{row.getVisibleCells().map((cell, i) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className={
|
||||
"[&:nth-child(n+3)]:border-x relative first:text-left first:px-0 last:border-none"
|
||||
}
|
||||
>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td
|
||||
className="p-3 border-y border-gray-200 text-gray-600 hover:bg-gray-50"
|
||||
colSpan={100}
|
||||
onClick={addUser}
|
||||
>
|
||||
<span className="flex ml-1 text-gray-500">
|
||||
<PlusIcon className="inline h-4 mr-1" />
|
||||
New
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
import { Bars2Icon } from "@heroicons/react/24/solid";
|
||||
import { useState } from "react";
|
||||
import useTagsHandler from "@/components/TagsInput/TagsHandler";
|
||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
||||
import { Table } from "@/components/Table/Table";
|
||||
import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
||||
import TagsInput from "@/components/TagsInput/Index";
|
||||
import Resource from "@/utils/models/Resource";
|
||||
import { DataPoint } from "@/components/Table/Table";
|
||||
|
||||
/**
|
||||
* Table componenet used for displaying resources
|
||||
* @param props.resources List of resources to be displayed by the table
|
||||
*/
|
||||
export default function ResourceTable({ resources }: { resources: Resource[] }) {
|
||||
const columnHelper = createColumnHelper<Resource>();
|
||||
const [data, setData] = useState<DataPoint[]>([...resources]);
|
||||
|
||||
// TODO: Update preset options for resources
|
||||
const { presetOptions, setPresetOptions, getTagColor } = useTagsHandler([
|
||||
"administrator",
|
||||
"volunteer",
|
||||
"employee",
|
||||
])
|
||||
|
||||
const handleRowUpdate = (updatedRow: DataPoint) => {
|
||||
const dataIndex = data.findIndex((row) => row.id === updatedRow.id);
|
||||
if (dataIndex !== -1) {
|
||||
const updatedData = [...data];
|
||||
updatedData[dataIndex] = updatedRow;
|
||||
setData(updatedData);
|
||||
}
|
||||
};
|
||||
|
||||
const columns: ColumnDef<Resource, any>[] = [
|
||||
columnHelper.accessor("name", {
|
||||
header: () => (
|
||||
<>
|
||||
<Bars2Icon className="inline align-top h-4" /> Name
|
||||
</>
|
||||
),
|
||||
cell: (info) => (
|
||||
<RowOpenAction
|
||||
title={info.getValue()}
|
||||
rowData={info.row.original}
|
||||
onRowUpdate={handleRowUpdate}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("link", {
|
||||
header: () => (
|
||||
<>
|
||||
<Bars2Icon className="inline align-top h-4" /> Link
|
||||
</>
|
||||
),
|
||||
cell: (info) => (
|
||||
<a
|
||||
href={info.getValue()}
|
||||
target={"_blank"}
|
||||
className="ml-2 text-gray-500 underline hover:text-gray-400"
|
||||
>
|
||||
{info.getValue()}
|
||||
</a>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("program", {
|
||||
header: () => (
|
||||
<>
|
||||
<Bars2Icon className="inline align-top h-4" /> Program
|
||||
</>
|
||||
),
|
||||
cell: (info) => (
|
||||
<TagsInput
|
||||
presetValue={info.getValue()}
|
||||
presetOptions={presetOptions}
|
||||
setPresetOptions={setPresetOptions}
|
||||
getTagColor={getTagColor}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
|
||||
columnHelper.accessor("summary", {
|
||||
header: () => (
|
||||
<>
|
||||
<Bars2Icon className="inline align-top h-4" /> Summary
|
||||
</>
|
||||
),
|
||||
cell: (info) => (
|
||||
<span className="ml-2 text-gray-500">{info.getValue()}</span>
|
||||
),
|
||||
}),
|
||||
];
|
||||
|
||||
return <Table data={data} setData={setData} columns={columns}/>
|
||||
}
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
import Drawer from "@/components/Drawer/Drawer";
|
||||
import { ChangeEvent, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { DataPoint } from "@/components/Table/Table";
|
||||
|
||||
export const RowOpenAction = ({ title, rowData, onRowUpdate }) => {
|
||||
type RowOpenActionProps = {
|
||||
title: string,
|
||||
rowData: DataPoint,
|
||||
onRowUpdate: (updatedRow: DataPoint) => void;
|
||||
}
|
||||
|
||||
export const RowOpenAction = ({ title, rowData, onRowUpdate }: RowOpenActionProps) => {
|
||||
const [pageContent, setPageContent] = useState("");
|
||||
|
||||
const handleDrawerContentChange = (newContent) => {
|
||||
const handleDrawerContentChange = (newContent: string) => {
|
||||
setPageContent(newContent);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,125 +1,38 @@
|
|||
// 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 { Bars2Icon } from "@heroicons/react/24/solid";
|
||||
import { useState } from "react";
|
||||
import useTagsHandler from "@/components/TagsInput/TagsHandler";
|
||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
||||
import { Table } from "@/components/Table/Table";
|
||||
import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
||||
import TagsInput from "@/components/TagsInput/Index";
|
||||
import Service from "@/utils/models/Service";
|
||||
import { DataPoint } from "@/components/Table/Table";
|
||||
|
||||
// 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[] }) => {
|
||||
/**
|
||||
* Table componenet used for displaying services
|
||||
* @param props.services List of services to be displayed by the table
|
||||
*/
|
||||
export default function ServiceTable({ services }: { services: 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([
|
||||
const [data, setData] = useState<DataPoint[]>([...services]);
|
||||
|
||||
// TODO: Update preset options for services
|
||||
const { presetOptions, setPresetOptions, getTagColor } = useTagsHandler([
|
||||
"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));
|
||||
const handleRowUpdate = (updatedRow: DataPoint) => {
|
||||
const dataIndex = data.findIndex((row) => row.id === updatedRow.id);
|
||||
if (dataIndex !== -1) {
|
||||
const updatedData = [...data];
|
||||
updatedData[dataIndex] = updatedRow;
|
||||
setData(updatedData);
|
||||
}
|
||||
return tagColors.get(tag);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
columnHelper.display({
|
||||
id: "options",
|
||||
cell: (props) => (
|
||||
<RowOptionMenu
|
||||
onDelete={() => {}}
|
||||
onHide={() => hideUser(props.row.original.id)}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
const columns: ColumnDef<Service, any>[] = [
|
||||
columnHelper.accessor("name", {
|
||||
header: () => (
|
||||
<>
|
||||
|
@ -150,7 +63,14 @@ export const ServiceTable = ({ users }: { users: Service[] }) => {
|
|||
<Bars2Icon className="inline align-top h-4" /> Program
|
||||
</>
|
||||
),
|
||||
cell: (info) => <TagsInput presetValue={info.getValue()} />,
|
||||
cell: (info) => (
|
||||
<TagsInput
|
||||
presetValue={info.getValue()}
|
||||
presetOptions={presetOptions}
|
||||
setPresetOptions={setPresetOptions}
|
||||
getTagColor={getTagColor}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("requirements", {
|
||||
header: () => (
|
||||
|
@ -159,10 +79,12 @@ export const ServiceTable = ({ users }: { users: Service[] }) => {
|
|||
</>
|
||||
),
|
||||
cell: (info) => (
|
||||
// TODO: Setup different tag handler for requirements
|
||||
<TagsInput
|
||||
presetValue={
|
||||
info.getValue()[0] !== "" ? info.getValue() : ["N/A"]
|
||||
}
|
||||
presetValue={info.getValue()[0] !== "" ? info.getValue() : ["N/A"]}
|
||||
presetOptions={presetOptions}
|
||||
setPresetOptions={setPresetOptions}
|
||||
getTagColor={getTagColor}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
|
@ -179,134 +101,5 @@ export const ServiceTable = ({ users }: { users: Service[] }) => {
|
|||
}),
|
||||
];
|
||||
|
||||
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>
|
||||
);
|
||||
return <Table data={data} setData={setData} columns={columns} />
|
||||
};
|
||||
|
|
|
@ -1,36 +1,34 @@
|
|||
// for showcasing to compass
|
||||
import {
|
||||
Cell,
|
||||
ColumnDef,
|
||||
import {
|
||||
Row,
|
||||
createColumnHelper,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
sortingFns,
|
||||
ColumnDef,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
getCoreRowModel,
|
||||
flexRender,
|
||||
createColumnHelper
|
||||
} from "@tanstack/react-table";
|
||||
import {
|
||||
ChangeEvent,
|
||||
useState,
|
||||
useEffect,
|
||||
FunctionComponent,
|
||||
useRef,
|
||||
ChangeEventHandler,
|
||||
Key,
|
||||
Dispatch,
|
||||
SetStateAction
|
||||
} 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 { PlusIcon } from "@heroicons/react/24/solid";
|
||||
import { rankItem } from "@tanstack/match-sorter-utils";
|
||||
import Resource from "@/utils/models/Resource";
|
||||
import Service from "@/utils/models/Service";
|
||||
import User from "@/utils/models/User";
|
||||
import { RowOptionMenu } from "./RowOptionMenu";
|
||||
|
||||
export type DataPoint = Resource | User | Service;
|
||||
|
||||
type TableProps = {
|
||||
data: DataPoint[],
|
||||
setData: Dispatch<SetStateAction<DataPoint[]>>
|
||||
columns: ColumnDef<any, any>[]
|
||||
};
|
||||
|
||||
// For search
|
||||
const fuzzyFilter = (
|
||||
|
@ -49,16 +47,66 @@ const fuzzyFilter = (
|
|||
return itemRank.passed;
|
||||
};
|
||||
|
||||
// TODO: Rename everything to resources
|
||||
export const TestTable = ({ initialData, columns }: { initialData: Resource[], columns: ColumnDef<any, any>[] }) => {
|
||||
/**
|
||||
* General componenet that holds shared functionality for any data table component
|
||||
* @param props.data List of data, managed through state, to be held in the table
|
||||
* @param props.setData State setter to be used for data manipulation methods
|
||||
* @param props.columns Column definitions made with Tanstack columnHelper
|
||||
*/
|
||||
export const Table = ({ data, setData, columns }: TableProps) => {
|
||||
const columnHelper = createColumnHelper<Resource>();
|
||||
useEffect(() => {
|
||||
const sortedData = [...initialData].sort((a, b) =>
|
||||
const sortedData = [...data].sort((a, b) =>
|
||||
a.visible === b.visible ? 0 : a.visible ? -1 : 1
|
||||
);
|
||||
setData(sortedData);
|
||||
}, [initialData]);
|
||||
}, [data, setData]);
|
||||
|
||||
const [data, setData] = useState<Resource[]>([...initialData]);
|
||||
|
||||
// Data manipulation
|
||||
// TODO: Connect data manipulation methods to the database (deleteResource, hideResource, addResource)
|
||||
const deleteData = (dataId: number) => {
|
||||
console.log(data);
|
||||
setData((currentData) =>
|
||||
currentData.filter((data) => data.id !== dataId)
|
||||
);
|
||||
};
|
||||
|
||||
const hideData = (dataId: number) => {
|
||||
console.log(`Toggling visibility for data with ID: ${dataId}`);
|
||||
setData((currentData) => {
|
||||
const newData = currentData
|
||||
.map((data) => {
|
||||
if (data.id === dataId) {
|
||||
return { ...data, visible: !data.visible };
|
||||
}
|
||||
return data;
|
||||
})
|
||||
.sort((a, b) =>
|
||||
a.visible === b.visible ? 0 : a.visible ? -1 : 1
|
||||
);
|
||||
|
||||
console.log(newData);
|
||||
return newData;
|
||||
});
|
||||
};
|
||||
|
||||
const addData = () => {
|
||||
setData([...data]);
|
||||
};
|
||||
|
||||
// Add data manipulation options to the first column
|
||||
columns.unshift(
|
||||
columnHelper.display({
|
||||
id: "options",
|
||||
cell: (props) => (
|
||||
<RowOptionMenu
|
||||
onDelete={() => deleteData(props.row.original.id)}
|
||||
onHide={() => hideData(props.row.original.id)}
|
||||
/>
|
||||
),
|
||||
})
|
||||
)
|
||||
|
||||
// Searching
|
||||
const [query, setQuery] = useState("");
|
||||
|
@ -76,9 +124,6 @@ export const TestTable = ({ initialData, columns }: { initialData: Resource[], c
|
|||
|
||||
// TODO: Sorting
|
||||
|
||||
// added this fn for editing rows
|
||||
|
||||
|
||||
const table = useReactTable({
|
||||
columns,
|
||||
data,
|
||||
|
@ -137,9 +182,9 @@ export const TestTable = ({ initialData, columns }: { initialData: Resource[], c
|
|||
<tbody>
|
||||
{table.getRowModel().rows.map((row) => {
|
||||
// Individual row
|
||||
const isUserVisible = row.original.visible;
|
||||
const isDataVisible = row.original.visible;
|
||||
const rowClassNames = `text-gray-800 border-y lowercase hover:bg-gray-50 ${
|
||||
!isUserVisible ? "bg-gray-200 text-gray-500" : ""
|
||||
!isDataVisible ? "bg-gray-200 text-gray-500" : ""
|
||||
}`;
|
||||
return (
|
||||
<tr className={rowClassNames} key={row.id}>
|
||||
|
@ -165,7 +210,7 @@ export const TestTable = ({ initialData, columns }: { initialData: Resource[], c
|
|||
<td
|
||||
className="p-3 border-y border-gray-200 text-gray-600 hover:bg-gray-50"
|
||||
colSpan={100}
|
||||
onClick={addUser}
|
||||
onClick={addData}
|
||||
>
|
||||
<span className="flex ml-1 text-gray-500">
|
||||
<PlusIcon className="inline h-4 mr-1" />
|
||||
|
|
97
compass/components/Table/UserIndex.tsx
Normal file
97
compass/components/Table/UserIndex.tsx
Normal file
|
@ -0,0 +1,97 @@
|
|||
import { ArrowDownCircleIcon, AtSymbolIcon, Bars2Icon } from "@heroicons/react/24/solid";
|
||||
import { useState } from "react";
|
||||
import useTagsHandler from "@/components/TagsInput/TagsHandler";
|
||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
||||
import { Table } from "@/components/Table/Table";
|
||||
import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
||||
import TagsInput from "@/components/TagsInput/Index";
|
||||
import User from "@/utils/models/User";
|
||||
import { DataPoint } from "@/components/Table/Table";
|
||||
|
||||
/**
|
||||
* Table componenet used for displaying users
|
||||
* @param props.users List of users to be displayed by the table
|
||||
*/
|
||||
export default function UserTable({ users }: { users: User[] }) {
|
||||
const columnHelper = createColumnHelper<User>();
|
||||
const [data, setData] = useState<DataPoint[]>([...users]);
|
||||
|
||||
const { presetOptions, setPresetOptions, getTagColor } = useTagsHandler([
|
||||
"administrator",
|
||||
"volunteer",
|
||||
"employee",
|
||||
])
|
||||
|
||||
const handleRowUpdate = (updatedRow: DataPoint) => {
|
||||
const dataIndex = data.findIndex((row) => row.id === updatedRow.id);
|
||||
if (dataIndex !== -1) {
|
||||
const updatedData = [...data];
|
||||
updatedData[dataIndex] = updatedRow;
|
||||
setData(updatedData);
|
||||
}
|
||||
};
|
||||
|
||||
const columns: ColumnDef<User, any>[] = [
|
||||
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}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
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
|
||||
</>
|
||||
),
|
||||
// TODO: Setup different tags handler for program
|
||||
cell: (info) => (
|
||||
<TagsInput
|
||||
presetValue={info.getValue()}
|
||||
presetOptions={presetOptions}
|
||||
setPresetOptions={setPresetOptions}
|
||||
getTagColor={getTagColor}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
];
|
||||
|
||||
return <Table data={data} setData={setData} columns={columns}/>
|
||||
}
|
|
@ -7,7 +7,7 @@ import { CreateNewTagAction } from "./CreateNewTagAction";
|
|||
interface TagsInputProps {
|
||||
presetOptions: string[];
|
||||
presetValue: string | string[];
|
||||
setPresetOptions: Dispatch<SetStateAction<string | string[]>>;
|
||||
setPresetOptions: Dispatch<SetStateAction<string[]>>;
|
||||
getTagColor(tag: string): string;
|
||||
}
|
||||
|
||||
|
|
35
compass/components/TagsInput/TagsHandler.tsx
Normal file
35
compass/components/TagsInput/TagsHandler.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
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 }
|
||||
}
|
Loading…
Reference in New Issue
Block a user