From cb54c9829d39874ba4aabefebd9ef15087f7ffc1 Mon Sep 17 00:00:00 2001
From: pmoharana-cmd <pmoharana032474@gmail.com>
Date: Tue, 15 Oct 2024 19:28:32 -0400
Subject: [PATCH 1/3] Remove angular cli dependency

---
 .devcontainer/Dockerfile | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index f27a6ec..40c5bb9 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -47,9 +47,6 @@ RUN mkdir -p /etc/apt/keyrings \
     && npm install -g npm@latest \
     && rm -rf /var/lib/apt/lists/*
 
-# Install Angular CLI Globally
-RUN npm install -g @angular/cli
-
 # Use a non-root user per https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user
 ARG USERNAME=vscode
 ARG USER_UID=1000

From 2e0dd3b98707126f2770bc8ee65ce6266bacf276 Mon Sep 17 00:00:00 2001
From: Nicolas Asanov <98969136+naasanov@users.noreply.github.com>
Date: Mon, 4 Nov 2024 15:10:13 -0500
Subject: [PATCH 2/3] Refactored Table Components (#43)

* Created mock/test table and resource page to see if implementation works

* Fixed typing for TagsInput

* cleaned up imports

* Started moving data manipulation into Table

* moved data manipulation logic into Table

* added useTagsHandler custom hook to consolidate getTagColor and presetOptions state into one function

* Fixed type errors for RowOpenAction

* Refactored ServiceIndex

* Refactored user table

* Updated imports and client facing routes

* added documentation for table components

* Added documentation for TagsHandler

* small changes for cleaner code

* refactored typing for tables. More work needs to be done to ensure tables are overall working properly

* added todo

* updated client paths with new table props

* alterned handleRowUpdate to only use setData

* diverted responsibility of handleRowChange to Drawer instead of Table to remove repetition

* updated documentation

* added sorting util function to Table.tsx to reduce repetition

* Edited sorting func to be more comaptible and edited hideData to be more concise

* formatting

* updated imports

* updated tags for all tables

* removed DataPoint dependecy from User, Service, and Resource models as it was unnecesary

* Added inline documentation to table components

* added documentation for DataPoint model

* Update package-lock.json
---
 compass/app/admin/page.tsx                    |   4 +-
 compass/app/resource/page.tsx                 |   6 +-
 compass/app/service/page.tsx                  |   8 +-
 compass/components/Drawer/Drawer.tsx          | 504 +++++++++--------
 compass/components/Table/Index.tsx            | 306 ----------
 compass/components/Table/ResourceTable.tsx    |  89 +++
 compass/components/Table/RowOpenAction.tsx    |  62 +-
 compass/components/Table/ServiceIndex.tsx     | 312 -----------
 compass/components/Table/ServiceTable.tsx     | 108 ++++
 .../Table/{ResourceIndex.tsx => Table.tsx}    | 528 ++++++++----------
 compass/components/Table/UserTable.tsx        |  95 ++++
 compass/components/TagsInput/Index.tsx        |   6 +-
 compass/components/TagsInput/TagsArray.tsx    |   2 +-
 compass/components/TagsInput/TagsHandler.tsx  |  35 ++
 compass/utils/models/DataPoint.ts             |   9 +
 15 files changed, 864 insertions(+), 1210 deletions(-)
 delete mode 100644 compass/components/Table/Index.tsx
 create mode 100644 compass/components/Table/ResourceTable.tsx
 delete mode 100644 compass/components/Table/ServiceIndex.tsx
 create mode 100644 compass/components/Table/ServiceTable.tsx
 rename compass/components/Table/{ResourceIndex.tsx => Table.tsx} (55%)
 create mode 100644 compass/components/Table/UserTable.tsx
 create mode 100644 compass/components/TagsInput/TagsHandler.tsx
 create mode 100644 compass/utils/models/DataPoint.ts

diff --git a/compass/app/admin/page.tsx b/compass/app/admin/page.tsx
index fede31d..abe2a8c 100644
--- a/compass/app/admin/page.tsx
+++ b/compass/app/admin/page.tsx
@@ -1,7 +1,7 @@
 "use client";
 
 import { PageLayout } from "@/components/PageLayout";
-import { Table } from "@/components/Table/Index";
+import UserTable from "@/components/Table/UserTable";
 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 data={users} setData={setUsers} />
             </PageLayout>
         </div>
     );
diff --git a/compass/app/resource/page.tsx b/compass/app/resource/page.tsx
index fc4df14..68a620f 100644
--- a/compass/app/resource/page.tsx
+++ b/compass/app/resource/page.tsx
@@ -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/ResourceTable";
 import { createClient } from "@/utils/supabase/client";
 
 import { BookmarkIcon } from "@heroicons/react/24/solid";
@@ -27,7 +27,7 @@ export default function Page() {
             );
 
             const resourcesAPI: Resource[] = await userListData.json();
-
+            
             setResources(resourcesAPI);
         }
 
@@ -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 data={resources} setData={setResources} />
             </PageLayout>
         </div>
     );
diff --git a/compass/app/service/page.tsx b/compass/app/service/page.tsx
index 8ebea4f..efe6337 100644
--- a/compass/app/service/page.tsx
+++ b/compass/app/service/page.tsx
@@ -1,7 +1,7 @@
 "use client";
 
 import { PageLayout } from "@/components/PageLayout";
-import { ServiceTable } from "@/components/Table/ServiceIndex";
+import ServiceTable from "@/components/Table/ServiceTable";
 import Service from "@/utils/models/Service";
 import { createClient } from "@/utils/supabase/client";
 
@@ -9,7 +9,7 @@ import { ClipboardIcon } from "@heroicons/react/24/solid";
 import { useEffect, useState } from "react";
 
 export default function Page() {
-    const [services, setUsers] = useState<Service[]>([]);
+    const [services, setServices] = useState<Service[]>([]);
 
     useEffect(() => {
         async function getServices() {
@@ -27,7 +27,7 @@ export default function Page() {
             );
 
             const servicesAPI: Service[] = await serviceListData.json();
-            setUsers(servicesAPI);
+            setServices(servicesAPI);
         }
 
         getServices();
@@ -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 data={services} setData={setServices} />
             </PageLayout>
         </div>
     );
diff --git a/compass/components/Drawer/Drawer.tsx b/compass/components/Drawer/Drawer.tsx
index 6879f75..17b4557 100644
--- a/compass/components/Drawer/Drawer.tsx
+++ b/compass/components/Drawer/Drawer.tsx
@@ -1,247 +1,257 @@
-import { FunctionComponent, ReactNode } from "react";
-import React, { useState } from "react";
-import { ChevronDoubleLeftIcon } from "@heroicons/react/24/solid";
-import {
-    StarIcon as SolidStarIcon,
-    EnvelopeIcon,
-    UserIcon,
-} from "@heroicons/react/24/solid";
-import {
-    ArrowsPointingOutIcon,
-    ArrowsPointingInIcon,
-    StarIcon as OutlineStarIcon,
-    ListBulletIcon,
-} from "@heroicons/react/24/outline";
-import TagsInput from "../TagsInput/Index";
-
-type DrawerProps = {
-    title: string;
-    children: ReactNode;
-    onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
-    type?: "button" | "submit" | "reset"; // specify possible values for type
-    disabled?: boolean;
-    editableContent?: any;
-    onSave?: (content: any) => void;
-    rowContent?: any;
-    onRowUpdate?: (content: any) => void;
-};
-
-interface EditContent {
-    content: string;
-    isEditing: boolean;
-}
-
-const Drawer: FunctionComponent<DrawerProps> = ({
-    title,
-    children,
-    onSave,
-    editableContent,
-    rowContent,
-    onRowUpdate,
-}) => {
-    const [isOpen, setIsOpen] = useState(false);
-    const [isFull, setIsFull] = useState(false);
-    const [isFavorite, setIsFavorite] = useState(false);
-    const [tempRowContent, setTempRowContent] = useState(rowContent);
-
-    const handleTempRowContentChange = (e) => {
-        const { name, value } = e.target;
-        console.log(name);
-        console.log(value);
-        setTempRowContent((prevContent) => ({
-            ...prevContent,
-            [name]: value,
-        }));
-    };
-
-    const handleEnterPress = (e) => {
-        if (e.key === "Enter") {
-            e.preventDefault();
-            // Update the rowContent with the temporaryRowContent
-            if (onRowUpdate) {
-                onRowUpdate(tempRowContent);
-            }
-        }
-    };
-
-    const toggleDrawer = () => {
-        setIsOpen(!isOpen);
-        if (isFull) {
-            setIsFull(!isFull);
-        }
-    };
-
-    const toggleDrawerFullScreen = () => setIsFull(!isFull);
-
-    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 ${
-        isOpen ? "translate-x-0 shadow-xl" : "translate-x-full"
-    } ${isFull ? "w-full" : "w-1/2"}`;
-
-    const iconComponent = isFull ? (
-        <ArrowsPointingInIcon className="h-5 w-5" />
-    ) : (
-        <ArrowsPointingOutIcon className="h-5 w-5" />
-    );
-
-    const favoriteIcon = isFavorite ? (
-        <SolidStarIcon className="h-5 w-5" />
-    ) : (
-        <OutlineStarIcon className="h-5 w-5" />
-    );
-
-    const [presetOptions, setPresetOptions] = useState([
-        "administrator",
-        "volunteer",
-        "employee",
-    ]);
-    const [rolePresetOptions, setRolePresetOptions] = useState([
-        "domestic",
-        "community",
-        "economic",
-    ]);
-    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);
-    };
-
-    return (
-        <div>
-            <button
-                className={
-                    "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"
-                }
-                onClick={toggleDrawer}
-            >
-                Open
-            </button>
-            <div className={drawerClassName}></div>
-            <div className={drawerClassName}>
-                <div className="flex items-center justify-between p-4">
-                    <div className="flex flex-row items-center justify-between space-x-2">
-                        <span className="h-5 text-purple-200 w-5">
-                            <UserIcon />
-                        </span>
-                        <h2 className="text-lg text-gray-800 font-semibold">
-                            {rowContent.username}
-                        </h2>
-                    </div>
-                    <div>
-                        <button
-                            onClick={toggleFavorite}
-                            className="py-2 text-gray-500 hover:text-gray-800 mr-2"
-                        >
-                            {favoriteIcon}
-                        </button>
-                        <button
-                            onClick={toggleDrawerFullScreen}
-                            className="py-2 text-gray-500 hover:text-gray-800 mr-2"
-                        >
-                            {iconComponent}
-                        </button>
-                        <button
-                            onClick={toggleDrawer}
-                            className="py-2 text-gray-500 hover:text-gray-800"
-                        >
-                            <ChevronDoubleLeftIcon className="h-5 w-5" />
-                        </button>
-                    </div>
-                </div>
-                <div className="p-4">
-                    <table className="p-4">
-                        <tbody className="items-center">
-                            <tr className="w-full text-xs items-center flex flex-row justify-between">
-                                <div className="flex flex-row space-x-2 text-gray-500 items-center">
-                                    <td>
-                                        <UserIcon className="h-4 w-4" />
-                                    </td>
-                                    <td className="w-32">Username</td>
-                                </div>
-                                <td className="w-3/4 w-3/4 p-2 pl-0">
-                                    <input
-                                        type="text"
-                                        name="username"
-                                        value={tempRowContent.username}
-                                        onChange={handleTempRowContentChange}
-                                        onKeyDown={handleEnterPress}
-                                        className="ml-2 w-full p-1 focus:outline-gray-200  hover:bg-gray-50"
-                                    />
-                                </td>
-                            </tr>
-                            <tr className="w-full text-xs items-center flex flex-row justify-between">
-                                <div className="flex flex-row space-x-2 text-gray-500 items-center">
-                                    <td>
-                                        <ListBulletIcon className="h-4 w-4" />
-                                    </td>
-                                    <td className="w-32">Role</td>
-                                </div>
-                                <td className="w-3/4 hover:bg-gray-50">
-                                    <TagsInput
-                                        presetValue={tempRowContent.role}
-                                        presetOptions={presetOptions}
-                                        setPresetOptions={setPresetOptions}
-                                        getTagColor={getTagColor}
-                                        setTagColors={setTagColors}
-                                    />
-                                </td>
-                            </tr>
-                            <tr className="w-full text-xs items-center flex flex-row justify-between">
-                                <div className="flex flex-row space-x-2 text-gray-500 items-center">
-                                    <td>
-                                        <EnvelopeIcon className="h-4 w-4" />
-                                    </td>
-                                    <td className="w-32">Email</td>
-                                </div>
-                                <td className="w-3/4 p-2 pl-0">
-                                    <input
-                                        type="text"
-                                        name="email"
-                                        value={tempRowContent.email}
-                                        onChange={handleTempRowContentChange}
-                                        onKeyDown={handleEnterPress}
-                                        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>
-                            </tr>
-                            <tr className="w-full text-xs items-center flex flex-row justify-between">
-                                <div className="flex flex-row space-x-2 text-gray-500 items-center">
-                                    <td>
-                                        <ListBulletIcon className="h-4 w-4" />
-                                    </td>
-                                    <td className="w-32">Type of Program</td>
-                                </div>
-                                <td className="w-3/4 hover:bg-gray-50">
-                                    {/* {rowContent.program} */}
-                                    <TagsInput
-                                        presetValue={tempRowContent.program}
-                                        presetOptions={rolePresetOptions}
-                                        setPresetOptions={setRolePresetOptions}
-                                        getTagColor={getTagColor}
-                                        setTagColors={setTagColors}
-                                    />
-                                </td>
-                            </tr>
-                        </tbody>
-                    </table>
-                    <br />
-                </div>
-            </div>
-        </div>
-    );
-};
-
-export default Drawer;
+import { Dispatch, FunctionComponent, ReactNode, SetStateAction } from "react";
+import React, { useState } from "react";
+import { ChevronDoubleLeftIcon } from "@heroicons/react/24/solid";
+import {
+    StarIcon as SolidStarIcon,
+    EnvelopeIcon,
+    UserIcon,
+} from "@heroicons/react/24/solid";
+import {
+    ArrowsPointingOutIcon,
+    ArrowsPointingInIcon,
+    StarIcon as OutlineStarIcon,
+    ListBulletIcon,
+} from "@heroicons/react/24/outline";
+import TagsInput from "../TagsInput/Index";
+
+type DrawerProps = {
+    title: string;
+    children: ReactNode;
+    onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
+    type?: "button" | "submit" | "reset"; // specify possible values for type
+    disabled?: boolean;
+    editableContent?: any;
+    onSave?: (content: any) => void;
+    rowContent?: any;
+    setData: Dispatch<SetStateAction<any>>;
+};
+
+interface EditContent {
+    content: string;
+    isEditing: boolean;
+}
+
+const Drawer: FunctionComponent<DrawerProps> = ({
+    title,
+    children,
+    onSave,
+    editableContent,
+    rowContent,
+    setData,
+}) => {
+    const [isOpen, setIsOpen] = useState(false);
+    const [isFull, setIsFull] = useState(false);
+    const [isFavorite, setIsFavorite] = useState(false);
+    const [tempRowContent, setTempRowContent] = useState(rowContent);
+
+    const onRowUpdate = (updatedRow: any) => {
+        setData((prevData: any) => (
+            prevData.map((row: any) => (
+                row.id === updatedRow.id
+                ? updatedRow
+                : row
+            ))
+        ))
+    };
+
+    const handleTempRowContentChange = (e) => {
+        const { name, value } = e.target;
+        console.log(name);
+        console.log(value);
+        setTempRowContent((prevContent) => ({
+            ...prevContent,
+            [name]: value,
+        }));
+    };
+
+    const handleEnterPress = (e) => {
+        if (e.key === "Enter") {
+            e.preventDefault();
+            // Update the rowContent with the temporaryRowContent
+            if (onRowUpdate) {
+                onRowUpdate(tempRowContent);
+            }
+        }
+    };
+
+    const toggleDrawer = () => {
+        setIsOpen(!isOpen);
+        if (isFull) {
+            setIsFull(!isFull);
+        }
+    };
+
+    const toggleDrawerFullScreen = () => setIsFull(!isFull);
+
+    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 ${
+        isOpen ? "translate-x-0 shadow-xl" : "translate-x-full"
+    } ${isFull ? "w-full" : "w-1/2"}`;
+
+    const iconComponent = isFull ? (
+        <ArrowsPointingInIcon className="h-5 w-5" />
+    ) : (
+        <ArrowsPointingOutIcon className="h-5 w-5" />
+    );
+
+    const favoriteIcon = isFavorite ? (
+        <SolidStarIcon className="h-5 w-5" />
+    ) : (
+        <OutlineStarIcon className="h-5 w-5" />
+    );
+
+    const [presetOptions, setPresetOptions] = useState([
+        "administrator",
+        "volunteer",
+        "employee",
+    ]);
+    const [rolePresetOptions, setRolePresetOptions] = useState([
+        "domestic",
+        "community",
+        "economic",
+    ]);
+    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);
+    };
+
+    return (
+        <div>
+            <button
+                className={
+                    "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"
+                }
+                onClick={toggleDrawer}
+            >
+                Open
+            </button>
+            <div className={drawerClassName}></div>
+            <div className={drawerClassName}>
+                <div className="flex items-center justify-between p-4">
+                    <div className="flex flex-row items-center justify-between space-x-2">
+                        <span className="h-5 text-purple-200 w-5">
+                            <UserIcon />
+                        </span>
+                        <h2 className="text-lg text-gray-800 font-semibold">
+                            {rowContent.username}
+                        </h2>
+                    </div>
+                    <div>
+                        <button
+                            onClick={toggleFavorite}
+                            className="py-2 text-gray-500 hover:text-gray-800 mr-2"
+                        >
+                            {favoriteIcon}
+                        </button>
+                        <button
+                            onClick={toggleDrawerFullScreen}
+                            className="py-2 text-gray-500 hover:text-gray-800 mr-2"
+                        >
+                            {iconComponent}
+                        </button>
+                        <button
+                            onClick={toggleDrawer}
+                            className="py-2 text-gray-500 hover:text-gray-800"
+                        >
+                            <ChevronDoubleLeftIcon className="h-5 w-5" />
+                        </button>
+                    </div>
+                </div>
+                <div className="p-4">
+                    <table className="p-4">
+                        <tbody className="items-center">
+                            <tr className="w-full text-xs items-center flex flex-row justify-between">
+                                <div className="flex flex-row space-x-2 text-gray-500 items-center">
+                                    <td>
+                                        <UserIcon className="h-4 w-4" />
+                                    </td>
+                                    <td className="w-32">Username</td>
+                                </div>
+                                <td className="w-3/4 w-3/4 p-2 pl-0">
+                                    <input
+                                        type="text"
+                                        name="username"
+                                        value={tempRowContent.username}
+                                        onChange={handleTempRowContentChange}
+                                        onKeyDown={handleEnterPress}
+                                        className="ml-2 w-full p-1 focus:outline-gray-200  hover:bg-gray-50"
+                                    />
+                                </td>
+                            </tr>
+                            <tr className="w-full text-xs items-center flex flex-row justify-between">
+                                <div className="flex flex-row space-x-2 text-gray-500 items-center">
+                                    <td>
+                                        <ListBulletIcon className="h-4 w-4" />
+                                    </td>
+                                    <td className="w-32">Role</td>
+                                </div>
+                                <td className="w-3/4 hover:bg-gray-50">
+                                    <TagsInput
+                                        presetValue={tempRowContent.role}
+                                        presetOptions={presetOptions}
+                                        setPresetOptions={setPresetOptions}
+                                        getTagColor={getTagColor}
+                                        setTagColors={setTagColors}
+                                    />
+                                </td>
+                            </tr>
+                            <tr className="w-full text-xs items-center flex flex-row justify-between">
+                                <div className="flex flex-row space-x-2 text-gray-500 items-center">
+                                    <td>
+                                        <EnvelopeIcon className="h-4 w-4" />
+                                    </td>
+                                    <td className="w-32">Email</td>
+                                </div>
+                                <td className="w-3/4 p-2 pl-0">
+                                    <input
+                                        type="text"
+                                        name="email"
+                                        value={tempRowContent.email}
+                                        onChange={handleTempRowContentChange}
+                                        onKeyDown={handleEnterPress}
+                                        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>
+                            </tr>
+                            <tr className="w-full text-xs items-center flex flex-row justify-between">
+                                <div className="flex flex-row space-x-2 text-gray-500 items-center">
+                                    <td>
+                                        <ListBulletIcon className="h-4 w-4" />
+                                    </td>
+                                    <td className="w-32">Type of Program</td>
+                                </div>
+                                <td className="w-3/4 hover:bg-gray-50">
+                                    {/* {rowContent.program} */}
+                                    <TagsInput
+                                        presetValue={tempRowContent.program}
+                                        presetOptions={rolePresetOptions}
+                                        setPresetOptions={setRolePresetOptions}
+                                        getTagColor={getTagColor}
+                                        setTagColors={setTagColors}
+                                    />
+                                </td>
+                            </tr>
+                        </tbody>
+                    </table>
+                    <br />
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default Drawer;
diff --git a/compass/components/Table/Index.tsx b/compass/components/Table/Index.tsx
deleted file mode 100644
index 931b039..0000000
--- a/compass/components/Table/Index.tsx
+++ /dev/null
@@ -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>
-    );
-};
diff --git a/compass/components/Table/ResourceTable.tsx b/compass/components/Table/ResourceTable.tsx
new file mode 100644
index 0000000..a02162a
--- /dev/null
+++ b/compass/components/Table/ResourceTable.tsx
@@ -0,0 +1,89 @@
+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}/>
+}
diff --git a/compass/components/Table/RowOpenAction.tsx b/compass/components/Table/RowOpenAction.tsx
index 9a7103c..3678630 100644
--- a/compass/components/Table/RowOpenAction.tsx
+++ b/compass/components/Table/RowOpenAction.tsx
@@ -1,28 +1,34 @@
-import Drawer from "@/components/Drawer/Drawer";
-import { ChangeEvent, useState } from "react";
-
-export const RowOpenAction = ({ title, rowData, onRowUpdate }) => {
-    const [pageContent, setPageContent] = useState("");
-
-    const handleDrawerContentChange = (newContent) => {
-        setPageContent(newContent);
-    };
-
-    return (
-        <div className="font-semibold group flex flex-row items-center justify-between pr-2">
-            {title}
-            <span>
-                {/* Added OnRowUpdate to drawer */}
-                <Drawer
-                    title="My Drawer Title"
-                    editableContent={pageContent}
-                    rowContent={rowData}
-                    onSave={handleDrawerContentChange}
-                    onRowUpdate={onRowUpdate}
-                >
-                    {pageContent}
-                </Drawer>
-            </span>
-        </div>
-    );
-};
+import Drawer from "@/components/Drawer/Drawer";
+import DataPoint from "@/utils/models/DataPoint";
+import { Dispatch, SetStateAction, useState } from "react";
+
+type RowOpenActionProps<T extends DataPoint> = {
+    title: string,
+    rowData: T,
+    setData: Dispatch<SetStateAction<T[]>>
+}
+
+export function RowOpenAction<T extends DataPoint>({ title, rowData, setData }: RowOpenActionProps<T>) {
+    const [pageContent, setPageContent] = useState("");
+
+    const handleDrawerContentChange = (newContent: string) => {
+        setPageContent(newContent);
+    };
+
+    return (
+        <div className="font-semibold group flex flex-row items-center justify-between pr-2">
+            {title}
+            <span>
+                <Drawer
+                    title="My Drawer Title"
+                    editableContent={pageContent}
+                    rowContent={rowData}
+                    onSave={handleDrawerContentChange}
+                    setData={setData}
+                >
+                    {pageContent}
+                </Drawer>
+            </span>
+        </div>
+    );
+};
diff --git a/compass/components/Table/ServiceIndex.tsx b/compass/components/Table/ServiceIndex.tsx
deleted file mode 100644
index 6895984..0000000
--- a/compass/components/Table/ServiceIndex.tsx
+++ /dev/null
@@ -1,312 +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 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>
-    );
-};
diff --git a/compass/components/Table/ServiceTable.tsx b/compass/components/Table/ServiceTable.tsx
new file mode 100644
index 0000000..05e42b2
--- /dev/null
+++ b/compass/components/Table/ServiceTable.tsx
@@ -0,0 +1,108 @@
+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} />
+};
diff --git a/compass/components/Table/ResourceIndex.tsx b/compass/components/Table/Table.tsx
similarity index 55%
rename from compass/components/Table/ResourceIndex.tsx
rename to compass/components/Table/Table.tsx
index a714836..ef0a002 100644
--- a/compass/components/Table/ResourceIndex.tsx
+++ b/compass/components/Table/Table.tsx
@@ -1,304 +1,224 @@
-// 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 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("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 { 
+    Row,
+    ColumnDef,
+    useReactTable,
+    getCoreRowModel,
+    flexRender,
+    createColumnHelper
+ } from "@tanstack/react-table";
+import {
+    ChangeEvent,
+    useState,
+    useEffect,
+    Key,
+    Dispatch,
+    SetStateAction
+} from "react";
+import { TableAction } from "./TableAction";
+import { PlusIcon } from "@heroicons/react/24/solid";
+import { rankItem } from "@tanstack/match-sorter-utils";
+import { RowOptionMenu } from "./RowOptionMenu";
+import DataPoint from "@/utils/models/DataPoint";
+
+type TableProps<T extends DataPoint> = {
+    data: T[],
+    setData: Dispatch<SetStateAction<T[]>>,
+    columns: ColumnDef<T, any>[]
+};
+
+/** Fuzzy search function */
+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;
+};
+
+/**
+ * General componenet that holds shared functionality for any data table component
+ * @param props.data Stateful list of data to be held in the table
+ * @param props.setData State setter for the list of data
+ * @param props.columns Column definitions made with Tanstack columnHelper
+*/
+export default function Table<T extends DataPoint>({ data, setData, columns }: TableProps<T>) {
+    const columnHelper = createColumnHelper<T>();
+    
+    /** Sorting function based on visibility */
+    const visibilitySort = (a: T, b: T) => (
+        a.visible === b.visible 
+        ? 0 
+        : a.visible ? -1 : 1
+    )
+
+    // Sort data on load
+    useEffect(() => {
+        setData(prevData => prevData.sort(visibilitySort))
+    }, [setData]);
+
+    // Data manipulation methods
+    // TODO: Connect data manipulation methods to the database (deleteData, hideData, addData)
+    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 => (
+                    data.id === dataId
+                    ? { ...data, visible: !data.visible }
+                    : data
+                ))
+                .sort(visibilitySort);
+
+            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("");
+    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
+
+    // Define Tanstack table
+    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 isDataVisible = row.original.visible;
+                        const rowClassNames = `text-gray-800 border-y lowercase hover:bg-gray-50 ${
+                            !isDataVisible ? "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={addData}
+                        >
+                            <span className="flex ml-1 text-gray-500">
+                                <PlusIcon className="inline h-4 mr-1" />
+                                New
+                            </span>
+                        </td>
+                    </tr>
+                </tfoot>
+            </table>
+        </div>
+    );
+};
diff --git a/compass/components/Table/UserTable.tsx b/compass/components/Table/UserTable.tsx
new file mode 100644
index 0000000..0f81d66
--- /dev/null
+++ b/compass/components/Table/UserTable.tsx
@@ -0,0 +1,95 @@
+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}/>
+}
diff --git a/compass/components/TagsInput/Index.tsx b/compass/components/TagsInput/Index.tsx
index 19c2a77..f4b021e 100644
--- a/compass/components/TagsInput/Index.tsx
+++ b/compass/components/TagsInput/Index.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useRef } from "react";
+import React, { useState, useRef, Dispatch, SetStateAction } from "react";
 import "tailwindcss/tailwind.css";
 import { TagsArray } from "./TagsArray";
 import { TagDropdown } from "./TagDropdown";
@@ -7,8 +7,8 @@ import { CreateNewTagAction } from "./CreateNewTagAction";
 interface TagsInputProps {
     presetOptions: string[];
     presetValue: string | string[];
-    setPresetOptions: () => {};
-    getTagColor: () => {};
+    setPresetOptions: Dispatch<SetStateAction<string[]>>;
+    getTagColor(tag: string): string;
 }
 
 const TagsInput: React.FC<TagsInputProps> = ({
diff --git a/compass/components/TagsInput/TagsArray.tsx b/compass/components/TagsInput/TagsArray.tsx
index c014e7c..845e739 100644
--- a/compass/components/TagsInput/TagsArray.tsx
+++ b/compass/components/TagsInput/TagsArray.tsx
@@ -7,7 +7,7 @@ export interface Tags {
 }
 
 export const TagsArray = ({ tags, handleDelete, active = false }: Tags) => {
-    console.log(tags);
+    // console.log(tags);
 
     return (
         <div className="flex ml-2 flex-wrap gap-2 items-center">
diff --git a/compass/components/TagsInput/TagsHandler.tsx b/compass/components/TagsInput/TagsHandler.tsx
new file mode 100644
index 0000000..e8fde75
--- /dev/null
+++ b/compass/components/TagsInput/TagsHandler.tsx
@@ -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 }
+}
\ No newline at end of file
diff --git a/compass/utils/models/DataPoint.ts b/compass/utils/models/DataPoint.ts
new file mode 100644
index 0000000..b7dcacb
--- /dev/null
+++ b/compass/utils/models/DataPoint.ts
@@ -0,0 +1,9 @@
+/**
+ * Represents metadata of the Resource, Service, and User models to be used in a table
+ */
+interface DataPoint {
+    id: number;
+    visible: boolean;
+}
+
+export default DataPoint;
\ No newline at end of file

From 99e43c7b309d36e64086b6bd33100c0854ff30e8 Mon Sep 17 00:00:00 2001
From: Emma Foster <111466810+emmalynfoster@users.noreply.github.com>
Date: Mon, 4 Nov 2024 15:10:58 -0500
Subject: [PATCH 3/3] Resource Service and Tests (#41)

* Implement Resource service, tests, and test data with 97% coverage.

* Update slug service to return empty list and corresponding tests

* Update resource update tests to grab resource by id from the db to check
---
 backend/services/resource.py                |  45 ++++---
 backend/test/conftest.py                    |   3 +-
 backend/test/services/fixtures.py           |   8 +-
 backend/test/services/resource_test.py      | 126 ++++++++++++++++++++
 backend/test/services/resource_test_data.py |  22 +++-
 5 files changed, 174 insertions(+), 30 deletions(-)
 create mode 100644 backend/test/services/resource_test.py

diff --git a/backend/services/resource.py b/backend/services/resource.py
index 2648ad6..67959c7 100644
--- a/backend/services/resource.py
+++ b/backend/services/resource.py
@@ -35,17 +35,15 @@ class ResourceService:
     def create(self, user: User, resource: Resource) -> Resource:
         """
         Creates a resource based on the input object and adds it to the table if the user has the right permissions.
-
         Parameters:
             user: a valid User model representing the currently logged in User
             resource: Resource object to add to table
-
         Returns:
             Resource: Object added to table
         """
-        if resource.role != user.role or resource.group != user.group:
+        if user.role != UserTypeEnum.ADMIN:
             raise PermissionError(
-                "User does not have permission to add resources in this role or group."
+                "User does not have permission to add resources in this program."
             )
 
         resource_entity = ResourceEntity.from_model(resource)
@@ -57,14 +55,11 @@ class ResourceService:
     def get_by_id(self, user: User, id: int) -> Resource:
         """
         Gets a resource based on the resource id that the user has access to
-
         Parameters:
             user: a valid User model representing the currently logged in User
             id: int, the id of the resource
-
         Returns:
             Resource
-
         Raises:
             ResourceNotFoundException: If no resource is found with id
         """
@@ -72,8 +67,7 @@ class ResourceService:
             self._session.query(ResourceEntity)
             .filter(
                 ResourceEntity.id == id,
-                ResourceEntity.role == user.role,
-                ResourceEntity.group == user.group,
+                ResourceEntity.program.in_(user.program),
             )
             .one_or_none()
         )
@@ -86,18 +80,15 @@ class ResourceService:
     def update(self, user: User, resource: ResourceEntity) -> Resource:
         """
         Update the resource if the user has access
-
         Parameters:
             user: a valid User model representing the currently logged in User
             resource (ResourceEntity): Resource to update
-
         Returns:
             Resource: Updated resource object
-
         Raises:
             ResourceNotFoundException: If no resource is found with the corresponding ID
         """
-        if resource.role != user.role or resource.group != user.group:
+        if user.role != UserTypeEnum.ADMIN:
             raise PermissionError(
                 "User does not have permission to update this resource."
             )
@@ -109,7 +100,11 @@ class ResourceService:
                 f"No resource found with matching id: {resource.id}"
             )
 
-        obj.update_from_model(resource)  # Assuming an update method exists
+        obj.name = resource.name
+        obj.summary = resource.summary
+        obj.link = resource.link
+        obj.program = resource.program
+
         self._session.commit()
 
         return obj.to_model()
@@ -117,20 +112,21 @@ class ResourceService:
     def delete(self, user: User, id: int) -> None:
         """
         Delete resource based on id that the user has access to
-
         Parameters:
             user: a valid User model representing the currently logged in User
             id: int, a unique resource id
-
         Raises:
             ResourceNotFoundException: If no resource is found with the corresponding id
         """
+        if user.role != UserTypeEnum.ADMIN:
+            raise PermissionError(
+                "User does not have permission to delete this resource."
+            )
+
         resource = (
             self._session.query(ResourceEntity)
             .filter(
                 ResourceEntity.id == id,
-                ResourceEntity.role == user.role,
-                ResourceEntity.group == user.group,
             )
             .one_or_none()
         )
@@ -144,22 +140,21 @@ class ResourceService:
     def get_by_slug(self, user: User, search_string: str) -> list[Resource]:
         """
         Get a list of resources given a search string that the user has access to
-
         Parameters:
             user: a valid User model representing the currently logged in User
             search_string: a string to search resources by
-
         Returns:
             list[Resource]: list of resources relating to the string
-
         Raises:
             ResourceNotFoundException if no resource is found with the corresponding slug
         """
         query = select(ResourceEntity).where(
-            ResourceEntity.title.ilike(f"%{search_string}%"),
-            ResourceEntity.role == user.role,
-            ResourceEntity.group == user.group,
+            ResourceEntity.name.ilike(f"%{search_string}%"),
+            ResourceEntity.program.in_(user.program)
         )
         entities = self._session.scalars(query).all()
 
-        return [entity.to_model() for entity in entities]
+        if not entities:
+            return []
+
+        return [entity.to_model() for entity in entities]
\ No newline at end of file
diff --git a/backend/test/conftest.py b/backend/test/conftest.py
index b91a2ac..f231859 100644
--- a/backend/test/conftest.py
+++ b/backend/test/conftest.py
@@ -4,7 +4,7 @@ import pytest
 from sqlalchemy import Engine, create_engine, text
 from sqlalchemy.orm import Session
 from sqlalchemy.exc import OperationalError
-from .services import user_test_data, tag_test_data, service_test_data
+from .services import user_test_data, tag_test_data, service_test_data, resource_test_data
 
 from ..database import _engine_str
 from ..env import getenv
@@ -57,5 +57,6 @@ def setup_insert_data_fixture(session: Session):
     user_test_data.insert_fake_data(session)
     tag_test_data.insert_fake_data(session)
     service_test_data.insert_fake_data(session)
+    resource_test_data.insert_fake_data(session)
     session.commit()
     yield
diff --git a/backend/test/services/fixtures.py b/backend/test/services/fixtures.py
index 9fb349a..213f1bf 100644
--- a/backend/test/services/fixtures.py
+++ b/backend/test/services/fixtures.py
@@ -6,6 +6,7 @@ from sqlalchemy.orm import Session
 from ...services import UserService
 from ...services import TagService
 from ...services import ServiceService
+from ...services import ResourceService
 
 
 
@@ -23,4 +24,9 @@ def tag_svc(session: Session):
 @pytest.fixture()
 def service_svc(session: Session):
     """This fixture is used to test the ServiceService class"""
-    return ServiceService(session)
\ No newline at end of file
+    return ServiceService(session)
+
+@pytest.fixture()
+def resource_svc(session: Session):
+    """This fixture is used to test the ResourceService class"""
+    return ResourceService(session)
\ No newline at end of file
diff --git a/backend/test/services/resource_test.py b/backend/test/services/resource_test.py
new file mode 100644
index 0000000..7d9b007
--- /dev/null
+++ b/backend/test/services/resource_test.py
@@ -0,0 +1,126 @@
+from backend.models.user_model import User
+from backend.entities.resource_entity import ResourceEntity
+from ...models.enum_for_models import ProgramTypeEnum
+from backend.services.resource import ResourceService
+from backend.services.tag import TagService
+from backend.services.exceptions import ResourceNotFoundException
+from . import resource_test_data
+from . import user_test_data
+from .fixtures import resource_svc, user_svc, tag_svc
+from backend.models.resource_model import Resource
+import pytest
+
+
+def test_get_resource_by_user_volunteer(resource_svc: ResourceService): 
+    """ Test getting resources by a volunteer """
+    resources = resource_svc.get_resource_by_user(user_test_data.volunteer)
+    assert len(resources) == 2
+    assert isinstance(resources[0], Resource)
+
+def test_get_resources_admin(resource_svc: ResourceService):
+    """ Test getting resources by an admin """
+    resources = resource_svc.get_resource_by_user(user_test_data.admin)
+    assert len(resources) == len(resource_test_data.resources)
+    assert isinstance(resources[0], Resource)
+
+def test_get_resources_employee(resource_svc: ResourceService):
+    """ Test getting by an employee """
+    resources = resource_svc.get_resource_by_user(user_test_data.employee)
+    assert len(resources) == 5
+    assert isinstance(resources[0], Resource)
+
+def test_create_resource_admin(resource_svc: ResourceService):
+    """ Test creating resources as an admin """
+    resource = resource_svc.create(user_test_data.admin, resource_test_data.resource6)
+    assert resource.name == resource_test_data.resource6.name
+    assert isinstance(resource, Resource)
+
+def test_create_not_permitted(resource_svc: ResourceService):
+    """ Test creating resources without permission """
+    with pytest.raises(PermissionError):
+        resource = resource_svc.create(user_test_data.volunteer, resource_test_data.resource1)
+        pytest.fail()
+
+def test_get_by_id(resource_svc: ResourceService):
+    """ Test getting a resource by id as an admin """
+    test_resource = resource_test_data.resource1
+    resource = resource_svc.get_by_id(user_test_data.admin, test_resource.id)
+    assert resource is not None
+    assert resource.id == test_resource.id
+    assert resource.name == test_resource.name
+
+def test_get_by_id_no_access(resource_svc: ResourceService):
+    """ Test getting a resourced with an id no accessible to an employee """
+    test_resource = resource_test_data.resource2
+    with pytest.raises(ResourceNotFoundException):
+        resource = resource_svc.get_by_id(user_test_data.employee, test_resource.id)
+        pytest.fail()
+
+def test_update(resource_svc: ResourceService):
+    """ Test updating a resource by an admin """
+    updated_resource = resource_test_data.resource5_new
+    resource = resource_svc.update(user_test_data.admin, updated_resource)
+    db_resource = resource_svc.get_by_id(user_test_data.admin, resource.id)
+    assert resource.id == updated_resource.id
+    assert resource.name == updated_resource.name
+    assert resource.summary == updated_resource.summary
+    assert resource.link == updated_resource.link
+    assert resource.program == updated_resource.program
+    assert db_resource.id == updated_resource.id
+    assert db_resource.name == updated_resource.name
+    assert db_resource.summary == updated_resource.summary
+    assert db_resource.link == updated_resource.link
+    assert db_resource.program == updated_resource.program
+
+
+def test_update_no_permission(resource_svc: ResourceService):
+    """ Test updating a resource without permission """
+    with pytest.raises(PermissionError):
+        resource = resource_svc.update(user_test_data.employee, resource_test_data.resource5_new)
+        pytest.fail()
+
+def test_delete(resource_svc: ResourceService):
+    """ Test deleting a resource as an admin """
+    resource_svc.delete(user_test_data.admin, resource_test_data.resource5.id)
+    resources = resource_svc.get_resource_by_user(user_test_data.admin)
+    assert len(resources) == len(resource_test_data.resources) - 1
+
+def test_delete_no_permission(resource_svc: ResourceService):
+    """ Test deleting a resource with no permission """
+    with pytest.raises(PermissionError):
+        resource = resource_svc.delete(user_test_data.employee, resource_test_data.resource5.id)
+        pytest.fail()
+
+def test_get_1_by_slug(resource_svc: ResourceService):
+    """ Test getting 1 resource with a specific search """
+    resource_to_test = resource_test_data.resource1
+    slug = "Resource 1"
+    resources = resource_svc.get_by_slug(user_test_data.admin, slug)
+    assert len(resources) == 1
+    resource = resources[0]
+    assert resource.id == resource_to_test.id 
+    assert resource.name == resource_to_test.name 
+    assert resource.summary == resource_to_test.summary 
+    assert resource.link == resource_to_test.link 
+    assert resource.program == resource_to_test.program
+
+def test_get_by_slug(resource_svc: ResourceService):
+    """ Test a generic search to get all resources """
+    slug = "Resource"
+    resources = resource_svc.get_by_slug(user_test_data.admin, slug)
+    assert len(resources) == 5
+
+def test_get_by_slug_not_found(resource_svc: ResourceService):
+    """ Test getting a resource that does not exist """
+    slug = "Not Found"
+    resources = resource_svc.get_by_slug(user_test_data.admin, slug)
+    assert len(resources) == 0
+    assert resources == []
+
+
+def test_get_by_slug_no_permission(resource_svc: ResourceService):
+    """ Test getting a resource the user does not have access to """
+    slug = "Resource 2"
+    resources = resource_svc.get_by_slug(user_test_data.employee, slug)
+    assert len(resources) == 0
+    assert resources == []
\ No newline at end of file
diff --git a/backend/test/services/resource_test_data.py b/backend/test/services/resource_test_data.py
index bb39266..7634df7 100644
--- a/backend/test/services/resource_test_data.py
+++ b/backend/test/services/resource_test_data.py
@@ -50,6 +50,24 @@ resource5 = Resource(
     created_at=datetime(2023, 6, 5, 11, 30, 0),
 )
 
+resource6 = Resource(
+    id=6,
+    name="Resource 6",
+    summary="New Financial Resource",
+    link="https://example.com/resource6",
+    program=ProgramTypeEnum.ECONOMIC,
+    created_at=datetime(2024, 6, 5, 11, 30, 0),
+)
+
+resource5_new = Resource(
+    id=5,
+    name="Resource 5",
+    summary = "Updated shelter and housing resources",
+    link="https://example.com/resource5/new",
+    program=ProgramTypeEnum.DOMESTIC,
+    created_at=datetime(2023, 6, 5, 11, 30, 0),
+)
+
 resources = [resource1, resource2, resource3, resource4, resource5]
 
 resource_1 = Resource(
@@ -266,13 +284,11 @@ def reset_table_id_seq(
     next_id: int,
 ) -> None:
     """Reset the ID sequence of an entity table.
-
     Args:
         session (Session) - A SQLAlchemy Session
         entity (DeclarativeBase) - The SQLAlchemy Entity table to target
         entity_id_column (MappedColumn) - The ID column (should be an int column)
         next_id (int) - Where the next inserted, autogenerated ID should begin
-
     Returns:
         None"""
     table = entity.__table__
@@ -312,4 +328,4 @@ def insert_fake_data(session: Session):
     reset_table_id_seq(session, ResourceEntity, ResourceEntity.id, len(resources) + 1)
 
     # Commit all changes
-    session.commit()
+    session.commit()
\ No newline at end of file