mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-21 18:59:49 -04:00
Compare commits
No commits in common. "04c7fbe2747e9d822e1c30476c7f44bd818f950d" and "cb54c9829d39874ba4aabefebd9ef15087f7ffc1" have entirely different histories.
04c7fbe274
...
cb54c9829d
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
306
compass/components/Table/Index.tsx
Normal file
306
compass/components/Table/Index.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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}/>
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
312
compass/components/Table/ServiceIndex.tsx
Normal file
312
compass/components/Table/ServiceIndex.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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} />
|
|
||||||
};
|
|
|
@ -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}/>
|
|
||||||
}
|
|
|
@ -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> = ({
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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 }
|
|
||||||
}
|
|
|
@ -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;
|
|
Loading…
Reference in New Issue
Block a user