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

This commit is contained in:
Nick A 2024-10-29 13:11:12 -04:00
parent 08d3be079d
commit 2bf3df68f8
5 changed files with 268 additions and 280 deletions

View File

@ -1,247 +1,257 @@
import { FunctionComponent, ReactNode } from "react"; import { Dispatch, FunctionComponent, ReactNode, SetStateAction } from "react";
import React, { useState } from "react"; import React, { useState } from "react";
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/solid"; import { ChevronDoubleLeftIcon } from "@heroicons/react/24/solid";
import { import {
StarIcon as SolidStarIcon, StarIcon as SolidStarIcon,
EnvelopeIcon, EnvelopeIcon,
UserIcon, UserIcon,
} from "@heroicons/react/24/solid"; } from "@heroicons/react/24/solid";
import { import {
ArrowsPointingOutIcon, ArrowsPointingOutIcon,
ArrowsPointingInIcon, ArrowsPointingInIcon,
StarIcon as OutlineStarIcon, StarIcon as OutlineStarIcon,
ListBulletIcon, ListBulletIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import TagsInput from "../TagsInput/Index"; import TagsInput from "../TagsInput/Index";
type DrawerProps = { type DrawerProps = {
title: string; title: string;
children: ReactNode; children: ReactNode;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void; onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
type?: "button" | "submit" | "reset"; // specify possible values for type type?: "button" | "submit" | "reset"; // specify possible values for type
disabled?: boolean; disabled?: boolean;
editableContent?: any; editableContent?: any;
onSave?: (content: any) => void; onSave?: (content: any) => void;
rowContent?: any; rowContent?: any;
onRowUpdate?: (content: any) => void; setData: Dispatch<SetStateAction<any>>;
}; };
interface EditContent { interface EditContent {
content: string; content: string;
isEditing: boolean; isEditing: boolean;
} }
const Drawer: FunctionComponent<DrawerProps> = ({ const Drawer: FunctionComponent<DrawerProps> = ({
title, title,
children, children,
onSave, onSave,
editableContent, editableContent,
rowContent, rowContent,
onRowUpdate, setData,
}) => { }) => {
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 handleTempRowContentChange = (e) => { const onRowUpdate = (updatedRow: any) => {
const { name, value } = e.target; setData((prevData: any) => (
console.log(name); prevData.map((row: any) => (
console.log(value); row.id === updatedRow.id
setTempRowContent((prevContent) => ({ ? updatedRow
...prevContent, : row
[name]: value, ))
})); ))
}; };
const handleEnterPress = (e) => { const handleTempRowContentChange = (e) => {
if (e.key === "Enter") { const { name, value } = e.target;
e.preventDefault(); console.log(name);
// Update the rowContent with the temporaryRowContent console.log(value);
if (onRowUpdate) { setTempRowContent((prevContent) => ({
onRowUpdate(tempRowContent); ...prevContent,
} [name]: value,
} }));
}; };
const toggleDrawer = () => { const handleEnterPress = (e) => {
setIsOpen(!isOpen); if (e.key === "Enter") {
if (isFull) { e.preventDefault();
setIsFull(!isFull); // Update the rowContent with the temporaryRowContent
} if (onRowUpdate) {
}; onRowUpdate(tempRowContent);
}
const toggleDrawerFullScreen = () => setIsFull(!isFull); }
};
const toggleFavorite = () => setIsFavorite(!isFavorite);
const toggleDrawer = () => {
const drawerClassName = `fixed top-0 right-0 w-1/2 h-full bg-white transform ease-in-out duration-300 z-20 ${ setIsOpen(!isOpen);
isOpen ? "translate-x-0 shadow-xl" : "translate-x-full" if (isFull) {
} ${isFull ? "w-full" : "w-1/2"}`; setIsFull(!isFull);
}
const iconComponent = isFull ? ( };
<ArrowsPointingInIcon className="h-5 w-5" />
) : ( const toggleDrawerFullScreen = () => setIsFull(!isFull);
<ArrowsPointingOutIcon className="h-5 w-5" />
); const toggleFavorite = () => setIsFavorite(!isFavorite);
const favoriteIcon = isFavorite ? ( const drawerClassName = `fixed top-0 right-0 w-1/2 h-full bg-white transform ease-in-out duration-300 z-20 ${
<SolidStarIcon className="h-5 w-5" /> isOpen ? "translate-x-0 shadow-xl" : "translate-x-full"
) : ( } ${isFull ? "w-full" : "w-1/2"}`;
<OutlineStarIcon className="h-5 w-5" />
); const iconComponent = isFull ? (
<ArrowsPointingInIcon className="h-5 w-5" />
const [presetOptions, setPresetOptions] = useState([ ) : (
"administrator", <ArrowsPointingOutIcon className="h-5 w-5" />
"volunteer", );
"employee",
]); const favoriteIcon = isFavorite ? (
const [rolePresetOptions, setRolePresetOptions] = useState([ <SolidStarIcon className="h-5 w-5" />
"domestic", ) : (
"community", <OutlineStarIcon className="h-5 w-5" />
"economic", );
]);
const [tagColors, setTagColors] = useState(new Map()); const [presetOptions, setPresetOptions] = useState([
"administrator",
const getTagColor = (tag: string) => { "volunteer",
if (!tagColors.has(tag)) { "employee",
const colors = [ ]);
"bg-cyan-100", const [rolePresetOptions, setRolePresetOptions] = useState([
"bg-blue-100", "domestic",
"bg-green-100", "community",
"bg-yellow-100", "economic",
"bg-purple-100", ]);
]; const [tagColors, setTagColors] = useState(new Map());
const randomColor =
colors[Math.floor(Math.random() * colors.length)]; const getTagColor = (tag: string) => {
setTagColors(new Map(tagColors).set(tag, randomColor)); if (!tagColors.has(tag)) {
} const colors = [
return tagColors.get(tag); "bg-cyan-100",
}; "bg-blue-100",
"bg-green-100",
return ( "bg-yellow-100",
<div> "bg-purple-100",
<button ];
className={ const randomColor =
"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" colors[Math.floor(Math.random() * colors.length)];
} setTagColors(new Map(tagColors).set(tag, randomColor));
onClick={toggleDrawer} }
> return tagColors.get(tag);
Open };
</button>
<div className={drawerClassName}></div> return (
<div className={drawerClassName}> <div>
<div className="flex items-center justify-between p-4"> <button
<div className="flex flex-row items-center justify-between space-x-2"> className={
<span className="h-5 text-purple-200 w-5"> "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"
<UserIcon /> }
</span> onClick={toggleDrawer}
<h2 className="text-lg text-gray-800 font-semibold"> >
{rowContent.username} Open
</h2> </button>
</div> <div className={drawerClassName}></div>
<div> <div className={drawerClassName}>
<button <div className="flex items-center justify-between p-4">
onClick={toggleFavorite} <div className="flex flex-row items-center justify-between space-x-2">
className="py-2 text-gray-500 hover:text-gray-800 mr-2" <span className="h-5 text-purple-200 w-5">
> <UserIcon />
{favoriteIcon} </span>
</button> <h2 className="text-lg text-gray-800 font-semibold">
<button {rowContent.username}
onClick={toggleDrawerFullScreen} </h2>
className="py-2 text-gray-500 hover:text-gray-800 mr-2" </div>
> <div>
{iconComponent} <button
</button> onClick={toggleFavorite}
<button className="py-2 text-gray-500 hover:text-gray-800 mr-2"
onClick={toggleDrawer} >
className="py-2 text-gray-500 hover:text-gray-800" {favoriteIcon}
> </button>
<ChevronDoubleLeftIcon className="h-5 w-5" /> <button
</button> onClick={toggleDrawerFullScreen}
</div> className="py-2 text-gray-500 hover:text-gray-800 mr-2"
</div> >
<div className="p-4"> {iconComponent}
<table className="p-4"> </button>
<tbody className="items-center"> <button
<tr className="w-full text-xs items-center flex flex-row justify-between"> onClick={toggleDrawer}
<div className="flex flex-row space-x-2 text-gray-500 items-center"> className="py-2 text-gray-500 hover:text-gray-800"
<td> >
<UserIcon className="h-4 w-4" /> <ChevronDoubleLeftIcon className="h-5 w-5" />
</td> </button>
<td className="w-32">Username</td> </div>
</div> </div>
<td className="w-3/4 w-3/4 p-2 pl-0"> <div className="p-4">
<input <table className="p-4">
type="text" <tbody className="items-center">
name="username" <tr className="w-full text-xs items-center flex flex-row justify-between">
value={tempRowContent.username} <div className="flex flex-row space-x-2 text-gray-500 items-center">
onChange={handleTempRowContentChange} <td>
onKeyDown={handleEnterPress} <UserIcon className="h-4 w-4" />
className="ml-2 w-full p-1 focus:outline-gray-200 hover:bg-gray-50" </td>
/> <td className="w-32">Username</td>
</td> </div>
</tr> <td className="w-3/4 w-3/4 p-2 pl-0">
<tr className="w-full text-xs items-center flex flex-row justify-between"> <input
<div className="flex flex-row space-x-2 text-gray-500 items-center"> type="text"
<td> name="username"
<ListBulletIcon className="h-4 w-4" /> value={tempRowContent.username}
</td> onChange={handleTempRowContentChange}
<td className="w-32">Role</td> onKeyDown={handleEnterPress}
</div> className="ml-2 w-full p-1 focus:outline-gray-200 hover:bg-gray-50"
<td className="w-3/4 hover:bg-gray-50"> />
<TagsInput </td>
presetValue={tempRowContent.role} </tr>
presetOptions={presetOptions} <tr className="w-full text-xs items-center flex flex-row justify-between">
setPresetOptions={setPresetOptions} <div className="flex flex-row space-x-2 text-gray-500 items-center">
getTagColor={getTagColor} <td>
setTagColors={setTagColors} <ListBulletIcon className="h-4 w-4" />
/> </td>
</td> <td className="w-32">Role</td>
</tr> </div>
<tr className="w-full text-xs items-center flex flex-row justify-between"> <td className="w-3/4 hover:bg-gray-50">
<div className="flex flex-row space-x-2 text-gray-500 items-center"> <TagsInput
<td> presetValue={tempRowContent.role}
<EnvelopeIcon className="h-4 w-4" /> presetOptions={presetOptions}
</td> setPresetOptions={setPresetOptions}
<td className="w-32">Email</td> getTagColor={getTagColor}
</div> setTagColors={setTagColors}
<td className="w-3/4 p-2 pl-0"> />
<input </td>
type="text" </tr>
name="email" <tr className="w-full text-xs items-center flex flex-row justify-between">
value={tempRowContent.email} <div className="flex flex-row space-x-2 text-gray-500 items-center">
onChange={handleTempRowContentChange} <td>
onKeyDown={handleEnterPress} <EnvelopeIcon className="h-4 w-4" />
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>
/> <td className="w-32">Email</td>
</td> </div>
</tr> <td className="w-3/4 p-2 pl-0">
<tr className="w-full text-xs items-center flex flex-row justify-between"> <input
<div className="flex flex-row space-x-2 text-gray-500 items-center"> type="text"
<td> name="email"
<ListBulletIcon className="h-4 w-4" /> value={tempRowContent.email}
</td> onChange={handleTempRowContentChange}
<td className="w-32">Type of Program</td> onKeyDown={handleEnterPress}
</div> className="ml-2 w-80 p-1 font-normal hover:text-gray-400 focus:outline-gray-200 hover:bg-gray-50 underline text-gray-500"
<td className="w-3/4 hover:bg-gray-50"> />
{/* {rowContent.program} */} </td>
<TagsInput </tr>
presetValue={tempRowContent.program} <tr className="w-full text-xs items-center flex flex-row justify-between">
presetOptions={rolePresetOptions} <div className="flex flex-row space-x-2 text-gray-500 items-center">
setPresetOptions={setRolePresetOptions} <td>
getTagColor={getTagColor} <ListBulletIcon className="h-4 w-4" />
setTagColors={setTagColors} </td>
/> <td className="w-32">Type of Program</td>
</td> </div>
</tr> <td className="w-3/4 hover:bg-gray-50">
</tbody> {/* {rowContent.program} */}
</table> <TagsInput
<br /> presetValue={tempRowContent.program}
</div> presetOptions={rolePresetOptions}
</div> setPresetOptions={setRolePresetOptions}
</div> getTagColor={getTagColor}
); setTagColors={setTagColors}
}; />
</td>
export default Drawer; </tr>
</tbody>
</table>
<br />
</div>
</div>
</div>
);
};
export default Drawer;

View File

@ -26,16 +26,6 @@ export default function ResourceTable({ data, setData }: ResourceTableProps ) {
"employee", "employee",
]) ])
const handleRowUpdate = (updatedRow: Resource) => {
setData(prevData => (
prevData.map(row => (
row.id === updatedRow.id
? updatedRow
: row
))
))
};
const columns: ColumnDef<Resource, any>[] = [ const columns: ColumnDef<Resource, any>[] = [
columnHelper.accessor("name", { columnHelper.accessor("name", {
header: () => ( header: () => (
@ -47,7 +37,7 @@ export default function ResourceTable({ data, setData }: ResourceTableProps ) {
<RowOpenAction <RowOpenAction
title={info.getValue()} title={info.getValue()}
rowData={info.row.original} rowData={info.row.original}
onRowUpdate={handleRowUpdate} setData={setData}
/> />
), ),
}), }),

View File

@ -1,14 +1,14 @@
import Drawer from "@/components/Drawer/Drawer"; import Drawer from "@/components/Drawer/Drawer";
import { useState } from "react"; import DataPoint from "@/utils/models/DataPoint";
import { DataPoint } from "@/components/Table/Table"; import { Dispatch, SetStateAction, useState } from "react";
type RowOpenActionProps = { type RowOpenActionProps<T extends DataPoint> = {
title: string, title: string,
rowData: DataPoint, rowData: T,
onRowUpdate: (updatedRow: DataPoint) => void; setData: Dispatch<SetStateAction<T[]>>
} }
export const RowOpenAction = ({ title, rowData, onRowUpdate }: RowOpenActionProps) => { export function RowOpenAction<T extends DataPoint>({ title, rowData, setData }: RowOpenActionProps<T>) {
const [pageContent, setPageContent] = useState(""); const [pageContent, setPageContent] = useState("");
const handleDrawerContentChange = (newContent: string) => { const handleDrawerContentChange = (newContent: string) => {
@ -25,7 +25,7 @@ export const RowOpenAction = ({ title, rowData, onRowUpdate }: RowOpenActionProp
editableContent={pageContent} editableContent={pageContent}
rowContent={rowData} rowContent={rowData}
onSave={handleDrawerContentChange} onSave={handleDrawerContentChange}
onRowUpdate={onRowUpdate} setData={setData}
> >
{pageContent} {pageContent}
</Drawer> </Drawer>

View File

@ -27,16 +27,6 @@ export default function ServiceTable({ data, setData }: ServiceTableProps ) {
"employee", "employee",
]) ])
const handleRowUpdate = (updatedRow: Service) => {
setData(prevData => (
prevData.map(row => (
row.id === updatedRow.id
? updatedRow
: row
))
))
};
const columns: ColumnDef<Service, any>[] = [ const columns: ColumnDef<Service, any>[] = [
columnHelper.accessor("name", { columnHelper.accessor("name", {
header: () => ( header: () => (
@ -48,7 +38,7 @@ export default function ServiceTable({ data, setData }: ServiceTableProps ) {
<RowOpenAction <RowOpenAction
title={info.getValue()} title={info.getValue()}
rowData={info.row.original} rowData={info.row.original}
onRowUpdate={handleRowUpdate} setData={setData}
/> />
), ),
}), }),
@ -106,5 +96,5 @@ export default function ServiceTable({ data, setData }: ServiceTableProps ) {
}), }),
]; ];
return <Table<Service> data={data} setData={setData} columns={columns} /> return <Table data={data} setData={setData} columns={columns} />
}; };

View File

@ -12,8 +12,6 @@ type UserTableProps = {
setData: Dispatch<SetStateAction<User[]>> setData: Dispatch<SetStateAction<User[]>>
} }
//TODO: Remove dependecy on `data`. Only `setData` is needed. Do for others as well
/** /**
* Table componenet used for displaying users * Table componenet used for displaying users
* @param props.users List of users to be displayed by the table * @param props.users List of users to be displayed by the table
@ -48,7 +46,7 @@ export default function UserTable({ data, setData }: UserTableProps ) {
<RowOpenAction <RowOpenAction
title={info.getValue()} title={info.getValue()}
rowData={info.row.original} rowData={info.row.original}
onRowUpdate={handleRowUpdate} setData={setData}
/> />
), ),
}), }),