Implement 'Create New' button and fix no tags bug

This commit is contained in:
pmoharana-cmd 2025-01-03 21:58:03 -05:00
parent a516c414f6
commit 1f1658c708
7 changed files with 242 additions and 37 deletions

View File

@ -0,0 +1,171 @@
import { Dispatch, FunctionComponent, ReactNode, SetStateAction } from "react";
import React, { useState } from "react";
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/solid";
import {
ArrowsPointingOutIcon,
ArrowsPointingInIcon,
} from "@heroicons/react/24/outline";
import TagsInput from "../TagsInput/Index";
import { Details } from "./Drawer";
type CreateDrawerProps = {
details: Details[];
onCreate: (newItem: any) => void;
};
const CreateDrawer: FunctionComponent<CreateDrawerProps> = ({
details,
onCreate,
}: CreateDrawerProps) => {
const [isOpen, setIsOpen] = useState(false);
const [isFull, setIsFull] = useState(false);
const [newItemContent, setNewItemContent] = useState<any>({});
const handleContentChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = e.target;
setNewItemContent((prev: any) => ({
...prev,
[name]: value,
}));
};
const handleCreate = () => {
onCreate(newItemContent);
setNewItemContent({});
setIsOpen(false);
};
const toggleDrawer = () => {
setIsOpen(!isOpen);
if (isFull) {
setIsFull(!isFull);
}
};
const toggleDrawerFullScreen = () => setIsFull(!isFull);
const drawerClassName = `fixed top-0 right-0 h-full bg-white transform ease-in-out duration-300 z-20 overflow-y-auto ${
isOpen ? "translate-x-0 shadow-2xl" : "translate-x-full"
} ${isFull ? "w-full" : "w-[600px]"}`;
const iconComponent = isFull ? (
<ArrowsPointingInIcon className="h-5 w-5" />
) : (
<ArrowsPointingOutIcon className="h-5 w-5" />
);
return (
<div>
<button
className="text-sm text-white font-medium bg-purple-600 hover:bg-purple-700 px-3 py-1 rounded-md"
onClick={toggleDrawer}
>
Create New
</button>
<div className={drawerClassName}>
<div className="sticky top-0 flex items-center justify-between p-4 bg-white border-b border-gray-200">
<h2 className="text-xl font-semibold text-gray-800">
Create New Item
</h2>
<div className="flex items-center space-x-2">
<button
onClick={toggleDrawerFullScreen}
className="p-2 text-gray-500 hover:text-gray-800 hover:bg-gray-100 rounded-lg"
>
{iconComponent}
</button>
<button
onClick={toggleDrawer}
className="p-2 text-gray-500 hover:text-gray-800 hover:bg-gray-100 rounded-lg"
>
<ChevronDoubleLeftIcon className="h-5 w-5" />
</button>
</div>
</div>
<div className="p-6">
<div className="flex flex-col space-y-4">
{details.map((detail, index) => {
const value = newItemContent[detail.key] || "";
let inputField;
switch (detail.inputType) {
case "select-one":
case "select-multiple":
inputField = (
<TagsInput
presetValue={[]}
presetOptions={
detail.presetOptionsValues || []
}
setPresetOptions={
detail.presetOptionsSetter ||
(() => {})
}
/>
);
break;
case "textarea":
inputField = (
<textarea
name={detail.key}
value={value}
onChange={handleContentChange}
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"
placeholder={`Enter ${detail.label.toLowerCase()}...`}
/>
);
break;
default:
inputField = (
<input
type={detail.inputType}
name={detail.key}
value={value}
onChange={handleContentChange}
className="w-full p-2 border border-gray-200 rounded-md focus:outline-none focus:border-purple-500"
placeholder={`Enter ${detail.label.toLowerCase()}...`}
/>
);
}
return (
<div
key={index}
className="flex flex-col gap-2"
>
<label className="flex items-center text-sm text-gray-700 gap-2">
<span className="text-gray-500">
{detail.icon}
</span>
{detail.label}
</label>
{inputField}
</div>
);
})}
</div>
<div className="mt-6 flex justify-end">
<button
onClick={handleCreate}
className="px-4 py-2 bg-purple-600 text-white rounded-md hover:bg-purple-700"
>
Create
</button>
</div>
</div>
</div>
</div>
);
};
export default CreateDrawer;

View File

@ -127,5 +127,12 @@ export default function ResourceTable({ data, setData }: ResourceTableProps) {
}),
];
return <Table data={data} setData={setData} columns={columns} />;
return (
<Table
data={data}
setData={setData}
columns={columns}
details={resourceDetails}
/>
);
}

View File

@ -151,5 +151,12 @@ export default function ServiceTable({ data, setData }: ServiceTableProps) {
}),
];
return <Table data={data} setData={setData} columns={columns} />;
return (
<Table
data={data}
setData={setData}
columns={columns}
details={serviceDetails}
/>
);
}

View File

@ -19,11 +19,14 @@ import { PlusIcon } from "@heroicons/react/24/solid";
import { rankItem } from "@tanstack/match-sorter-utils";
import { RowOptionMenu } from "./RowOptionMenu";
import DataPoint from "@/utils/models/DataPoint";
import CreateDrawer from "../Drawer/CreateDrawer";
import { Details } from "../Drawer/Drawer";
type TableProps<T extends DataPoint> = {
data: T[];
setData: Dispatch<SetStateAction<T[]>>;
columns: ColumnDef<T, any>[];
details: Details[];
};
/** Fuzzy search function */
@ -53,6 +56,7 @@ export default function Table<T extends DataPoint>({
data,
setData,
columns,
details,
}: TableProps<T>) {
const columnHelper = createColumnHelper<T>();
@ -208,14 +212,13 @@ export default function Table<T extends DataPoint>({
<tfoot>
<tr>
<td
className="p-3 border-y border-gray-200 text-gray-600 hover:bg-gray-50"
className="p-3 border-y border-gray-200"
colSpan={100}
onClick={addData}
>
<span className="flex ml-1 text-gray-500">
<PlusIcon className="inline h-4 mr-1" />
New
</span>
<CreateDrawer
details={details}
onCreate={(newItem) => {}}
/>
</td>
</tr>
</tfoot>

View File

@ -130,5 +130,12 @@ export default function UserTable({ data, setData }: UserTableProps) {
}),
];
return <Table<User> data={data} setData={setData} columns={columns} />;
return (
<Table<User>
data={data}
setData={setData}
columns={columns}
details={userDetails}
/>
);
}

View File

@ -16,21 +16,27 @@ export const TagDropdown = ({
}: TagDropdownProps) => {
return (
<div className="z-50 flex flex-col space-y-2 mt-2">
{Array.from(tags).map((tag, index) => (
<div
key={index}
className="items-center rounded-md p-1 flex flex-row justify-between hover:bg-gray-100"
>
<button onClick={() => handleAdd(tag)}>
<Tag>{tag}</Tag>
</button>
<DropdownAction
handleDeleteTag={handleDeleteTag}
handleEditTag={handleEditTag}
tag={tag}
/>
{Array.from(tags).length > 0 ? (
Array.from(tags).map((tag, index) => (
<div
key={index}
className="items-center rounded-md p-1 flex flex-row justify-between hover:bg-gray-100"
>
<button onClick={() => handleAdd(tag)}>
<Tag>{tag}</Tag>
</button>
<DropdownAction
handleDeleteTag={handleDeleteTag}
handleEditTag={handleEditTag}
tag={tag}
/>
</div>
))
) : (
<div className="text-gray-500 text-sm p-1">
No options available. Type to create new ones.
</div>
))}
)}
</div>
);
};

View File

@ -7,21 +7,25 @@ export interface Tags {
}
export const TagsArray = ({ tags, handleDelete, active = false }: Tags) => {
// console.log(tags);
return (
<div className="flex ml-2 flex-wrap gap-2 items-center">
{Array.from(tags).map((tag, index) => {
return (
<Tag
handleDelete={handleDelete}
active={active}
key={index}
>
{tag}
</Tag>
);
})}
<div className="flex ml-2 flex-wrap gap-2 items-center min-h-[24px] min-w-[100px] rounded-md hover:bg-gray-100 p-1">
{Array.from(tags).length > 0 ? (
Array.from(tags).map((tag, index) => {
return (
<Tag
handleDelete={handleDelete}
active={active}
key={index}
>
{tag}
</Tag>
);
})
) : (
<span className="text-gray-400 text-sm cursor-pointer">
Click to select tags
</span>
)}
</div>
);
};