mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-03 19:40:16 -04:00
New btn moharana (#51)
* Implement 'Create New' button and fix no tags bug * Implement local state editting when creating new element * Add defaults when no tags * Reset tags whenever new item is created
This commit is contained in:
parent
a516c414f6
commit
251222167d
200
compass/components/Drawer/CreateDrawer.tsx
Normal file
200
compass/components/Drawer/CreateDrawer.tsx
Normal file
|
@ -0,0 +1,200 @@
|
|||
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) => boolean;
|
||||
};
|
||||
|
||||
const CreateDrawer: FunctionComponent<CreateDrawerProps> = ({
|
||||
details,
|
||||
onCreate,
|
||||
}: CreateDrawerProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isFull, setIsFull] = useState(false);
|
||||
const [newItemContent, setNewItemContent] = useState<any>({});
|
||||
const [renderKey, setRenderKey] = useState(0);
|
||||
|
||||
const handleContentChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||
) => {
|
||||
const { name, value } = e.target;
|
||||
console.log(newItemContent);
|
||||
console.log(Object.keys(newItemContent).length);
|
||||
setNewItemContent((prev: any) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const initializeSelectField = (key: string) => {
|
||||
if (!newItemContent[key]) {
|
||||
setNewItemContent((prev: any) => ({
|
||||
...prev,
|
||||
[key]: [],
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreate = () => {
|
||||
if (onCreate(newItemContent)) {
|
||||
setNewItemContent({});
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleDrawer = () => {
|
||||
setIsOpen(!isOpen);
|
||||
if (isFull) {
|
||||
setIsFull(!isFull);
|
||||
}
|
||||
if (!isOpen) {
|
||||
setRenderKey((prev) => prev + 1);
|
||||
}
|
||||
};
|
||||
|
||||
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":
|
||||
initializeSelectField(detail.key);
|
||||
inputField = (
|
||||
<TagsInput
|
||||
key={`${detail.key}-${renderKey}`}
|
||||
presetValue={[]}
|
||||
presetOptions={
|
||||
detail.presetOptionsValues || []
|
||||
}
|
||||
setPresetOptions={
|
||||
detail.presetOptionsSetter ||
|
||||
(() => {})
|
||||
}
|
||||
onTagsChange={(
|
||||
tags: Set<string>
|
||||
) => {
|
||||
setNewItemContent(
|
||||
(prev: any) => ({
|
||||
...prev,
|
||||
[detail.key]:
|
||||
Array.from(tags),
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
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;
|
|
@ -106,7 +106,11 @@ export default function ResourceTable({ data, setData }: ResourceTableProps) {
|
|||
),
|
||||
cell: (info) => (
|
||||
<div className="flex flex-wrap gap-2 items-center px-2">
|
||||
<Tag>{info.getValue()}</Tag>
|
||||
<Tag>
|
||||
{info.getValue().length != 0
|
||||
? info.getValue()
|
||||
: "no program"}
|
||||
</Tag>
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
|
@ -127,5 +131,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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -115,7 +115,11 @@ export default function ServiceTable({ data, setData }: ServiceTableProps) {
|
|||
),
|
||||
cell: (info) => (
|
||||
<div className="flex flex-wrap gap-2 items-center px-2">
|
||||
<Tag>{info.getValue()}</Tag>
|
||||
<Tag>
|
||||
{info.getValue().length != 0
|
||||
? info.getValue()
|
||||
: "no program"}
|
||||
</Tag>
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
|
@ -128,9 +132,13 @@ export default function ServiceTable({ data, setData }: ServiceTableProps) {
|
|||
),
|
||||
cell: (info) => (
|
||||
<div className="flex flex-wrap gap-2 items-center px-2">
|
||||
{info.getValue().map((tag: string, index: number) => {
|
||||
return <Tag key={index}>{tag}</Tag>;
|
||||
})}
|
||||
{info.getValue().length > 0 ? (
|
||||
info.getValue().map((tag: string, index: number) => {
|
||||
return <Tag key={index}>{tag}</Tag>;
|
||||
})
|
||||
) : (
|
||||
<Tag>no requirements</Tag>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
|
@ -151,5 +159,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,33 @@ 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[];
|
||||
};
|
||||
|
||||
/** Validates that all required fields in a new item have values */
|
||||
const validateNewItem = (newItem: any, details: Details[]): boolean => {
|
||||
const hasEmptyFields = details.some((detail) => {
|
||||
const value = newItem[detail.key];
|
||||
return (
|
||||
value === undefined ||
|
||||
value === null ||
|
||||
value === "" ||
|
||||
(Array.isArray(value) && value.length === 0)
|
||||
);
|
||||
});
|
||||
|
||||
if (hasEmptyFields) {
|
||||
alert("Please fill in all fields before creating a new item");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/** Fuzzy search function */
|
||||
|
@ -53,42 +75,43 @@ export default function Table<T extends DataPoint>({
|
|||
data,
|
||||
setData,
|
||||
columns,
|
||||
details,
|
||||
}: TableProps<T>) {
|
||||
const columnHelper = createColumnHelper<T>();
|
||||
|
||||
/** Sorting function based on visibility */
|
||||
const visibilitySort = (a: T, b: T) =>
|
||||
a.visible === b.visible ? 0 : a.visible ? -1 : 1;
|
||||
// /** Sorting function based on visibility */
|
||||
// const visibilitySort = (a: T, b: T) =>
|
||||
// a.visible === b.visible ? 0 : a.visible ? -1 : 1;
|
||||
|
||||
// Sort data on load
|
||||
useEffect(() => {
|
||||
setData((prevData) => prevData.sort(visibilitySort));
|
||||
}, [setData]);
|
||||
// // Sort data on load
|
||||
// useEffect(() => {
|
||||
// setData((prevData) => prevData.sort(visibilitySort));
|
||||
// }, [setData]);
|
||||
|
||||
// Data manipulation methods
|
||||
// TODO: Connect data manipulation methods to the database (deleteData, hideData, addData)
|
||||
const deleteData = (dataId: number) => {
|
||||
console.log(data);
|
||||
setData((currentData) =>
|
||||
currentData.filter((data) => data.id !== dataId)
|
||||
);
|
||||
};
|
||||
// // Data manipulation methods
|
||||
// // TODO: Connect data manipulation methods to the database (deleteData, hideData, addData)
|
||||
// const deleteData = (dataId: number) => {
|
||||
// console.log(data);
|
||||
// setData((currentData) =>
|
||||
// currentData.filter((data) => data.id !== dataId)
|
||||
// );
|
||||
// };
|
||||
|
||||
const hideData = (dataId: number) => {
|
||||
console.log(`Toggling visibility for data with ID: ${dataId}`);
|
||||
setData((currentData) => {
|
||||
const newData = currentData
|
||||
.map((data) =>
|
||||
data.id === dataId
|
||||
? { ...data, visible: !data.visible }
|
||||
: data
|
||||
)
|
||||
.sort(visibilitySort);
|
||||
// const hideData = (dataId: number) => {
|
||||
// console.log(`Toggling visibility for data with ID: ${dataId}`);
|
||||
// setData((currentData) => {
|
||||
// const newData = currentData
|
||||
// .map((data) =>
|
||||
// data.id === dataId
|
||||
// ? { ...data, visible: !data.visible }
|
||||
// : data
|
||||
// )
|
||||
// .sort(visibilitySort);
|
||||
|
||||
console.log(newData);
|
||||
return newData;
|
||||
});
|
||||
};
|
||||
// console.log(newData);
|
||||
// return newData;
|
||||
// });
|
||||
// };
|
||||
|
||||
const addData = () => {
|
||||
setData([...data]);
|
||||
|
@ -100,8 +123,10 @@ export default function Table<T extends DataPoint>({
|
|||
id: "options",
|
||||
cell: (props) => (
|
||||
<RowOptionMenu
|
||||
onDelete={() => deleteData(props.row.original.id)}
|
||||
onHide={() => hideData(props.row.original.id)}
|
||||
onDelete={() => {}}
|
||||
onHide={() => {}}
|
||||
// onDelete={() => deleteData(props.row.original.id)}
|
||||
// onHide={() => hideData(props.row.original.id)}
|
||||
/>
|
||||
),
|
||||
})
|
||||
|
@ -208,14 +233,21 @@ 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) => {
|
||||
if (!validateNewItem(newItem, details)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
newItem.visible = true;
|
||||
setData((prev) => [...prev, newItem]);
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
|
|
|
@ -97,8 +97,12 @@ export default function UserTable({ data, setData }: UserTableProps) {
|
|||
</>
|
||||
),
|
||||
cell: (info) => (
|
||||
<div className="flex ml-2 flex-wrap gap-2 items-center">
|
||||
<Tag>{info.getValue()}</Tag>
|
||||
<div className="flex flex-wrap gap-2 items-center px-2">
|
||||
<Tag>
|
||||
{info.getValue().length != 0
|
||||
? info.getValue()
|
||||
: "no role"}
|
||||
</Tag>
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
|
@ -122,13 +126,24 @@ export default function UserTable({ data, setData }: UserTableProps) {
|
|||
),
|
||||
cell: (info) => (
|
||||
<div className="flex ml-2 flex-wrap gap-2 items-center">
|
||||
{info.getValue().map((tag: string, index: number) => {
|
||||
return <Tag key={index}>{tag}</Tag>;
|
||||
})}
|
||||
{info.getValue().length > 0 ? (
|
||||
info.getValue().map((tag: string, index: number) => {
|
||||
return <Tag key={index}>{tag}</Tag>;
|
||||
})
|
||||
) : (
|
||||
<Tag>no programs</Tag>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
];
|
||||
|
||||
return <Table<User> data={data} setData={setData} columns={columns} />;
|
||||
return (
|
||||
<Table<User>
|
||||
data={data}
|
||||
setData={setData}
|
||||
columns={columns}
|
||||
details={userDetails}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,12 +8,14 @@ interface TagsInputProps {
|
|||
presetOptions: string[];
|
||||
presetValue: string[];
|
||||
setPresetOptions: Dispatch<SetStateAction<string[]>>;
|
||||
onTagsChange?: (tags: Set<string>) => void;
|
||||
}
|
||||
|
||||
const TagsInput: React.FC<TagsInputProps> = ({
|
||||
presetValue,
|
||||
presetOptions,
|
||||
setPresetOptions,
|
||||
onTagsChange,
|
||||
}) => {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [cellSelected, setCellSelected] = useState(false);
|
||||
|
@ -70,23 +72,28 @@ const TagsInput: React.FC<TagsInputProps> = ({
|
|||
const addTag = (e?: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
e?.stopPropagation();
|
||||
|
||||
const newTags = new Set(Array.from(tags).concat(inputValue));
|
||||
setOptions(new Set(Array.from(options).concat(inputValue)));
|
||||
setTags(new Set(Array.from(tags).concat(inputValue)));
|
||||
setTags(newTags);
|
||||
setFilteredOptions(new Set(Array.from(options).concat(inputValue)));
|
||||
setInputValue("");
|
||||
onTagsChange?.(newTags);
|
||||
};
|
||||
|
||||
const handleSelectTag = (tagToAdd: string) => {
|
||||
console.log(tagToAdd);
|
||||
console.log(tags);
|
||||
|
||||
if (!tags.has(tagToAdd)) {
|
||||
setTags(new Set(Array.from(tags).concat(tagToAdd)));
|
||||
const newTags = new Set(Array.from(tags).concat(tagToAdd));
|
||||
setTags(newTags);
|
||||
onTagsChange?.(newTags);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteTag = (tagToDelete: string) => {
|
||||
setTags(new Set(Array.from(tags).filter((tag) => tag !== tagToDelete)));
|
||||
const newTags = new Set(
|
||||
Array.from(tags).filter((tag) => tag !== tagToDelete)
|
||||
);
|
||||
setTags(newTags);
|
||||
onTagsChange?.(newTags);
|
||||
};
|
||||
|
||||
const handleDeleteTagOption = (tagToDelete: string) => {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user