diff --git a/.DS_Store b/backend/.DS_Store similarity index 73% rename from .DS_Store rename to backend/.DS_Store index 2a70607..059e395 100644 Binary files a/.DS_Store and b/backend/.DS_Store differ diff --git a/compass/app/admin/layout.tsx b/compass/app/admin/layout.tsx new file mode 100644 index 0000000..bb7fddd --- /dev/null +++ b/compass/app/admin/layout.tsx @@ -0,0 +1,37 @@ +"use client" + +import Sidebar from '@/components/resource/Sidebar'; +import React, { useState } from 'react'; +import { ChevronDoubleRightIcon } from '@heroicons/react/24/outline'; + +export default function RootLayout({ + + children, +}: { + children: React.ReactNode +}) { + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + + return ( +
+ {/* button to open sidebar */} + + {/* sidebar */} +
+ +
+ {/* page ui */} +
+ {children} +
+
+ ) +} \ No newline at end of file diff --git a/compass/app/admin/page.tsx b/compass/app/admin/page.tsx new file mode 100644 index 0000000..b6461ae --- /dev/null +++ b/compass/app/admin/page.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { PageLayout } from "@/components/PageLayout"; +import { Table } from "@/components/Table/Index"; + +import { UsersIcon } from "@heroicons/react/24/solid"; + + + + + +export default function Page() { + + return ( +
+ {/* icon + title */} + }> + + + + ); +} diff --git a/compass/app/page.tsx b/compass/app/page.tsx deleted file mode 100644 index dba820a..0000000 --- a/compass/app/page.tsx +++ /dev/null @@ -1,86 +0,0 @@ -// pages/index.tsx -"use client"; - -import Button from '@/components/Button'; -import Input from '@/components/Input' -import InlineLink from '@/components/InlineLink'; -import Paper from '@/components/auth/Paper'; -// import { Metadata } from 'next' -import Image from 'next/image'; -import {ChangeEvent, useState} from "react"; - -// export const metadata: Metadata = { -// title: 'Login', -// } - -export default function Page() { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(""); - - const handleEmailChange = (event: React.ChangeEvent) => { - setEmail(event.currentTarget.value); - console.log("email " + email); - } - - const handlePasswordChange = (event: React.ChangeEvent) => { - setPassword(event.currentTarget.value); - console.log("password " + password) - } - - const handleClick = (event: React.MouseEvent) => { - event.preventDefault(); - // Priority: Incorrect combo > Missing email > Missing password - - if (password.trim().length === 0) { - setError("Please enter your password.") - } - // This shouldn't happen, already provides validation, but just in case. - if (email.trim().length === 0) { - setError("Please enter your email.") - } - // Placeholder for incorrect email + password combo. - if (email === "incorrect@gmail.com" && password) { - setError("Incorrect password.") - } - } - - - - return ( - <> - -
- -

Login

-
- -
-
- -
-
- - Forgot password? - - - - -
- -

- © 2024 Compass Center -

-
- - ); -}; \ No newline at end of file diff --git a/compass/app/pages/page/page.tsx b/compass/app/pages/page/page.tsx deleted file mode 100644 index 391c81c..0000000 --- a/compass/app/pages/page/page.tsx +++ /dev/null @@ -1,40 +0,0 @@ -// pages/index.tsx -"use client"; -import Image from 'next/image'; - - - -import Drawer from '@/components/page/Drawer'; -// import { Metadata } from 'next' -import {ChangeEvent, useState} from "react"; - -// export const metadata: Metadata = { -// title: 'Login', -// } - -export default function Page() { - const [pageContent, setPageContent] = useState("") - - const handleDrawerContentChange = (newContent) => { - setPageContent(newContent); - }; - - return ( -
-
-
- -

Untitled Page

-
- {pageContent} -
-
- ); -}; - diff --git a/compass/app/resource/page.tsx b/compass/app/resource/page.tsx index ca68cdb..2e4f4cd 100644 --- a/compass/app/resource/page.tsx +++ b/compass/app/resource/page.tsx @@ -9,7 +9,7 @@ export default function Page() { return (
{/* icon + title */} -
+
= ({ icon, title, children }) => { + return ( +
+ {/* icon + title */} +
+
+ {icon} +

{title}

+
+
+ {/* data */} +
+ {children} +
+
+ ); +}; diff --git a/compass/components/Table.tsx b/compass/components/Table.tsx new file mode 100644 index 0000000..ac55a42 --- /dev/null +++ b/compass/components/Table.tsx @@ -0,0 +1,45 @@ +import { Component } from "react" + +// interface TableHeader { +// title: string, +// type: string +// } + +// interface TableRow { +// [key: string]: any, +// } +interface TableProps { + headersData: string[]; + data: { [key: string]: any }[]; +} + +const Table: React.FC = ({ headersData, data }) => { + const headers = headersData.map((header, i) => { + return
+ }) + + console.log(data); + + const rows = data.map((item) => { + const row = headersData.map(key => { + return + }); + return {row} + }) + + return (<> +
{header}{item[key]}
+ + + {headers} + + + + {rows} + +
+ ); + +} + +export default Table diff --git a/compass/components/Table/Index.tsx b/compass/components/Table/Index.tsx new file mode 100644 index 0000000..7b5ee92 --- /dev/null +++ b/compass/components/Table/Index.tsx @@ -0,0 +1,113 @@ +// for showcasing to compass + +import usersImport from "./users.json"; +import { + ColumnDef, + createColumnHelper, + flexRender, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table"; +import { useState } from "react"; +import { RowOptionMenu } from "./RowOptionMenu"; +import { RowOpenAction } from "./RowOpenAction"; +import { TableAction } from "./TableAction"; +import { AtSymbolIcon, Bars2Icon } from "@heroicons/react/24/solid"; +import TagsInput from "../TagsInput/Index"; + +const usersExample = usersImport as unknown as User[]; + +type User = { + id: number; + created_at: any; + username: string; + role: "administrator" | "employee" | "volunteer"; + email: string; + program: "domestic" | "economic" | "community"; + experience: number; + group?: string; +}; + + + +export const Table = () => { + const columnHelper = createColumnHelper(); + const columns = [ + columnHelper.display({ + id: "options", + cell: props => + }), + columnHelper.accessor("username", { + header: () => <> Username, + cell: (info) => , + }), + columnHelper.accessor("role", { + cell: (info) => , + }), + columnHelper.accessor("email", { + header: () => <> Email, + cell: (info) => info.renderValue(), + }), + columnHelper.accessor("program", { + cell: (info) => info.renderValue(), + }), + ]; + + const [data, setData] = useState([...usersExample]); + + const table = useReactTable({ + columns, + data, + getCoreRowModel: getCoreRowModel(), + }); + + return ( +
+
+ +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header, i) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell, i) => ( + + ))} + + ))} + +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+ ) +} diff --git a/compass/components/Table/RowOpenAction.tsx b/compass/components/Table/RowOpenAction.tsx new file mode 100644 index 0000000..30d1cd3 --- /dev/null +++ b/compass/components/Table/RowOpenAction.tsx @@ -0,0 +1,21 @@ +import Drawer from "@/components/page/Drawer"; +import {ChangeEvent, useState} from "react"; + +export const RowOpenAction = ({ title }) => { + const [pageContent, setPageContent] = useState("") + + const handleDrawerContentChange = (newContent) => { + setPageContent(newContent); + }; + + + + return ( +
+ {title} + + {pageContent} + +
+ ); +}; diff --git a/compass/components/Table/RowOptionMenu.tsx b/compass/components/Table/RowOptionMenu.tsx new file mode 100644 index 0000000..f7a5c92 --- /dev/null +++ b/compass/components/Table/RowOptionMenu.tsx @@ -0,0 +1,26 @@ +//delete, duplicate, open +import { TrashIcon, DocumentDuplicateIcon, ArrowUpRightIcon, EllipsisVerticalIcon } from "@heroicons/react/24/solid"; +import { useState, useEffect, useRef } from "react"; + + +export const RowOptionMenu = () => { + const [menuOpen, setMenuOpen] = useState(false); + const openMenu = () => setMenuOpen(true); + const closeMenu = () => setMenuOpen(false); + + + // TODO: Hide menu if clicked elsewhere + + return ( + <> + +
*]:p-1 [&>*]:px-5 [&>*]:rounded" + (!menuOpen ? " invisible" : "")} + > +
Delete
+
Duplicate
+
Open
+
+ + ); +} diff --git a/compass/components/Table/TableAction.tsx b/compass/components/Table/TableAction.tsx new file mode 100644 index 0000000..f2a72a0 --- /dev/null +++ b/compass/components/Table/TableAction.tsx @@ -0,0 +1,36 @@ +import { MagnifyingGlassIcon } from "@heroicons/react/24/solid"; +import { useRef, useState } from "react"; + +export const TableAction = () => { + const searchInput = useRef(null); + const [searchActive, setSearchActive] = useState(false); + const activateSearch = () => { + setSearchActive(true); + if (searchInput.current === null) { return; } + searchInput.current.focus(); + searchInput.current.addEventListener("focusout", () => { + if (searchInput.current?.value.trim() === "") { + searchInput.current.value = ""; + deactivateSearch(); + } + }) + } + + const deactivateSearch = () => setSearchActive(false); + + return ( +
+ Filter + Sort + activateSearch()}> + + + +
+ ); +}; diff --git a/compass/components/Table/users.json b/compass/components/Table/users.json new file mode 100644 index 0000000..20caf99 --- /dev/null +++ b/compass/components/Table/users.json @@ -0,0 +1 @@ +[{"id":0,"created_at":1711482132230,"username":"Bo_Pfeffer","role":"ADMIN","email":"Bo.Pfeffer@gmail.com","program":"DOMESTIC","experience":2,"group":""},{"id":1,"created_at":1711482132231,"username":"Marianna_Heathcote76","role":"ADMIN","email":"Marianna_Heathcote14@yahoo.com","program":"DOMESTIC","experience":1,"group":""},{"id":2,"created_at":1711482132231,"username":"Queenie_Schroeder","role":"VOLUNTEER","email":"Queenie_Schroeder@yahoo.com","program":"COMMUNITY","experience":5,"group":""},{"id":3,"created_at":1711482132231,"username":"Arne.Bode","role":"VOLUNTEER","email":"Arne.Bode@hotmail.com","program":"DOMESTIC","experience":3,"group":""},{"id":4,"created_at":1711482132231,"username":"Maia.Zulauf9","role":"ADMIN","email":"Maia_Zulauf@gmail.com","program":"DOMESTIC","experience":5,"group":""},{"id":5,"created_at":1711482132231,"username":"River_Bauch","role":"EMPLOYEE","email":"River.Bauch@yahoo.com","program":"ECONOMIC","experience":2,"group":""},{"id":6,"created_at":1711482132231,"username":"Virgil.Hilll","role":"VOLUNTEER","email":"Virgil.Hilll@yahoo.com","program":"ECONOMIC","experience":3,"group":""},{"id":7,"created_at":1711482132231,"username":"Bridget_Cartwright","role":"ADMIN","email":"Bridget_Cartwright@yahoo.com","program":"ECONOMIC","experience":3,"group":""},{"id":8,"created_at":1711482132231,"username":"Glennie_Keebler64","role":"EMPLOYEE","email":"Glennie_Keebler60@yahoo.com","program":"DOMESTIC","experience":2,"group":""},{"id":9,"created_at":1711482132232,"username":"Orin.Jenkins53","role":"EMPLOYEE","email":"Orin.Jenkins@gmail.com","program":"ECONOMIC","experience":1,"group":""},{"id":10,"created_at":1711482132232,"username":"Zachery.Rosenbaum","role":"ADMIN","email":"Zachery.Rosenbaum@hotmail.com","program":"COMMUNITY","experience":3,"group":""},{"id":11,"created_at":1711482132232,"username":"Phoebe.Ziemann","role":"EMPLOYEE","email":"Phoebe_Ziemann92@gmail.com","program":"COMMUNITY","experience":2,"group":""},{"id":12,"created_at":1711482132232,"username":"Bradford_Conroy53","role":"VOLUNTEER","email":"Bradford_Conroy94@hotmail.com","program":"COMMUNITY","experience":2,"group":""},{"id":13,"created_at":1711482132232,"username":"Florine_Strosin55","role":"VOLUNTEER","email":"Florine.Strosin29@hotmail.com","program":"ECONOMIC","experience":1,"group":""},{"id":14,"created_at":1711482132232,"username":"Constance.Doyle59","role":"EMPLOYEE","email":"Constance_Doyle@hotmail.com","program":"DOMESTIC","experience":3,"group":""},{"id":15,"created_at":1711482132232,"username":"Chauncey_Lockman","role":"ADMIN","email":"Chauncey_Lockman@yahoo.com","program":"DOMESTIC","experience":5,"group":""},{"id":16,"created_at":1711482132232,"username":"Esther_Wuckert-Larson26","role":"EMPLOYEE","email":"Esther_Wuckert-Larson@gmail.com","program":"ECONOMIC","experience":0,"group":""},{"id":17,"created_at":1711482132232,"username":"Jewel.Kunde","role":"VOLUNTEER","email":"Jewel_Kunde29@gmail.com","program":"ECONOMIC","experience":5,"group":""},{"id":18,"created_at":1711482132232,"username":"Hildegard_Parker92","role":"ADMIN","email":"Hildegard_Parker74@yahoo.com","program":"ECONOMIC","experience":2,"group":""},{"id":19,"created_at":1711482132232,"username":"Jordane.Lakin2","role":"ADMIN","email":"Jordane_Lakin@hotmail.com","program":"COMMUNITY","experience":1,"group":""}] \ No newline at end of file diff --git a/compass/components/TagsInput/CreateNewTagAction.tsx b/compass/components/TagsInput/CreateNewTagAction.tsx new file mode 100644 index 0000000..f906270 --- /dev/null +++ b/compass/components/TagsInput/CreateNewTagAction.tsx @@ -0,0 +1,10 @@ +import { Tag } from "./Tag" + +export const CreateNewTagAction = ({ input }) => { + return ( +
+

Create

+ {input} +
+ ) +} \ No newline at end of file diff --git a/compass/components/TagsInput/DropdownAction.tsx b/compass/components/TagsInput/DropdownAction.tsx new file mode 100644 index 0000000..7c9b7d1 --- /dev/null +++ b/compass/components/TagsInput/DropdownAction.tsx @@ -0,0 +1,49 @@ +import { EllipsisHorizontalIcon, TrashIcon } from "@heroicons/react/24/solid"; +import { useState } from "react"; + +export const DropdownAction = ({ tag, handleDeleteTag, handleEditTag }) => { + const [isVisible, setVisible] = useState(false); + const [inputValue, setInputValue] = useState(tag); + + + const editTagOption = (e) => { + if (e.key === 'Enter') { + handleEditTag(tag, inputValue) + setVisible(false); + } + }; + + + const handleInputChange = (e: React.ChangeEvent) => { + setInputValue(e.target.value); + }; + + + return ( +
+ setVisible(!isVisible)} + /> + {isVisible && ( +
+ + +
+ )} +
+ ); +}; diff --git a/compass/components/TagsInput/Index.tsx b/compass/components/TagsInput/Index.tsx new file mode 100644 index 0000000..ca0c947 --- /dev/null +++ b/compass/components/TagsInput/Index.tsx @@ -0,0 +1,122 @@ +import React, { useState } from "react"; +import "tailwindcss/tailwind.css"; +import { TagsArray } from "./TagsArray"; +import { TagDropdown } from "./TagDropdown"; +import { CreateNewTagAction } from "./CreateNewTagAction"; + +interface TagsInputProps { + presetOptions: string[]; +} + +const TagsInput: React.FC = ({ + presetValue, + presetOptions, +}) => { + const [inputValue, setInputValue] = useState(""); + const [cellSelected, setCellSelected] = useState(false); + const [tags, setTags] = useState>( + new Set(presetValue ? [presetValue] : []) + ); + const [options, setOptions] = useState>(new Set(presetOptions)); + + const handleInputChange = (e: React.ChangeEvent) => { + setInputValue(e.target.value); + }; + + const handleAddTag = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && inputValue.trim()) { + setTags((prevTags) => new Set(prevTags).add(inputValue)); + setOptions((prevOptions) => new Set(prevOptions).add(inputValue)); + setInputValue(""); + } + }; + + const handleSelectTag = (tagToAdd: string) => { + if (!tags.has(tagToAdd)) { + // Corrected syntax for checking if a Set contains an item + setTags((prevTags) => new Set(prevTags).add(tagToAdd)); + } + }; + + const handleDeleteTag = (tagToDelete: string) => { + setTags((prevTags) => { + const updatedTags = new Set(prevTags); + updatedTags.delete(tagToDelete); + return updatedTags; + }); + }; + + const handleDeleteTagOption = (tagToDelete: string) => { + setOptions((prevOptions) => { + const updatedOptions = new Set(prevOptions); + updatedOptions.delete(tagToDelete); + return updatedOptions; + }); + if (tags.has(tagToDelete)) { + handleDeleteTag(tagToDelete); + } + }; + + const handleEditTag = (oldTag: string, newTag: string) => { + if (oldTag !== newTag) { + setTags((prevTags) => { + const tagsArray = Array.from(prevTags); + const oldTagIndex = tagsArray.indexOf(oldTag); + if (oldTagIndex !== -1) { + tagsArray.splice(oldTagIndex, 1, newTag); + } + return new Set(tagsArray); + }); + + setOptions((prevOptions) => { + const optionsArray = Array.from(prevOptions); + const oldTagIndex = optionsArray.indexOf(oldTag); + if (oldTagIndex !== -1) { + optionsArray.splice(oldTagIndex, 1, newTag); + } + return new Set(optionsArray); + }); + } + }; + + + + return ( +
setCellSelected(true)}> + {!cellSelected ? ( + + ) : ( +
+
+
+ + +
+
+

Select an option or create one

+ + {inputValue.length > 0 && ( + + )} +
+
+
+ )} +
+ ); +}; + +export default TagsInput; diff --git a/compass/components/TagsInput/Input.tsx b/compass/components/TagsInput/Input.tsx new file mode 100644 index 0000000..e69de29 diff --git a/compass/components/TagsInput/Tag.tsx b/compass/components/TagsInput/Tag.tsx new file mode 100644 index 0000000..1d5999c --- /dev/null +++ b/compass/components/TagsInput/Tag.tsx @@ -0,0 +1,15 @@ +import { XMarkIcon } from "@heroicons/react/24/solid"; + +export const Tag = ({ children, handleDelete, active = false }) => { + + return ( + + {children} + {active && handleDelete && ( + + )} + + ); +}; diff --git a/compass/components/TagsInput/TagDropdown.tsx b/compass/components/TagsInput/TagDropdown.tsx new file mode 100644 index 0000000..6f4558a --- /dev/null +++ b/compass/components/TagsInput/TagDropdown.tsx @@ -0,0 +1,16 @@ + +import { Tag } from "./Tag"; +import { DropdownAction } from "./DropdownAction"; + +export const TagDropdown = ({ tags, handleEditTag,handleDeleteTag,handleAdd }) => { + return ( +
+ {Array.from(tags).map((tag, index) => ( +
+ + +
+ ))} +
+ ); +}; diff --git a/compass/components/TagsInput/TagsArray.tsx b/compass/components/TagsInput/TagsArray.tsx new file mode 100644 index 0000000..59d8aa1 --- /dev/null +++ b/compass/components/TagsInput/TagsArray.tsx @@ -0,0 +1,16 @@ +import { Tag } from "./Tag" + +export const TagsArray = ({ tags, handleDelete, active = false }) => { + + return( +
+ { + Array.from(tags).map((tag) => { + return ( + {tag} + ) + }) + } +
+ ) +} \ No newline at end of file diff --git a/compass/components/resource/Sidebar.tsx b/compass/components/resource/Sidebar.tsx index 97e59d4..187f4a9 100644 --- a/compass/components/resource/Sidebar.tsx +++ b/compass/components/resource/Sidebar.tsx @@ -31,7 +31,7 @@ const Sidebar: React.FC = ({ setIsSidebarOpen }) => {

Pages