diff --git a/compass/components/Table/Index.tsx b/compass/components/Table/Index.tsx index 5699fb1..f07a20f 100644 --- a/compass/components/Table/Index.tsx +++ b/compass/components/Table/Index.tsx @@ -16,7 +16,8 @@ import { ChangeEvent, useState, useEffect, FunctionComponent, useRef, ChangeEven import { RowOptionMenu } from "./RowOptionMenu"; import { RowOpenAction } from "./RowOpenAction"; import { TableAction } from "./TableAction"; -import { Bars2Icon, AtSymbolIcon, HashtagIcon, ArrowDownCircleIcon, PlusIcon } from "@heroicons/react/24/solid"; +import { AtSymbolIcon, Bars2Icon, ArrowDownCircleIcon, PlusIcon } from "@heroicons/react/24/solid"; +import TagsInput from "../TagsInput/Index"; import { rankItem } from "@tanstack/match-sorter-utils"; import { TableCell } from "./TableCell"; import { PrimaryTableCell } from "./PrimaryTableCell"; @@ -27,14 +28,15 @@ type User = { id: number; created_at: any; username: string; - role: "ADMIN" | "EMPLOYEE" | "VOLUNTEER"; + role: "administrator" | "employee" | "volunteer"; email: string; - program: "DOMESTIC" | "ECONOMIC" | "COMMUNITY"; + program: "domestic" | "economic" | "community"; experience: number; group?: string; visible: boolean; }; + // For search const fuzzyFilter = (row: Row, columnId: string, value: any, addMeta: (meta: any) => void) => { // Rank the item @@ -74,6 +76,17 @@ export const Table = () => { 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({ @@ -81,12 +94,17 @@ export const Table = () => { cell: props => deleteUser(props.row.original.id)} onHide={() => hideUser(props.row.original.id)} /> }), columnHelper.accessor("username", { - header: () => <> Username, - cell: PrimaryTableCell, + header: () => <> Username, + cell: (info) => , }), columnHelper.accessor("role", { header: () => <> Role, - cell: TableCell, + cell: (info) => , }), columnHelper.accessor("email", { header: () => <> Email, @@ -94,7 +112,7 @@ export const Table = () => { }), columnHelper.accessor("program", { header: () => <> Program, - cell: TableCell, + cell: (info) => info.renderValue(), }), ]; @@ -119,6 +137,16 @@ export const Table = () => { // 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, @@ -149,6 +177,16 @@ export const Table = () => { } }); + 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 (
diff --git a/compass/components/Table/RowOpenAction.tsx b/compass/components/Table/RowOpenAction.tsx index 30d1cd3..a38ff51 100644 --- a/compass/components/Table/RowOpenAction.tsx +++ b/compass/components/Table/RowOpenAction.tsx @@ -1,7 +1,7 @@ import Drawer from "@/components/page/Drawer"; import {ChangeEvent, useState} from "react"; -export const RowOpenAction = ({ title }) => { +export const RowOpenAction = ({ title, rowData, onRowUpdate }) => { const [pageContent, setPageContent] = useState("") const handleDrawerContentChange = (newContent) => { @@ -14,7 +14,8 @@ export const RowOpenAction = ({ title }) => {
{title} - {pageContent} + {/* Added OnRowUpdate to drawer */} + {pageContent}
); 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..60f695c --- /dev/null +++ b/compass/components/TagsInput/Index.tsx @@ -0,0 +1,153 @@ +import React, { useState, useRef } 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, + setPresetOptions, + getTagColor +}) => { + 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 dropdown = useRef(null); + + const handleClick = () => { + if (!cellSelected) { + setCellSelected(true); + // Add event listener only after setting cellSelected to true + setTimeout(() => { + window.addEventListener("click", handleOutsideClick); + }, 100); + } + } + + const handleOutsideClick = (event) => { + if (dropdown.current && !dropdown.current.contains(event.target)) { + setCellSelected(false); + // Remove event listener after handling outside click + window.removeEventListener("click", handleOutsideClick); + } + }; + + const handleInputChange = (e: React.ChangeEvent) => { + setOptions(() => { + const newOptions = presetOptions.filter(item => item.includes(e.target.value.toLowerCase())); + return new Set(newOptions); + }) + setInputValue(e.target.value); // Update input value state + }; + + const handleAddTag = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && inputValue.trim()) { + // setPresetOptions((prevPreset) => { + // const uniqueSet = new Set(presetOptions); + // uniqueSet.add(inputValue); + // return Array.from(uniqueSet); + // }); + 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) => { + // setPresetOptions(presetOptions.filter(tag => tag !== tagToDelete)); + 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 ( +
+ {!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..c31950f --- /dev/null +++ b/compass/components/TagsInput/Tag.tsx @@ -0,0 +1,18 @@ +import { XMarkIcon } from "@heroicons/react/24/solid"; +import React, { useState, useEffect } from "react"; + +export const Tag = ({ children, handleDelete, active = false }) => { + + const [tagColor, setTagColor] = useState(''); + + return ( + + {children} + {active && handleDelete && ( + + )} + + ); +}; \ No newline at end of file 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/page/Card.tsx b/compass/components/page/Card.tsx new file mode 100644 index 0000000..39894cb --- /dev/null +++ b/compass/components/page/Card.tsx @@ -0,0 +1,29 @@ +import React, { ReactNode, useState } from "react"; + + +interface TagProps { + text: string; + icon: React.ReactNode; + onClick?: (event: React.MouseEvent) => void; + children?: React.ReactNode; +} + + +const Card: React.FC = ({children, text, icon, onClick }) => { + return ( + + ); +}; + +export default Card; diff --git a/compass/components/page/Drawer.tsx b/compass/components/page/Drawer.tsx index a60c447..d9d99ab 100644 --- a/compass/components/page/Drawer.tsx +++ b/compass/components/page/Drawer.tsx @@ -1,8 +1,10 @@ -import { FunctionComponent, ReactNode } from 'react'; -import Button from '@/components/Button' +import { FunctionComponent, ReactElement, ReactNode } from 'react'; import React, { useState } from 'react'; -import {DATATYPE} from '@/utils/constants' -import InlineLink from '@/components/InlineLink' +import { ChevronDoubleLeftIcon } from '@heroicons/react/24/solid'; +import { BookmarkIcon, XMarkIcon, StarIcon as SolidStarIcon, EnvelopeIcon, UserIcon } from "@heroicons/react/24/solid"; +import { ArrowsPointingOutIcon, ArrowsPointingInIcon, StarIcon as OutlineStarIcon, ListBulletIcon } from '@heroicons/react/24/outline'; +import Card from '@/components/page/Card' + type DrawerProps = { @@ -13,60 +15,140 @@ type DrawerProps = { disabled?: boolean; editableContent?: any; onSave?: (content: any) => void; + rowContent?: any; + onRowUpdate?: (content: any) => void; }; -const Drawer: FunctionComponent = ({ title, children, onSave, editableContent }) => { - const [isOpen, setIsOpen] = useState(false); - const [isEditing, setIsEditing] = useState(false); - const [editContent, setEditContent] = useState(editableContent || ''); +interface EditContent { + content: string; + isEditing: boolean; +} - const toggleDrawer = () => setIsOpen(!isOpen); - const toggleEditing = () => setIsEditing(!isEditing); - const handleContentChange = (event: React.ChangeEvent) => { - setEditContent(event.target.value); + +const Drawer: FunctionComponent = ({ title, children, onSave, editableContent, rowContent, onRowUpdate }) => { + const [isOpen, setIsOpen] = useState(false); + const [isFull, setIsFull] = useState(false); + const [currentCardIcon, setCurrentCardIcon] = useState(''); + 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" - }`; - const saveChanges = () => { - console.log(editContent); - if (onSave) { - onSave(editContent); - } - setIsEditing(false); - }; + } ${isFull ? "w-full" : "w-1/2"}`; - const addRow = () => { + const iconComponent = isFull ? : ; + + const favoriteIcon = isFavorite ? : - } return (
- + +
-

{title}

+
+ {currentCardIcon} +

{rowContent.username}

+
- - + + +
- {isEditing ? ( - <> - - Save - - ) : ( - children - )} + + + +
+
+ + + + + +
+
+ + + + + +
+
+ + + + + +
+
+ + + + + +
Username +
Role + {rowContent.role} +
Email +
Type of Program + {rowContent.program} +
+
@@ -74,3 +156,4 @@ const Drawer: FunctionComponent = ({ title, children, onSave, edita }; export default Drawer; + diff --git a/compass/components/page/DropDown.tsx b/compass/components/page/DropDown.tsx new file mode 100644 index 0000000..dfda408 --- /dev/null +++ b/compass/components/page/DropDown.tsx @@ -0,0 +1,30 @@ +import React, { ChangeEvent, FunctionComponent } from 'react'; + +// Define the shape of a single option +interface DropdownOption { + label: string; + value: string | number; +} + +// Define the props for the Dropdown component +interface DropdownProps { + options: DropdownOption[]; + onChange: (event: ChangeEvent) => void; // Type for change event on + {options.map((option, index) => ( + + ))} + + ); +}; + +export default Dropdown; \ No newline at end of file diff --git a/compass/components/page/Field.tsx b/compass/components/page/Field.tsx deleted file mode 100644 index 9ee44b1..0000000 --- a/compass/components/page/Field.tsx +++ /dev/null @@ -1,4 +0,0 @@ -// components/Field.tsx -import { FunctionComponent, ReactNode } from 'react'; -import Button from '@/components/Button' -import React, { useState } from 'react';