mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-06 20:50:17 -04:00
Merge pull request #33 from cssgunc/varun-admin-GEN-59-page
i love git so much
This commit is contained in:
commit
e89b00b4b2
|
@ -16,7 +16,8 @@ import { ChangeEvent, useState, useEffect, FunctionComponent, useRef, ChangeEven
|
||||||
import { RowOptionMenu } from "./RowOptionMenu";
|
import { RowOptionMenu } from "./RowOptionMenu";
|
||||||
import { RowOpenAction } from "./RowOpenAction";
|
import { RowOpenAction } from "./RowOpenAction";
|
||||||
import { TableAction } from "./TableAction";
|
import { TableAction } from "./TableAction";
|
||||||
import { Bars2Icon, AtSymbolIcon, HashtagIcon, ArrowDownCircleIcon, PlusIcon } from "@heroicons/react/24/solid";
|
import { AtSymbolIcon, Bars2Icon, ArrowDownCircleIcon, PlusIcon } from "@heroicons/react/24/solid";
|
||||||
|
import TagsInput from "../TagsInput/Index";
|
||||||
import { rankItem } from "@tanstack/match-sorter-utils";
|
import { rankItem } from "@tanstack/match-sorter-utils";
|
||||||
import { TableCell } from "./TableCell";
|
import { TableCell } from "./TableCell";
|
||||||
import { PrimaryTableCell } from "./PrimaryTableCell";
|
import { PrimaryTableCell } from "./PrimaryTableCell";
|
||||||
|
@ -27,14 +28,15 @@ type User = {
|
||||||
id: number;
|
id: number;
|
||||||
created_at: any;
|
created_at: any;
|
||||||
username: string;
|
username: string;
|
||||||
role: "ADMIN" | "EMPLOYEE" | "VOLUNTEER";
|
role: "administrator" | "employee" | "volunteer";
|
||||||
email: string;
|
email: string;
|
||||||
program: "DOMESTIC" | "ECONOMIC" | "COMMUNITY";
|
program: "domestic" | "economic" | "community";
|
||||||
experience: number;
|
experience: number;
|
||||||
group?: string;
|
group?: string;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// For search
|
// For search
|
||||||
const fuzzyFilter = (row: Row<any>, columnId: string, value: any, addMeta: (meta: any) => void) => {
|
const fuzzyFilter = (row: Row<any>, columnId: string, value: any, addMeta: (meta: any) => void) => {
|
||||||
// Rank the item
|
// Rank the item
|
||||||
|
@ -74,6 +76,17 @@ export const Table = () => {
|
||||||
return newData;
|
return newData;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
const [presetOptions, setPresetOptions] = useState(["administrator", "volunteer", "employee"]);
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
columnHelper.display({
|
columnHelper.display({
|
||||||
|
@ -81,12 +94,17 @@ export const Table = () => {
|
||||||
cell: props => <RowOptionMenu onDelete={() => deleteUser(props.row.original.id)} onHide={() => hideUser(props.row.original.id)} />
|
cell: props => <RowOptionMenu onDelete={() => deleteUser(props.row.original.id)} onHide={() => hideUser(props.row.original.id)} />
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("username", {
|
columnHelper.accessor("username", {
|
||||||
header: () => <><Bars2Icon className="inline align-top h-4 mr-2" /> Username</>,
|
header: () => <><Bars2Icon className="inline align-top h-4" /> Username</>,
|
||||||
cell: PrimaryTableCell,
|
cell: (info) => <RowOpenAction title={info.getValue()} rowData={info.row.original} onRowUpdate={handleRowUpdate} />,
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("role", {
|
columnHelper.accessor("role", {
|
||||||
header: () => <><ArrowDownCircleIcon className="inline align-top h-4" /> Role</>,
|
header: () => <><ArrowDownCircleIcon className="inline align-top h-4" /> Role</>,
|
||||||
cell: TableCell,
|
cell: (info) => <TagsInput presetValue={info.getValue() }
|
||||||
|
presetOptions={presetOptions}
|
||||||
|
setPresetOptions={setPresetOptions}
|
||||||
|
getTagColor={getTagColor}
|
||||||
|
setTagColors={setTagColors}
|
||||||
|
/>,
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("email", {
|
columnHelper.accessor("email", {
|
||||||
header: () => <><AtSymbolIcon className="inline align-top h-4" /> Email</>,
|
header: () => <><AtSymbolIcon className="inline align-top h-4" /> Email</>,
|
||||||
|
@ -94,7 +112,7 @@ export const Table = () => {
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("program", {
|
columnHelper.accessor("program", {
|
||||||
header: () => <><ArrowDownCircleIcon className="inline align-top h-4" /> Program</>,
|
header: () => <><ArrowDownCircleIcon className="inline align-top h-4" /> Program</>,
|
||||||
cell: TableCell,
|
cell: (info) => info.renderValue(),
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -119,6 +137,16 @@ export const Table = () => {
|
||||||
// TODO: Filtering
|
// TODO: Filtering
|
||||||
// TODO: Sorting
|
// TODO: Sorting
|
||||||
|
|
||||||
|
// added this fn for editing rows
|
||||||
|
const handleRowUpdate = (updatedRow: User) => {
|
||||||
|
const dataIndex = data.findIndex((row) => row.id === updatedRow.id);
|
||||||
|
if (dataIndex !== -1) {
|
||||||
|
const updatedData = [...data];
|
||||||
|
updatedData[dataIndex] = updatedRow;
|
||||||
|
setData(updatedData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
columns,
|
columns,
|
||||||
data,
|
data,
|
||||||
|
@ -149,6 +177,16 @@ export const Table = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleRowData = (row: any) => {
|
||||||
|
const rowData: any = {};
|
||||||
|
row.cells.forEach((cell: any) => {
|
||||||
|
rowData[cell.column.id] = cell.value;
|
||||||
|
});
|
||||||
|
// Use rowData object containing data from all columns for the current row
|
||||||
|
console.log(rowData);
|
||||||
|
return rowData;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex flex-row justify-end">
|
<div className="flex flex-row justify-end">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Drawer from "@/components/page/Drawer";
|
import Drawer from "@/components/page/Drawer";
|
||||||
import {ChangeEvent, useState} from "react";
|
import {ChangeEvent, useState} from "react";
|
||||||
|
|
||||||
export const RowOpenAction = ({ title }) => {
|
export const RowOpenAction = ({ title, rowData, onRowUpdate }) => {
|
||||||
const [pageContent, setPageContent] = useState("")
|
const [pageContent, setPageContent] = useState("")
|
||||||
|
|
||||||
const handleDrawerContentChange = (newContent) => {
|
const handleDrawerContentChange = (newContent) => {
|
||||||
|
@ -14,7 +14,8 @@ export const RowOpenAction = ({ title }) => {
|
||||||
<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 title="My Drawer Title" editableContent={pageContent} onSave={handleDrawerContentChange}>{pageContent}</Drawer>
|
{/* Added OnRowUpdate to drawer */}
|
||||||
|
<Drawer title="My Drawer Title" editableContent={pageContent} rowContent={rowData} onSave={handleDrawerContentChange} onRowUpdate={onRowUpdate}>{pageContent}</Drawer>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
10
compass/components/TagsInput/CreateNewTagAction.tsx
Normal file
10
compass/components/TagsInput/CreateNewTagAction.tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { Tag } from "./Tag"
|
||||||
|
|
||||||
|
export const CreateNewTagAction = ({ input }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row space-x-2 hover:bg-gray-100 rounded-md py-2 p-2 items-center">
|
||||||
|
<p className="capitalize">Create</p>
|
||||||
|
<Tag active={false} onDelete={null} >{input}</Tag>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
49
compass/components/TagsInput/DropdownAction.tsx
Normal file
49
compass/components/TagsInput/DropdownAction.tsx
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { EllipsisHorizontalIcon, TrashIcon } from "@heroicons/react/24/solid";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export const DropdownAction = ({ tag, handleDeleteTag, handleEditTag }) => {
|
||||||
|
const [isVisible, setVisible] = useState(false);
|
||||||
|
const [inputValue, setInputValue] = useState(tag);
|
||||||
|
|
||||||
|
|
||||||
|
const editTagOption = (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
handleEditTag(tag, inputValue)
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setInputValue(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<EllipsisHorizontalIcon
|
||||||
|
className="w-5 text-gray-500"
|
||||||
|
onClick={() => setVisible(!isVisible)}
|
||||||
|
/>
|
||||||
|
{isVisible && (
|
||||||
|
<div className="absolute flex flex-col justify-start z-50 rounded-md bg-white border border-gray-200 shadow p-2 space-y-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={inputValue}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
onKeyDown={editTagOption}
|
||||||
|
autoFocus
|
||||||
|
className="bg-gray-50 text-2xs focus:outline-none rounded-md font-normal text-gray-800 p-1 border-2 focus:border-blue-200"
|
||||||
|
/>
|
||||||
|
<button onClick={() => {
|
||||||
|
handleDeleteTag(inputValue)
|
||||||
|
setVisible(false)
|
||||||
|
}} className="justify-start flex flex-row space-x-4 hover:bg-gray-100 rounded-md items-center p-2 px-2">
|
||||||
|
<TrashIcon className="w-3 h-3" />
|
||||||
|
<p>Delete</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
153
compass/components/TagsInput/Index.tsx
Normal file
153
compass/components/TagsInput/Index.tsx
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
import React, { useState, useRef } from "react";
|
||||||
|
import "tailwindcss/tailwind.css";
|
||||||
|
import { TagsArray } from "./TagsArray";
|
||||||
|
import { TagDropdown } from "./TagDropdown";
|
||||||
|
import { CreateNewTagAction } from "./CreateNewTagAction";
|
||||||
|
|
||||||
|
interface TagsInputProps {
|
||||||
|
presetOptions: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const TagsInput: React.FC<TagsInputProps> = ({
|
||||||
|
presetValue,
|
||||||
|
presetOptions,
|
||||||
|
setPresetOptions,
|
||||||
|
getTagColor
|
||||||
|
}) => {
|
||||||
|
const [inputValue, setInputValue] = useState("");
|
||||||
|
const [cellSelected, setCellSelected] = useState(false);
|
||||||
|
const [tags, setTags] = useState<Set<string>>(
|
||||||
|
new Set(presetValue ? [presetValue] : [])
|
||||||
|
);
|
||||||
|
const [options, setOptions] = useState<Set<string>>(new Set(presetOptions));
|
||||||
|
const dropdown = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (!cellSelected) {
|
||||||
|
setCellSelected(true);
|
||||||
|
// Add event listener only after setting cellSelected to true
|
||||||
|
setTimeout(() => {
|
||||||
|
window.addEventListener("click", handleOutsideClick);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOutsideClick = (event) => {
|
||||||
|
if (dropdown.current && !dropdown.current.contains(event.target)) {
|
||||||
|
setCellSelected(false);
|
||||||
|
// Remove event listener after handling outside click
|
||||||
|
window.removeEventListener("click", handleOutsideClick);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setOptions(() => {
|
||||||
|
const newOptions = presetOptions.filter(item => item.includes(e.target.value.toLowerCase()));
|
||||||
|
return new Set(newOptions);
|
||||||
|
})
|
||||||
|
setInputValue(e.target.value); // Update input value state
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key === "Enter" && inputValue.trim()) {
|
||||||
|
// 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 handleSelectTag = (tagToAdd: string) => {
|
||||||
|
if (!tags.has(tagToAdd)) {
|
||||||
|
// Corrected syntax for checking if a Set contains an item
|
||||||
|
setTags((prevTags) => new Set(prevTags).add(tagToAdd));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteTag = (tagToDelete: string) => {
|
||||||
|
setTags((prevTags) => {
|
||||||
|
const updatedTags = new Set(prevTags);
|
||||||
|
updatedTags.delete(tagToDelete);
|
||||||
|
return updatedTags;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteTagOption = (tagToDelete: string) => {
|
||||||
|
// setPresetOptions(presetOptions.filter(tag => tag !== tagToDelete));
|
||||||
|
setOptions((prevOptions) => {
|
||||||
|
const updatedOptions = new Set(prevOptions);
|
||||||
|
updatedOptions.delete(tagToDelete);
|
||||||
|
return updatedOptions;
|
||||||
|
});
|
||||||
|
if (tags.has(tagToDelete)) {
|
||||||
|
handleDeleteTag(tagToDelete);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditTag = (oldTag: string, newTag: string) => {
|
||||||
|
if (oldTag !== newTag) {
|
||||||
|
setTags((prevTags) => {
|
||||||
|
const tagsArray = Array.from(prevTags);
|
||||||
|
const oldTagIndex = tagsArray.indexOf(oldTag);
|
||||||
|
if (oldTagIndex !== -1) {
|
||||||
|
tagsArray.splice(oldTagIndex, 1, newTag);
|
||||||
|
}
|
||||||
|
return new Set(tagsArray);
|
||||||
|
});
|
||||||
|
|
||||||
|
setOptions((prevOptions) => {
|
||||||
|
const optionsArray = Array.from(prevOptions);
|
||||||
|
const oldTagIndex = optionsArray.indexOf(oldTag);
|
||||||
|
if (oldTagIndex !== -1) {
|
||||||
|
optionsArray.splice(oldTagIndex, 1, newTag);
|
||||||
|
}
|
||||||
|
return new Set(optionsArray);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="cursor-pointer" onClick={handleClick}>
|
||||||
|
{!cellSelected ? (
|
||||||
|
<TagsArray handleDelete={handleDeleteTag} tags={tags} />
|
||||||
|
) : (
|
||||||
|
<div ref={dropdown}>
|
||||||
|
<div className="absolute w-64 z-50 -ml-3 -mt-7">
|
||||||
|
<div className="rounded-md border border-gray-200 shadow">
|
||||||
|
<div className="flex flex-wrap rounded-t-md items-center gap-2 bg-gray-50 p-2">
|
||||||
|
<TagsArray handleDelete={handleDeleteTag} active tags={tags} />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={inputValue}
|
||||||
|
placeholder="Search for an option..."
|
||||||
|
onChange={handleInputChange}
|
||||||
|
onKeyDown={handleAddTag}
|
||||||
|
className="focus:outline-none bg-transparent"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex rounded-b-md bg-white flex-col border-t border-gray-100 text-2xs font-medium text-gray-500 p-2">
|
||||||
|
<p className="capitalize">Select an option or create one</p>
|
||||||
|
<TagDropdown
|
||||||
|
handleDeleteTag={handleDeleteTagOption}
|
||||||
|
handleEditTag={handleEditTag}
|
||||||
|
handleAdd={handleSelectTag}
|
||||||
|
tags={options}
|
||||||
|
/>
|
||||||
|
{inputValue.length > 0 && (
|
||||||
|
<CreateNewTagAction input={inputValue} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TagsInput;
|
0
compass/components/TagsInput/Input.tsx
Normal file
0
compass/components/TagsInput/Input.tsx
Normal file
18
compass/components/TagsInput/Tag.tsx
Normal file
18
compass/components/TagsInput/Tag.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { XMarkIcon } from "@heroicons/react/24/solid";
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
export const Tag = ({ children, handleDelete, active = false }) => {
|
||||||
|
|
||||||
|
const [tagColor, setTagColor] = useState('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={`font-normal bg-cyan-100 text-gray-800 flex flex-row p-1 px-2 rounded-lg`}>
|
||||||
|
{children}
|
||||||
|
{active && handleDelete && (
|
||||||
|
<button onClick={() => handleDelete(children)}>
|
||||||
|
<XMarkIcon className={`ml-1 w-3 text-cyan-500`} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
16
compass/components/TagsInput/TagDropdown.tsx
Normal file
16
compass/components/TagsInput/TagDropdown.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
import { Tag } from "./Tag";
|
||||||
|
import { DropdownAction } from "./DropdownAction";
|
||||||
|
|
||||||
|
export const TagDropdown = ({ tags, handleEditTag,handleDeleteTag,handleAdd }) => {
|
||||||
|
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} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
16
compass/components/TagsInput/TagsArray.tsx
Normal file
16
compass/components/TagsInput/TagsArray.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { Tag } from "./Tag"
|
||||||
|
|
||||||
|
export const TagsArray = ({ tags, handleDelete, active = false }) => {
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div className="flex flex-wrap gap-2 items-center">
|
||||||
|
{
|
||||||
|
Array.from(tags).map((tag) => {
|
||||||
|
return (
|
||||||
|
<Tag handleDelete={handleDelete} active={active}>{tag}</Tag>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
29
compass/components/page/Card.tsx
Normal file
29
compass/components/page/Card.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import React, { ReactNode, useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
interface TagProps {
|
||||||
|
text: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const Card: React.FC<TagProps> = ({children, text, icon, onClick }) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
className="flex flex-row space-x-2 items-start justify-start border border-gray-200 bg-white hover:bg-gray-50 shadow rounded-md p-4 focus:outline-none focus:ring-2 focus:ring-purple-600 focus:ring-opacity-50 w-1/4"
|
||||||
|
>
|
||||||
|
<span className="h-5 text-purple-700 w-5">
|
||||||
|
{icon}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-gray-800 font-semibold">
|
||||||
|
{text}
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Card;
|
|
@ -1,8 +1,10 @@
|
||||||
import { FunctionComponent, ReactNode } from 'react';
|
import { FunctionComponent, ReactElement, ReactNode } from 'react';
|
||||||
import Button from '@/components/Button'
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import {DATATYPE} from '@/utils/constants'
|
import { ChevronDoubleLeftIcon } from '@heroicons/react/24/solid';
|
||||||
import InlineLink from '@/components/InlineLink'
|
import { BookmarkIcon, XMarkIcon, StarIcon as SolidStarIcon, EnvelopeIcon, UserIcon } from "@heroicons/react/24/solid";
|
||||||
|
import { ArrowsPointingOutIcon, ArrowsPointingInIcon, StarIcon as OutlineStarIcon, ListBulletIcon } from '@heroicons/react/24/outline';
|
||||||
|
import Card from '@/components/page/Card'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type DrawerProps = {
|
type DrawerProps = {
|
||||||
|
@ -13,60 +15,140 @@ type DrawerProps = {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
editableContent?: any;
|
editableContent?: any;
|
||||||
onSave?: (content: any) => void;
|
onSave?: (content: any) => void;
|
||||||
|
rowContent?: any;
|
||||||
|
onRowUpdate?: (content: any) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Drawer: FunctionComponent<DrawerProps> = ({ title, children, onSave, editableContent }) => {
|
interface EditContent {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
content: string;
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
isEditing: boolean;
|
||||||
const [editContent, setEditContent] = useState(editableContent || '');
|
}
|
||||||
|
|
||||||
const toggleDrawer = () => setIsOpen(!isOpen);
|
|
||||||
const toggleEditing = () => setIsEditing(!isEditing);
|
const Drawer: FunctionComponent<DrawerProps> = ({ title, children, onSave, editableContent, rowContent, onRowUpdate }) => {
|
||||||
const handleContentChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
setEditContent(event.target.value);
|
const [isFull, setIsFull] = useState(false);
|
||||||
|
const [currentCardIcon, setCurrentCardIcon] = useState<string>('');
|
||||||
|
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 ${
|
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"
|
isOpen ? "translate-x-0 shadow-xl" : "translate-x-full"
|
||||||
}`;
|
|
||||||
|
|
||||||
const saveChanges = () => {
|
} ${isFull ? "w-full" : "w-1/2"}`;
|
||||||
console.log(editContent);
|
|
||||||
if (onSave) {
|
|
||||||
onSave(editContent);
|
|
||||||
}
|
|
||||||
setIsEditing(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const addRow = () => {
|
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" />
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button className="ml-2 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>
|
<button className={"ml-2 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={drawerClassName}>
|
||||||
<div className="flex items-center justify-between p-4 border-b">
|
<div className="flex items-center justify-between p-4 border-b">
|
||||||
<h2>{title}</h2>
|
<div className="flex flex-row items-center justify-between space-x-2">
|
||||||
|
<span className="h-5 text-purple-700 w-5">{currentCardIcon}</span>
|
||||||
|
<h2 style={{ fontSize: '20px' }} className = "text-sm text-gray-800 font-semibold">{rowContent.username}</h2>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button onClick={toggleEditing}>{isEditing ? 'Cancel' : 'Edit'}</Button>
|
<button onClick={toggleFavorite} className="py-2 text-gray-500 hover:text-gray-800 mr-2">
|
||||||
<Button onClick={toggleDrawer}>«</Button>
|
{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>
|
</div>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
{isEditing ? (
|
<table className="p-4">
|
||||||
<>
|
<tbody>
|
||||||
<input
|
<tr className="w-full text-sm items-center flex flex-row justify-between">
|
||||||
type= 'text'
|
<div className="flex flex-row space-x-2 text-gray-500">
|
||||||
value={editContent}
|
<td><UserIcon className="h-4 w-4" /></td>
|
||||||
onChange={handleContentChange}
|
<td className="w-20">Username</td>
|
||||||
className="border p-2 w-full"
|
</div>
|
||||||
/>
|
<td className="w-3/4">
|
||||||
<InlineLink onClick={saveChanges}>Save</InlineLink>
|
<input
|
||||||
</>
|
type="text"
|
||||||
) : (
|
name="username"
|
||||||
children
|
value={tempRowContent.username}
|
||||||
)}
|
onChange={handleTempRowContentChange}
|
||||||
|
onKeyDown={handleEnterPress}
|
||||||
|
className="w-full p-1 focus:outline-gray-200 hover:bg-gray-50"
|
||||||
|
/></td>
|
||||||
|
</tr>
|
||||||
|
<tr className="w-full text-sm items-center flex flex-row justify-between">
|
||||||
|
<div className="flex flex-row space-x-2 text-gray-500">
|
||||||
|
<td><ListBulletIcon className="h-4 w-4" /></td>
|
||||||
|
<td className="w-20">Role</td>
|
||||||
|
</div>
|
||||||
|
<td className="w-3/4">
|
||||||
|
{rowContent.role}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="w-full text-sm items-center flex flex-row justify-between">
|
||||||
|
<div className="flex flex-row space-x-2 text-gray-500">
|
||||||
|
<td><EnvelopeIcon className="h-4 w-4" /></td>
|
||||||
|
<td className="w-20">Email</td>
|
||||||
|
</div>
|
||||||
|
<td className="w-3/4">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="email"
|
||||||
|
value={tempRowContent.email}
|
||||||
|
onChange={handleTempRowContentChange}
|
||||||
|
onKeyDown={handleEnterPress}
|
||||||
|
className="w-full p-1 focus:outline-gray-200 hover:bg-gray-50"
|
||||||
|
/></td>
|
||||||
|
</tr>
|
||||||
|
<tr className="w-full text-sm items-center flex flex-row justify-between">
|
||||||
|
<div className="flex flex-row space-x-2 text-gray-500">
|
||||||
|
<td><ListBulletIcon className="h-4 w-4" /></td>
|
||||||
|
<td className="w-20">Type of Program</td>
|
||||||
|
</div>
|
||||||
|
<td className="w-3/4">
|
||||||
|
{rowContent.program}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,3 +156,4 @@ const Drawer: FunctionComponent<DrawerProps> = ({ title, children, onSave, edita
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Drawer;
|
export default Drawer;
|
||||||
|
|
||||||
|
|
30
compass/components/page/DropDown.tsx
Normal file
30
compass/components/page/DropDown.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import React, { ChangeEvent, FunctionComponent } from 'react';
|
||||||
|
|
||||||
|
// Define the shape of a single option
|
||||||
|
interface DropdownOption {
|
||||||
|
label: string;
|
||||||
|
value: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the props for the Dropdown component
|
||||||
|
interface DropdownProps {
|
||||||
|
options: DropdownOption[];
|
||||||
|
onChange: (event: ChangeEvent<HTMLSelectElement>) => void; // Type for change event on <select>
|
||||||
|
defaultValue?: string | number;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dropdown Component
|
||||||
|
const Dropdown: FunctionComponent<DropdownProps> = ({ options, onChange, defaultValue, id }) => {
|
||||||
|
return (
|
||||||
|
<select id={id} defaultValue={defaultValue} onChange={onChange} className="form-select form-select-lg mb-3">
|
||||||
|
{options.map((option, index) => (
|
||||||
|
<option key={index} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dropdown;
|
|
@ -1,4 +0,0 @@
|
||||||
// components/Field.tsx
|
|
||||||
import { FunctionComponent, ReactNode } from 'react';
|
|
||||||
import Button from '@/components/Button'
|
|
||||||
import React, { useState } from 'react';
|
|
Loading…
Reference in New Issue
Block a user