mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-20 10:30:16 -04:00
Implement 'Create New' button and fix no tags bug
This commit is contained in:
parent
a516c414f6
commit
1f1658c708
171
compass/components/Drawer/CreateDrawer.tsx
Normal file
171
compass/components/Drawer/CreateDrawer.tsx
Normal 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;
|
|
@ -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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,14 @@ import { PlusIcon } from "@heroicons/react/24/solid";
|
||||||
import { rankItem } from "@tanstack/match-sorter-utils";
|
import { rankItem } from "@tanstack/match-sorter-utils";
|
||||||
import { RowOptionMenu } from "./RowOptionMenu";
|
import { RowOptionMenu } from "./RowOptionMenu";
|
||||||
import DataPoint from "@/utils/models/DataPoint";
|
import DataPoint from "@/utils/models/DataPoint";
|
||||||
|
import CreateDrawer from "../Drawer/CreateDrawer";
|
||||||
|
import { Details } from "../Drawer/Drawer";
|
||||||
|
|
||||||
type TableProps<T extends DataPoint> = {
|
type TableProps<T extends DataPoint> = {
|
||||||
data: T[];
|
data: T[];
|
||||||
setData: Dispatch<SetStateAction<T[]>>;
|
setData: Dispatch<SetStateAction<T[]>>;
|
||||||
columns: ColumnDef<T, any>[];
|
columns: ColumnDef<T, any>[];
|
||||||
|
details: Details[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Fuzzy search function */
|
/** Fuzzy search function */
|
||||||
|
@ -53,6 +56,7 @@ export default function Table<T extends DataPoint>({
|
||||||
data,
|
data,
|
||||||
setData,
|
setData,
|
||||||
columns,
|
columns,
|
||||||
|
details,
|
||||||
}: TableProps<T>) {
|
}: TableProps<T>) {
|
||||||
const columnHelper = createColumnHelper<T>();
|
const columnHelper = createColumnHelper<T>();
|
||||||
|
|
||||||
|
@ -208,14 +212,13 @@ export default function Table<T extends DataPoint>({
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<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}
|
colSpan={100}
|
||||||
onClick={addData}
|
|
||||||
>
|
>
|
||||||
<span className="flex ml-1 text-gray-500">
|
<CreateDrawer
|
||||||
<PlusIcon className="inline h-4 mr-1" />
|
details={details}
|
||||||
New
|
onCreate={(newItem) => {}}
|
||||||
</span>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
|
|
|
@ -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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,21 +16,27 @@ export const TagDropdown = ({
|
||||||
}: TagDropdownProps) => {
|
}: 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).length > 0 ? (
|
||||||
<div
|
Array.from(tags).map((tag, index) => (
|
||||||
key={index}
|
<div
|
||||||
className="items-center rounded-md p-1 flex flex-row justify-between hover:bg-gray-100"
|
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 onClick={() => handleAdd(tag)}>
|
||||||
</button>
|
<Tag>{tag}</Tag>
|
||||||
<DropdownAction
|
</button>
|
||||||
handleDeleteTag={handleDeleteTag}
|
<DropdownAction
|
||||||
handleEditTag={handleEditTag}
|
handleDeleteTag={handleDeleteTag}
|
||||||
tag={tag}
|
handleEditTag={handleEditTag}
|
||||||
/>
|
tag={tag}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="text-gray-500 text-sm p-1">
|
||||||
|
No options available. Type to create new ones.
|
||||||
</div>
|
</div>
|
||||||
))}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,21 +7,25 @@ export interface Tags {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TagsArray = ({ tags, handleDelete, active = false }: Tags) => {
|
export const TagsArray = ({ tags, handleDelete, active = false }: Tags) => {
|
||||||
// console.log(tags);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex ml-2 flex-wrap gap-2 items-center">
|
<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).map((tag, index) => {
|
{Array.from(tags).length > 0 ? (
|
||||||
return (
|
Array.from(tags).map((tag, index) => {
|
||||||
<Tag
|
return (
|
||||||
handleDelete={handleDelete}
|
<Tag
|
||||||
active={active}
|
handleDelete={handleDelete}
|
||||||
key={index}
|
active={active}
|
||||||
>
|
key={index}
|
||||||
{tag}
|
>
|
||||||
</Tag>
|
{tag}
|
||||||
);
|
</Tag>
|
||||||
})}
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<span className="text-gray-400 text-sm cursor-pointer">
|
||||||
|
Click to select tags
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user