mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-21 18:59:49 -04:00
Compare commits
No commits in common. "a516c414f64a4c263490dc761a68848fce4c02e2" and "00ba6d7df1484b4a0af8f222ce19dd8f0b21e6d1" have entirely different histories.
a516c414f6
...
00ba6d7df1
|
@ -44,7 +44,7 @@ RUN mkdir -p /etc/apt/keyrings \
|
||||||
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list \
|
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install nodejs -y \
|
&& apt-get install nodejs -y \
|
||||||
&& npm install -g npm@10.8.2 \
|
&& npm install -g npm@latest \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Use a non-root user per https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user
|
# Use a non-root user per https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user
|
||||||
|
|
|
@ -77,8 +77,6 @@ export default function Page() {
|
||||||
alt="Compass Center logo."
|
alt="Compass Center logo."
|
||||||
width={100}
|
width={100}
|
||||||
height={91}
|
height={91}
|
||||||
style={{ height: "auto", width: "auto" }}
|
|
||||||
priority
|
|
||||||
/>
|
/>
|
||||||
<h1 className="font-bold text-2xl text-purple-800">Login</h1>
|
<h1 className="font-bold text-2xl text-purple-800">Login</h1>
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
|
|
|
@ -76,8 +76,6 @@ export default function Page() {
|
||||||
alt="Compass Center logo."
|
alt="Compass Center logo."
|
||||||
width={100}
|
width={100}
|
||||||
height={91}
|
height={91}
|
||||||
style={{ height: "auto", width: "auto" }}
|
|
||||||
priority
|
|
||||||
/>
|
/>
|
||||||
<h1 className="font-bold text-2xl text-purple-800">Login</h1>
|
<h1 className="font-bold text-2xl text-purple-800">Login</h1>
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
|
|
|
@ -14,56 +14,61 @@ import {
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import TagsInput from "../TagsInput/Index";
|
import TagsInput from "../TagsInput/Index";
|
||||||
|
|
||||||
type InputType =
|
|
||||||
| "text"
|
|
||||||
| "email"
|
|
||||||
| "textarea"
|
|
||||||
| "select-one"
|
|
||||||
| "select-multiple";
|
|
||||||
|
|
||||||
export interface Details {
|
|
||||||
key: string;
|
|
||||||
label: string;
|
|
||||||
inputType: InputType;
|
|
||||||
icon: ReactNode;
|
|
||||||
presetOptionsValues?: string[];
|
|
||||||
presetOptionsSetter?: Dispatch<SetStateAction<string[]>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type DrawerProps = {
|
type DrawerProps = {
|
||||||
titleKey: string;
|
title: string;
|
||||||
details: Details[];
|
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;
|
rowContent?: any;
|
||||||
setRowContent?: Dispatch<SetStateAction<any>>;
|
setData: Dispatch<SetStateAction<any>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface EditContent {
|
||||||
|
content: string;
|
||||||
|
isEditing: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const Drawer: FunctionComponent<DrawerProps> = ({
|
const Drawer: FunctionComponent<DrawerProps> = ({
|
||||||
titleKey,
|
title,
|
||||||
details,
|
children,
|
||||||
|
onSave,
|
||||||
|
editableContent,
|
||||||
rowContent,
|
rowContent,
|
||||||
setRowContent,
|
setData,
|
||||||
}: DrawerProps) => {
|
}) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isFull, setIsFull] = useState(false);
|
const [isFull, setIsFull] = useState(false);
|
||||||
const [isFavorite, setIsFavorite] = useState(false);
|
const [isFavorite, setIsFavorite] = useState(false);
|
||||||
const [tempRowContent, setTempRowContent] = useState(rowContent);
|
const [tempRowContent, setTempRowContent] = useState(rowContent);
|
||||||
|
|
||||||
const onRowUpdate = (updatedRow: any) => {};
|
const onRowUpdate = (updatedRow: any) => {
|
||||||
|
setData((prevData: any) =>
|
||||||
|
prevData.map((row: any) =>
|
||||||
|
row.id === updatedRow.id ? updatedRow : row
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const handleTempRowContentChange = (
|
const handleTempRowContentChange = (e) => {
|
||||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
||||||
) => {
|
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setTempRowContent((prev: any) => ({
|
console.log(name);
|
||||||
...prev,
|
console.log(value);
|
||||||
|
setTempRowContent((prevContent) => ({
|
||||||
|
...prevContent,
|
||||||
[name]: value,
|
[name]: value,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEnterPress = (
|
const handleEnterPress = (e) => {
|
||||||
e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
||||||
) => {
|
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
// Update the rowContent with the temporaryRowContent
|
||||||
|
if (onRowUpdate) {
|
||||||
|
onRowUpdate(tempRowContent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -94,6 +99,34 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
||||||
<OutlineStarIcon 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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
|
@ -104,6 +137,7 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
||||||
>
|
>
|
||||||
Open
|
Open
|
||||||
</button>
|
</button>
|
||||||
|
<div className={drawerClassName}></div>
|
||||||
<div className={drawerClassName}>
|
<div className={drawerClassName}>
|
||||||
<div className="flex items-center justify-between p-4">
|
<div className="flex items-center justify-between p-4">
|
||||||
<div className="flex flex-row items-center justify-between space-x-2">
|
<div className="flex flex-row items-center justify-between space-x-2">
|
||||||
|
@ -111,7 +145,7 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
||||||
<UserIcon />
|
<UserIcon />
|
||||||
</span>
|
</span>
|
||||||
<h2 className="text-lg text-gray-800 font-semibold">
|
<h2 className="text-lg text-gray-800 font-semibold">
|
||||||
{rowContent[titleKey]}
|
{rowContent.username}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -136,116 +170,81 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="flex flex-col space-y-3">
|
<table className="p-4">
|
||||||
{details.map((detail, index) => {
|
<tbody className="items-center">
|
||||||
const value = tempRowContent[detail.key];
|
<tr className="w-full text-xs items-center flex flex-row justify-between">
|
||||||
let valueToRender = <></>;
|
<div className="flex flex-row space-x-2 text-gray-500 items-center">
|
||||||
|
<td>
|
||||||
switch (detail.inputType) {
|
<UserIcon className="h-4 w-4" />
|
||||||
case "select-one":
|
</td>
|
||||||
case "select-multiple":
|
<td className="w-32">Username</td>
|
||||||
valueToRender = (
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="hover:bg-gray-50 rounded-md px-2 py-1">
|
|
||||||
<TagsInput
|
|
||||||
presetValue={
|
|
||||||
typeof value ===
|
|
||||||
"string"
|
|
||||||
? [value]
|
|
||||||
: value
|
|
||||||
}
|
|
||||||
presetOptions={
|
|
||||||
detail.presetOptionsValues ||
|
|
||||||
[]
|
|
||||||
}
|
|
||||||
setPresetOptions={
|
|
||||||
detail.presetOptionsSetter ||
|
|
||||||
(() => {})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "textarea":
|
|
||||||
valueToRender = (
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="hover:bg-gray-50 rounded-md px-2 py-1">
|
|
||||||
<textarea
|
|
||||||
name={detail.key}
|
|
||||||
value={value}
|
|
||||||
onChange={
|
|
||||||
handleTempRowContentChange
|
|
||||||
}
|
|
||||||
onKeyDown={handleEnterPress}
|
|
||||||
rows={4}
|
|
||||||
onInput={(e) => {
|
|
||||||
const target =
|
|
||||||
e.target as HTMLTextAreaElement;
|
|
||||||
target.style.height =
|
|
||||||
"auto";
|
|
||||||
target.style.height =
|
|
||||||
target.scrollHeight +
|
|
||||||
"px";
|
|
||||||
}}
|
|
||||||
className="w-full p-2 focus:outline-none border border-gray-200 rounded-md resize-none font-normal bg-transparent"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "text":
|
|
||||||
valueToRender = (
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="hover:bg-gray-50 rounded-md px-2 py-1">
|
|
||||||
<input
|
|
||||||
type={detail.inputType}
|
|
||||||
name={detail.key}
|
|
||||||
value={value}
|
|
||||||
onChange={
|
|
||||||
handleTempRowContentChange
|
|
||||||
}
|
|
||||||
onKeyDown={handleEnterPress}
|
|
||||||
className="w-full p-1 focus:outline-gray-200 bg-transparent"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "email":
|
|
||||||
valueToRender = (
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="hover:bg-gray-50 rounded-md px-2 py-1">
|
|
||||||
<input
|
|
||||||
type={detail.inputType}
|
|
||||||
name={detail.key}
|
|
||||||
value={value}
|
|
||||||
onChange={
|
|
||||||
handleTempRowContentChange
|
|
||||||
}
|
|
||||||
onKeyDown={handleEnterPress}
|
|
||||||
className="w-full p-1 font-normal hover:text-gray-400 focus:outline-gray-200 underline text-gray-500 bg-transparent"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="flex items-center text-xs gap-3"
|
|
||||||
>
|
|
||||||
<div className="flex items-center text-gray-500">
|
|
||||||
{detail.icon}
|
|
||||||
</div>
|
|
||||||
<div className="w-32">{detail.label}</div>
|
|
||||||
{valueToRender}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
<td className="w-3/4 w-3/4 p-2 pl-0">
|
||||||
})}
|
<input
|
||||||
</div>
|
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 />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
import {
|
import { Bars2Icon } from "@heroicons/react/24/solid";
|
||||||
Bars2Icon,
|
|
||||||
DocumentTextIcon,
|
|
||||||
LinkIcon,
|
|
||||||
ListBulletIcon,
|
|
||||||
UserIcon,
|
|
||||||
} from "@heroicons/react/24/solid";
|
|
||||||
import { Dispatch, SetStateAction, useState } from "react";
|
import { Dispatch, SetStateAction, useState } from "react";
|
||||||
import useTagsHandler from "@/components/TagsInput/TagsHandler";
|
import useTagsHandler from "@/components/TagsInput/TagsHandler";
|
||||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
||||||
|
@ -12,8 +6,6 @@ import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
||||||
import Table from "@/components/Table/Table";
|
import Table from "@/components/Table/Table";
|
||||||
import TagsInput from "@/components/TagsInput/Index";
|
import TagsInput from "@/components/TagsInput/Index";
|
||||||
import Resource from "@/utils/models/Resource";
|
import Resource from "@/utils/models/Resource";
|
||||||
import { Details } from "../Drawer/Drawer";
|
|
||||||
import { Tag } from "../TagsInput/Tag";
|
|
||||||
|
|
||||||
type ResourceTableProps = {
|
type ResourceTableProps = {
|
||||||
data: Resource[];
|
data: Resource[];
|
||||||
|
@ -28,101 +20,60 @@ type ResourceTableProps = {
|
||||||
export default function ResourceTable({ data, setData }: ResourceTableProps) {
|
export default function ResourceTable({ data, setData }: ResourceTableProps) {
|
||||||
const columnHelper = createColumnHelper<Resource>();
|
const columnHelper = createColumnHelper<Resource>();
|
||||||
|
|
||||||
const [programPresets, setProgramPresets] = useState([
|
// Set up tag handling
|
||||||
"domestic",
|
const programProps = useTagsHandler(["community", "domestic", "economic"]);
|
||||||
"community",
|
|
||||||
"economic",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const resourceDetails: Details[] = [
|
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
label: "name",
|
|
||||||
inputType: "text",
|
|
||||||
icon: <UserIcon className="h-4 w-4" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "link",
|
|
||||||
label: "link",
|
|
||||||
inputType: "email",
|
|
||||||
icon: <LinkIcon className="h-4 w-4" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "program",
|
|
||||||
label: "program",
|
|
||||||
inputType: "select-one",
|
|
||||||
icon: <ListBulletIcon className="h-4 w-4" />,
|
|
||||||
presetOptionsValues: programPresets,
|
|
||||||
presetOptionsSetter: setProgramPresets,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "summary",
|
|
||||||
label: "summary",
|
|
||||||
inputType: "textarea",
|
|
||||||
icon: <DocumentTextIcon className="h-4 w-4" />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
// Define Tanstack columns
|
// Define Tanstack columns
|
||||||
const columns: ColumnDef<Resource, any>[] = [
|
const columns: ColumnDef<Resource, any>[] = [
|
||||||
columnHelper.accessor("name", {
|
columnHelper.accessor("name", {
|
||||||
header: () => (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
<UserIcon className="inline align-top h-4" /> Name
|
<Bars2Icon className="inline align-top h-4" /> Name
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<RowOpenAction
|
<RowOpenAction
|
||||||
title={info.getValue()}
|
title={info.getValue()}
|
||||||
titleKey="name"
|
|
||||||
rowData={info.row.original}
|
rowData={info.row.original}
|
||||||
setData={setData}
|
setData={setData}
|
||||||
details={resourceDetails}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("link", {
|
columnHelper.accessor("link", {
|
||||||
header: () => (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
<LinkIcon className="inline align-top h-4" /> Link
|
<Bars2Icon className="inline align-top h-4" /> Link
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<div className="flex items-start gap-2 px-2">
|
<a
|
||||||
<a
|
href={info.getValue()}
|
||||||
href={info.getValue()}
|
target={"_blank"}
|
||||||
target="_blank"
|
className="ml-2 text-gray-500 underline hover:text-gray-400"
|
||||||
className="text-gray-500 underline hover:text-gray-400 break-all"
|
>
|
||||||
>
|
{info.getValue()}
|
||||||
{info.getValue()}
|
</a>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("program", {
|
columnHelper.accessor("program", {
|
||||||
header: () => (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
<ListBulletIcon className="inline align-top h-4" /> Program
|
<Bars2Icon className="inline align-top h-4" /> Program
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<div className="flex flex-wrap gap-2 items-center px-2">
|
<TagsInput presetValue={info.getValue()} {...programProps} />
|
||||||
<Tag>{info.getValue()}</Tag>
|
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
columnHelper.accessor("summary", {
|
columnHelper.accessor("summary", {
|
||||||
header: () => (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
<DocumentTextIcon className="inline align-top h-4" />{" "}
|
<Bars2Icon className="inline align-top h-4" /> Summary
|
||||||
Summary
|
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<div className="flex items-start gap-2 px-2 py-1">
|
<span className="ml-2 text-gray-500">{info.getValue()}</span>
|
||||||
<span className="text-gray-500 max-h-8 overflow-y-auto">
|
|
||||||
{info.getValue()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,37 +1,37 @@
|
||||||
import Drawer, { Details } from "@/components/Drawer/Drawer";
|
import Drawer from "@/components/Drawer/Drawer";
|
||||||
import DataPoint from "@/utils/models/DataPoint";
|
import DataPoint from "@/utils/models/DataPoint";
|
||||||
import {
|
|
||||||
EnvelopeIcon,
|
|
||||||
ListBulletIcon,
|
|
||||||
UserIcon,
|
|
||||||
} from "@heroicons/react/24/solid";
|
|
||||||
import { Dispatch, SetStateAction, useState } from "react";
|
import { Dispatch, SetStateAction, useState } from "react";
|
||||||
|
|
||||||
type RowOpenActionProps<T extends DataPoint> = {
|
type RowOpenActionProps<T extends DataPoint> = {
|
||||||
title: string;
|
title: string;
|
||||||
titleKey: string;
|
|
||||||
rowData: T;
|
rowData: T;
|
||||||
setData: Dispatch<SetStateAction<T[]>>;
|
setData: Dispatch<SetStateAction<T[]>>;
|
||||||
details: Details[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function RowOpenAction<T extends DataPoint>({
|
export function RowOpenAction<T extends DataPoint>({
|
||||||
title,
|
title,
|
||||||
titleKey,
|
|
||||||
rowData,
|
rowData,
|
||||||
setData,
|
setData,
|
||||||
details,
|
|
||||||
}: RowOpenActionProps<T>) {
|
}: RowOpenActionProps<T>) {
|
||||||
|
const [pageContent, setPageContent] = useState("");
|
||||||
|
|
||||||
|
const handleDrawerContentChange = (newContent: string) => {
|
||||||
|
setPageContent(newContent);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="font-semibold group flex flex-row items-center justify-between pr-2">
|
<div className="font-semibold group flex flex-row items-center justify-between pr-2">
|
||||||
{title}
|
{title}
|
||||||
<span>
|
<span>
|
||||||
<Drawer
|
<Drawer
|
||||||
titleKey={titleKey}
|
title="My Drawer Title"
|
||||||
|
editableContent={pageContent}
|
||||||
rowContent={rowData}
|
rowContent={rowData}
|
||||||
details={details}
|
onSave={handleDrawerContentChange}
|
||||||
setRowContent={setData}
|
setData={setData}
|
||||||
/>
|
>
|
||||||
|
{pageContent}
|
||||||
|
</Drawer>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
import {
|
import { Bars2Icon } from "@heroicons/react/24/solid";
|
||||||
Bars2Icon,
|
import { Dispatch, SetStateAction } from "react";
|
||||||
CheckCircleIcon,
|
import useTagsHandler from "@/components/TagsInput/TagsHandler";
|
||||||
DocumentTextIcon,
|
|
||||||
ListBulletIcon,
|
|
||||||
UserIcon,
|
|
||||||
} from "@heroicons/react/24/solid";
|
|
||||||
import { Dispatch, SetStateAction, useState } from "react";
|
|
||||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
||||||
import Table from "@/components/Table/Table";
|
import Table from "@/components/Table/Table";
|
||||||
import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
||||||
|
import TagsInput from "@/components/TagsInput/Index";
|
||||||
import Service from "@/utils/models/Service";
|
import Service from "@/utils/models/Service";
|
||||||
import { Details } from "../Drawer/Drawer";
|
|
||||||
import { Tag } from "../TagsInput/Tag";
|
|
||||||
|
|
||||||
type ServiceTableProps = {
|
type ServiceTableProps = {
|
||||||
data: Service[];
|
data: Service[];
|
||||||
|
@ -26,13 +20,11 @@ type ServiceTableProps = {
|
||||||
export default function ServiceTable({ data, setData }: ServiceTableProps) {
|
export default function ServiceTable({ data, setData }: ServiceTableProps) {
|
||||||
const columnHelper = createColumnHelper<Service>();
|
const columnHelper = createColumnHelper<Service>();
|
||||||
|
|
||||||
const [programPresets, setProgramPresets] = useState([
|
// Set up tag handling
|
||||||
"domestic",
|
const programProps = useTagsHandler(["community", "domestic", "economic"]);
|
||||||
"community",
|
|
||||||
"economic",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [requirementPresets, setRequirementPresets] = useState([
|
// TODO: Dynamically or statically get full list of preset requirement tag options
|
||||||
|
const requirementProps = useTagsHandler([
|
||||||
"anonymous",
|
"anonymous",
|
||||||
"confidential",
|
"confidential",
|
||||||
"referral required",
|
"referral required",
|
||||||
|
@ -42,111 +34,67 @@ export default function ServiceTable({ data, setData }: ServiceTableProps) {
|
||||||
"initial assessment",
|
"initial assessment",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const serviceDetails: Details[] = [
|
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
label: "name",
|
|
||||||
inputType: "text",
|
|
||||||
icon: <UserIcon className="inline align-top h-4" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "status",
|
|
||||||
label: "status",
|
|
||||||
inputType: "text",
|
|
||||||
icon: <CheckCircleIcon className="inline align-top h-4" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "program",
|
|
||||||
label: "program",
|
|
||||||
inputType: "select-one",
|
|
||||||
icon: <ListBulletIcon className="inline align-top h-4" />,
|
|
||||||
presetOptionsValues: programPresets,
|
|
||||||
presetOptionsSetter: setProgramPresets,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "requirements",
|
|
||||||
label: "requirements",
|
|
||||||
inputType: "select-multiple",
|
|
||||||
icon: <ListBulletIcon className="inline align-top h-4" />,
|
|
||||||
presetOptionsValues: requirementPresets,
|
|
||||||
presetOptionsSetter: setRequirementPresets,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "summary",
|
|
||||||
label: "summary",
|
|
||||||
inputType: "textarea",
|
|
||||||
icon: <DocumentTextIcon className="inline align-top h-4" />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Define Tanstack columns
|
// Define Tanstack columns
|
||||||
const columns: ColumnDef<Service, any>[] = [
|
const columns: ColumnDef<Service, any>[] = [
|
||||||
columnHelper.accessor("name", {
|
columnHelper.accessor("name", {
|
||||||
header: () => (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
<UserIcon className="inline align-top h-4" /> Name
|
<Bars2Icon className="inline align-top h-4" /> Name
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<RowOpenAction
|
<RowOpenAction
|
||||||
title={info.getValue()}
|
title={info.getValue()}
|
||||||
titleKey="name"
|
|
||||||
rowData={info.row.original}
|
rowData={info.row.original}
|
||||||
setData={setData}
|
setData={setData}
|
||||||
details={serviceDetails}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("status", {
|
columnHelper.accessor("status", {
|
||||||
header: () => (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
<CheckCircleIcon className="inline align-top h-4" /> Status
|
<Bars2Icon className="inline align-top h-4" /> Status
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<span className="text-gray-500 px-2">{info.getValue()}</span>
|
<span className="ml-2 text-gray-500">{info.getValue()}</span>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("program", {
|
columnHelper.accessor("program", {
|
||||||
header: () => (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
<ListBulletIcon className="inline align-top h-4" /> Program
|
<Bars2Icon className="inline align-top h-4" /> Program
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<div className="flex flex-wrap gap-2 items-center px-2">
|
<TagsInput presetValue={info.getValue()} {...programProps} />
|
||||||
<Tag>{info.getValue()}</Tag>
|
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("requirements", {
|
columnHelper.accessor("requirements", {
|
||||||
header: () => (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
<ListBulletIcon className="inline align-top h-4" />{" "}
|
<Bars2Icon className="inline align-top h-4" /> Requirements
|
||||||
Requirements
|
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<div className="flex flex-wrap gap-2 items-center px-2">
|
// TODO: Setup different tag handler for requirements
|
||||||
{info.getValue().map((tag: string, index: number) => {
|
<TagsInput
|
||||||
return <Tag key={index}>{tag}</Tag>;
|
presetValue={
|
||||||
})}
|
info.getValue()[0] !== "" ? info.getValue() : ["N/A"]
|
||||||
</div>
|
}
|
||||||
|
{...requirementProps}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
columnHelper.accessor("summary", {
|
columnHelper.accessor("summary", {
|
||||||
header: () => (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
<DocumentTextIcon className="inline align-top h-4" />{" "}
|
<Bars2Icon className="inline align-top h-4" /> Summary
|
||||||
Summary
|
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<div className="flex items-start gap-2 px-2 py-1">
|
<span className="ml-2 text-gray-500">{info.getValue()}</span>
|
||||||
<span className="text-gray-500 max-h-8 overflow-y-auto">
|
|
||||||
{info.getValue()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
|
@ -2,19 +2,14 @@ import {
|
||||||
ArrowDownCircleIcon,
|
ArrowDownCircleIcon,
|
||||||
AtSymbolIcon,
|
AtSymbolIcon,
|
||||||
Bars2Icon,
|
Bars2Icon,
|
||||||
EnvelopeIcon,
|
|
||||||
ListBulletIcon,
|
|
||||||
UserIcon,
|
|
||||||
} from "@heroicons/react/24/solid";
|
} from "@heroicons/react/24/solid";
|
||||||
import { Dispatch, SetStateAction, useState } from "react";
|
import { Dispatch, SetStateAction } from "react";
|
||||||
import useTagsHandler from "@/components/TagsInput/TagsHandler";
|
import useTagsHandler from "@/components/TagsInput/TagsHandler";
|
||||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
||||||
import Table from "@/components/Table/Table";
|
import Table from "@/components/Table/Table";
|
||||||
import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
||||||
import TagsInput from "@/components/TagsInput/Index";
|
import TagsInput from "@/components/TagsInput/Index";
|
||||||
import User from "@/utils/models/User";
|
import User from "@/utils/models/User";
|
||||||
import { Details } from "../Drawer/Drawer";
|
|
||||||
import { Tag } from "../TagsInput/Tag";
|
|
||||||
|
|
||||||
type UserTableProps = {
|
type UserTableProps = {
|
||||||
data: User[];
|
data: User[];
|
||||||
|
@ -29,83 +24,46 @@ type UserTableProps = {
|
||||||
export default function UserTable({ data, setData }: UserTableProps) {
|
export default function UserTable({ data, setData }: UserTableProps) {
|
||||||
const columnHelper = createColumnHelper<User>();
|
const columnHelper = createColumnHelper<User>();
|
||||||
|
|
||||||
const [rolePresets, setRolePresets] = useState([
|
// Set up tag handling
|
||||||
"admin",
|
const roleProps = useTagsHandler([
|
||||||
|
"administrator",
|
||||||
"volunteer",
|
"volunteer",
|
||||||
"employee",
|
"employee",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [programPresets, setProgramPresets] = useState([
|
const programProps = useTagsHandler(["community", "domestic", "economic"]);
|
||||||
"domestic",
|
|
||||||
"community",
|
|
||||||
"economic",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const userDetails: Details[] = [
|
|
||||||
{
|
|
||||||
key: "username",
|
|
||||||
label: "username",
|
|
||||||
inputType: "text",
|
|
||||||
icon: <UserIcon className="h-4 w-4" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "role",
|
|
||||||
label: "role",
|
|
||||||
inputType: "select-one",
|
|
||||||
icon: <ListBulletIcon className="h-4 w-4" />,
|
|
||||||
presetOptionsValues: rolePresets,
|
|
||||||
presetOptionsSetter: setRolePresets,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "email",
|
|
||||||
label: "email",
|
|
||||||
inputType: "email",
|
|
||||||
icon: <EnvelopeIcon className="h-4 w-4" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "program",
|
|
||||||
label: "program",
|
|
||||||
inputType: "select-multiple",
|
|
||||||
icon: <ListBulletIcon className="h-4 w-4" />,
|
|
||||||
presetOptionsValues: programPresets,
|
|
||||||
presetOptionsSetter: setProgramPresets,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Define Tanstack columns
|
// Define Tanstack columns
|
||||||
const columns: ColumnDef<User, any>[] = [
|
const columns: ColumnDef<User, any>[] = [
|
||||||
columnHelper.accessor("username", {
|
columnHelper.accessor("username", {
|
||||||
header: () => (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
<UserIcon className="inline align-top h-4" /> Username
|
<Bars2Icon className="inline align-top h-4" /> Username
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<RowOpenAction
|
<RowOpenAction
|
||||||
title={info.getValue()}
|
title={info.getValue()}
|
||||||
titleKey="username"
|
|
||||||
rowData={info.row.original}
|
rowData={info.row.original}
|
||||||
setData={setData}
|
setData={setData}
|
||||||
details={userDetails}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("role", {
|
columnHelper.accessor("role", {
|
||||||
header: () => (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
<ListBulletIcon className="inline align-top h-4" /> Role
|
<ArrowDownCircleIcon className="inline align-top h-4" />{" "}
|
||||||
|
Role
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<div className="flex ml-2 flex-wrap gap-2 items-center">
|
<TagsInput presetValue={info.getValue()} {...roleProps} />
|
||||||
<Tag>{info.getValue()}</Tag>
|
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("email", {
|
columnHelper.accessor("email", {
|
||||||
header: () => (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
<EnvelopeIcon className="inline align-top h-4" /> Email
|
<AtSymbolIcon className="inline align-top h-4" /> Email
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
|
@ -117,15 +75,12 @@ export default function UserTable({ data, setData }: UserTableProps) {
|
||||||
columnHelper.accessor("program", {
|
columnHelper.accessor("program", {
|
||||||
header: () => (
|
header: () => (
|
||||||
<>
|
<>
|
||||||
<ListBulletIcon className="inline align-top h-4" /> Program
|
<ArrowDownCircleIcon className="inline align-top h-4" />{" "}
|
||||||
|
Program
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<div className="flex ml-2 flex-wrap gap-2 items-center">
|
<TagsInput presetValue={info.getValue()} {...programProps} />
|
||||||
{info.getValue().map((tag: string, index: number) => {
|
|
||||||
return <Tag key={index}>{tag}</Tag>;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,18 +1,12 @@
|
||||||
import { Tag } from "./Tag";
|
import { Tag } from "./Tag";
|
||||||
|
|
||||||
interface NewTagProps {
|
export const CreateNewTagAction = ({ input }) => {
|
||||||
input: string;
|
|
||||||
addTag: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CreateNewTagAction = ({ input, addTag }: NewTagProps) => {
|
|
||||||
return (
|
return (
|
||||||
<button
|
<div className="flex flex-row space-x-2 hover:bg-gray-100 rounded-md py-2 p-2 items-center">
|
||||||
className="flex flex-row space-x-2 hover:bg-gray-100 rounded-md py-2 p-2 items-center"
|
|
||||||
onClick={addTag}
|
|
||||||
>
|
|
||||||
<p className="capitalize">Create</p>
|
<p className="capitalize">Create</p>
|
||||||
<Tag active={false}>{input}</Tag>
|
<Tag active={false} onDelete={null}>
|
||||||
</button>
|
{input}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,21 +1,11 @@
|
||||||
import { EllipsisHorizontalIcon, TrashIcon } from "@heroicons/react/24/solid";
|
import { EllipsisHorizontalIcon, TrashIcon } from "@heroicons/react/24/solid";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
interface DropdownActionProps {
|
export const DropdownAction = ({ tag, handleDeleteTag, handleEditTag }) => {
|
||||||
tag: string;
|
|
||||||
handleDeleteTag: (tag: string) => void;
|
|
||||||
handleEditTag: (oldTag: string, newTag: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DropdownAction = ({
|
|
||||||
tag,
|
|
||||||
handleDeleteTag,
|
|
||||||
handleEditTag,
|
|
||||||
}: DropdownActionProps) => {
|
|
||||||
const [isVisible, setVisible] = useState(false);
|
const [isVisible, setVisible] = useState(false);
|
||||||
const [inputValue, setInputValue] = useState(tag);
|
const [inputValue, setInputValue] = useState(tag);
|
||||||
|
|
||||||
const editTagOption = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const editTagOption = (e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
handleEditTag(tag, inputValue);
|
handleEditTag(tag, inputValue);
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
|
|
|
@ -6,53 +6,51 @@ import { CreateNewTagAction } from "./CreateNewTagAction";
|
||||||
|
|
||||||
interface TagsInputProps {
|
interface TagsInputProps {
|
||||||
presetOptions: string[];
|
presetOptions: string[];
|
||||||
presetValue: string[];
|
presetValue: string | string[];
|
||||||
setPresetOptions: Dispatch<SetStateAction<string[]>>;
|
setPresetOptions: Dispatch<SetStateAction<string[]>>;
|
||||||
|
getTagColor(tag: string): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagsInput: React.FC<TagsInputProps> = ({
|
const TagsInput: React.FC<TagsInputProps> = ({
|
||||||
presetValue,
|
presetValue,
|
||||||
presetOptions,
|
presetOptions,
|
||||||
setPresetOptions,
|
setPresetOptions,
|
||||||
|
getTagColor,
|
||||||
}) => {
|
}) => {
|
||||||
const [inputValue, setInputValue] = useState("");
|
const [inputValue, setInputValue] = useState("");
|
||||||
const [cellSelected, setCellSelected] = useState(false);
|
const [cellSelected, setCellSelected] = useState(false);
|
||||||
|
|
||||||
// TODO: Add tags to the database and remove the presetValue and lowercasing
|
|
||||||
const [tags, setTags] = useState<Set<string>>(
|
const [tags, setTags] = useState<Set<string>>(
|
||||||
new Set(presetValue.map((tag) => tag.toLowerCase()))
|
typeof presetValue === "string"
|
||||||
);
|
? new Set([presetValue])
|
||||||
const [options, setOptions] = useState<Set<string>>(
|
: new Set(presetValue)
|
||||||
new Set(presetOptions.map((option) => option.toLowerCase()))
|
|
||||||
);
|
|
||||||
const [filteredOptions, setFilteredOptions] = useState<Set<string>>(
|
|
||||||
new Set(presetOptions)
|
|
||||||
);
|
);
|
||||||
|
const [options, setOptions] = useState<Set<string>>(new Set(presetOptions));
|
||||||
const dropdown = useRef<HTMLDivElement>(null);
|
const dropdown = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (!cellSelected) {
|
if (!cellSelected) {
|
||||||
setCellSelected(true);
|
setCellSelected(true);
|
||||||
|
// Add event listener only after setting cellSelected to true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.addEventListener("click", handleOutsideClick);
|
window.addEventListener("click", handleOutsideClick);
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Fix MouseEvent type and remove the as Node as that is completely wrong
|
||||||
const handleOutsideClick = (event: MouseEvent) => {
|
const handleOutsideClick = (event: MouseEvent) => {
|
||||||
console.log(dropdown.current);
|
|
||||||
if (
|
if (
|
||||||
dropdown.current &&
|
dropdown.current &&
|
||||||
!dropdown.current.contains(event.target as Node)
|
!dropdown.current.contains(event.target as Node)
|
||||||
) {
|
) {
|
||||||
console.log("outside");
|
|
||||||
setCellSelected(false);
|
setCellSelected(false);
|
||||||
|
// Remove event listener after handling outside click
|
||||||
window.removeEventListener("click", handleOutsideClick);
|
window.removeEventListener("click", handleOutsideClick);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setFilteredOptions(() => {
|
setOptions(() => {
|
||||||
const newOptions = presetOptions.filter((item) =>
|
const newOptions = presetOptions.filter((item) =>
|
||||||
item.includes(e.target.value.toLowerCase())
|
item.includes(e.target.value.toLowerCase())
|
||||||
);
|
);
|
||||||
|
@ -63,45 +61,39 @@ const TagsInput: React.FC<TagsInputProps> = ({
|
||||||
|
|
||||||
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
if (e.key === "Enter" && inputValue.trim()) {
|
if (e.key === "Enter" && inputValue.trim()) {
|
||||||
addTag(e);
|
// 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 addTag = (e?: React.KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
e?.stopPropagation();
|
|
||||||
|
|
||||||
setOptions(new Set(Array.from(options).concat(inputValue)));
|
|
||||||
setTags(new Set(Array.from(tags).concat(inputValue)));
|
|
||||||
setFilteredOptions(new Set(Array.from(options).concat(inputValue)));
|
|
||||||
setInputValue("");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectTag = (tagToAdd: string) => {
|
const handleSelectTag = (tagToAdd: string) => {
|
||||||
console.log(tagToAdd);
|
|
||||||
console.log(tags);
|
|
||||||
|
|
||||||
if (!tags.has(tagToAdd)) {
|
if (!tags.has(tagToAdd)) {
|
||||||
setTags(new Set(Array.from(tags).concat(tagToAdd)));
|
// Corrected syntax for checking if a Set contains an item
|
||||||
|
setTags((prevTags) => new Set(prevTags).add(tagToAdd));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteTag = (tagToDelete: string) => {
|
const handleDeleteTag = (tagToDelete: string) => {
|
||||||
setTags(new Set(Array.from(tags).filter((tag) => tag !== tagToDelete)));
|
setTags((prevTags) => {
|
||||||
|
const updatedTags = new Set(prevTags);
|
||||||
|
updatedTags.delete(tagToDelete);
|
||||||
|
return updatedTags;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteTagOption = (tagToDelete: string) => {
|
const handleDeleteTagOption = (tagToDelete: string) => {
|
||||||
setOptions(
|
// setPresetOptions(presetOptions.filter(tag => tag !== tagToDelete));
|
||||||
new Set(
|
setOptions((prevOptions) => {
|
||||||
Array.from(options).filter((option) => option !== tagToDelete)
|
const updatedOptions = new Set(prevOptions);
|
||||||
)
|
updatedOptions.delete(tagToDelete);
|
||||||
);
|
return updatedOptions;
|
||||||
|
});
|
||||||
setFilteredOptions(
|
|
||||||
new Set(
|
|
||||||
Array.from(options).filter((option) => option !== tagToDelete)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tags.has(tagToDelete)) {
|
if (tags.has(tagToDelete)) {
|
||||||
handleDeleteTag(tagToDelete);
|
handleDeleteTag(tagToDelete);
|
||||||
}
|
}
|
||||||
|
@ -109,20 +101,23 @@ const TagsInput: React.FC<TagsInputProps> = ({
|
||||||
|
|
||||||
const handleEditTag = (oldTag: string, newTag: string) => {
|
const handleEditTag = (oldTag: string, newTag: string) => {
|
||||||
if (oldTag !== newTag) {
|
if (oldTag !== newTag) {
|
||||||
setTags(
|
setTags((prevTags) => {
|
||||||
new Set(
|
const tagsArray = Array.from(prevTags);
|
||||||
Array.from(tags)
|
const oldTagIndex = tagsArray.indexOf(oldTag);
|
||||||
.filter((tag) => tag !== oldTag)
|
if (oldTagIndex !== -1) {
|
||||||
.concat(newTag)
|
tagsArray.splice(oldTagIndex, 1, newTag);
|
||||||
)
|
}
|
||||||
);
|
return new Set(tagsArray);
|
||||||
setOptions(
|
});
|
||||||
new Set(
|
|
||||||
Array.from(options)
|
setOptions((prevOptions) => {
|
||||||
.filter((option) => option !== oldTag)
|
const optionsArray = Array.from(prevOptions);
|
||||||
.concat(newTag)
|
const oldTagIndex = optionsArray.indexOf(oldTag);
|
||||||
)
|
if (oldTagIndex !== -1) {
|
||||||
);
|
optionsArray.splice(oldTagIndex, 1, newTag);
|
||||||
|
}
|
||||||
|
return new Set(optionsArray);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -162,13 +157,10 @@ const TagsInput: React.FC<TagsInputProps> = ({
|
||||||
handleDeleteTag={handleDeleteTagOption}
|
handleDeleteTag={handleDeleteTagOption}
|
||||||
handleEditTag={handleEditTag}
|
handleEditTag={handleEditTag}
|
||||||
handleAdd={handleSelectTag}
|
handleAdd={handleSelectTag}
|
||||||
tags={filteredOptions}
|
tags={options}
|
||||||
/>
|
/>
|
||||||
{inputValue.length > 0 && (
|
{inputValue.length > 0 && (
|
||||||
<CreateNewTagAction
|
<CreateNewTagAction input={inputValue} />
|
||||||
input={inputValue}
|
|
||||||
addTag={addTag}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,25 +1,14 @@
|
||||||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
interface TagProps {
|
export const Tag = ({ children, handleDelete, active = false }) => {
|
||||||
children: string;
|
|
||||||
handleDelete?: (tag: string) => void;
|
|
||||||
active?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Tag = ({ children, handleDelete, active = false }: TagProps) => {
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={`font-normal bg-purple-100 text-gray-800 flex flex-row p-1 px-2 rounded-lg`}
|
className={`font-normal bg-purple-100 text-gray-800 flex flex-row p-1 px-2 rounded-lg`}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
{active && handleDelete && (
|
{active && handleDelete && (
|
||||||
<button
|
<button onClick={() => handleDelete(children)}>
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleDelete(children);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<XMarkIcon className={`ml-1 w-3 text-purple-500`} />
|
<XMarkIcon className={`ml-1 w-3 text-purple-500`} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,19 +1,12 @@
|
||||||
import { Tag } from "./Tag";
|
import { Tag } from "./Tag";
|
||||||
import { DropdownAction } from "./DropdownAction";
|
import { DropdownAction } from "./DropdownAction";
|
||||||
|
|
||||||
interface TagDropdownProps {
|
|
||||||
tags: Set<string>;
|
|
||||||
handleEditTag: (oldTag: string, newTag: string) => void;
|
|
||||||
handleDeleteTag: (tag: string) => void;
|
|
||||||
handleAdd: (tag: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TagDropdown = ({
|
export const TagDropdown = ({
|
||||||
tags,
|
tags,
|
||||||
handleEditTag,
|
handleEditTag,
|
||||||
handleDeleteTag,
|
handleDeleteTag,
|
||||||
handleAdd,
|
handleAdd,
|
||||||
}: TagDropdownProps) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="z-50 flex flex-col space-y-2 mt-2">
|
<div className="z-50 flex flex-col space-y-2 mt-2">
|
||||||
{Array.from(tags).map((tag, index) => (
|
{Array.from(tags).map((tag, index) => (
|
||||||
|
|
|
@ -11,8 +11,6 @@ const Loading = () => {
|
||||||
alt="Compass Center logo."
|
alt="Compass Center logo."
|
||||||
width={100}
|
width={100}
|
||||||
height={91}
|
height={91}
|
||||||
style={{ height: "auto", width: "auto" }}
|
|
||||||
priority
|
|
||||||
/>
|
/>
|
||||||
<h1 className={styles.loadingTitle}>Loading...</h1>
|
<h1 className={styles.loadingTitle}>Loading...</h1>
|
||||||
<div className={styles.loadingSpinner}></div>
|
<div className={styles.loadingSpinner}></div>
|
||||||
|
|
|
@ -11,8 +11,6 @@ const LoggingOut = () => {
|
||||||
alt="Compass Center logo."
|
alt="Compass Center logo."
|
||||||
width={100}
|
width={100}
|
||||||
height={91}
|
height={91}
|
||||||
style={{ height: "auto", width: "auto" }}
|
|
||||||
priority
|
|
||||||
/>
|
/>
|
||||||
<h1 className={styles.loadingTitle}>Signing out...</h1>
|
<h1 className={styles.loadingTitle}>Signing out...</h1>
|
||||||
<div className={styles.loadingSpinner}></div>
|
<div className={styles.loadingSpinner}></div>
|
||||||
|
|
247
compass/components/page/Drawer.tsx
Normal file
247
compass/components/page/Drawer.tsx
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
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;
|
Loading…
Reference in New Issue
Block a user