🔧 fix(PaginatorComponent): remove unused imports and replace lucide-react icons with IconComponent for better modularity and reusability
✨ feat(PaginatorComponent): add support for custom icons using IconComponent for better customization options 🔧 fix(UserManagementModal): remove unused imports and fix import order for better organization ✨ feat(UserManagementModal): add password confirmation validation and reset form on modal open for better user experience 🔧 fix(UserManagementModal): fix modal size to be full height for better visibility and usability ✨ feat(UserManagementModal): add form fields for username and password with validation for better user input handling 🔧 fix(UserManagementModal): fix form field labels and error messages for better clarity and user guidance ✨ feat(UserManagementModal): add form submission and confirmation handling for better user interaction and data management 🔧 fix(UserManagementModal): fix cancel button styling for better consistency and user experience 🔧 fix(baseModal): add missing size options to the size prop to support additional modal sizes ✨ feat(baseModal): add support for "small-h-full" and "medium-h-full" sizes to allow modals with smaller height and full width 🔧 fix(AdminPage): import missing IconComponent and ShadTooltip components ✨ feat(AdminPage): add functionality to handle editing and adding new users 🔧 fix(applies.css): add missing semicolon to .field-invalid and .label-invalid classes 🔧 fix(styleUtils.ts): import missing Pencil, ChevronsRight, and ChevronsLeft icons
This commit is contained in:
parent
26345cc9a6
commit
5009715835
7 changed files with 291 additions and 117 deletions
|
|
@ -1,9 +1,3 @@
|
|||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Select,
|
||||
|
|
@ -13,6 +7,7 @@ import {
|
|||
SelectValue,
|
||||
} from "../../components/ui/select";
|
||||
import { PaginatorComponentType } from "../../types/components";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
export default function PaginatorComponent({
|
||||
|
|
@ -20,7 +15,7 @@ export default function PaginatorComponent({
|
|||
pageIndex = 1,
|
||||
rowsCount = [10, 20, 30],
|
||||
totalRowsCount = 0,
|
||||
paginate
|
||||
paginate,
|
||||
}: PaginatorComponentType) {
|
||||
const [size, setPageSize] = useState(pageSize);
|
||||
const [index, setPageIndex] = useState(pageIndex);
|
||||
|
|
@ -68,7 +63,7 @@ export default function PaginatorComponent({
|
|||
}}
|
||||
>
|
||||
<span className="sr-only">Go to first page</span>
|
||||
<ChevronsLeft className="h-4 w-4" strokeWidth={1.5} />
|
||||
<IconComponent name="ChevronsLeft" className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
|
|
@ -86,7 +81,7 @@ export default function PaginatorComponent({
|
|||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<span className="sr-only">Go to previous page</span>
|
||||
<ChevronLeft className="h-4 w-4" strokeWidth={1.5} />
|
||||
<IconComponent name="ChevronLeft" className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
|
|
@ -102,7 +97,7 @@ export default function PaginatorComponent({
|
|||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<span className="sr-only">Go to next page</span>
|
||||
<ChevronRight className="h-4 w-4" strokeWidth={1.5} />
|
||||
<IconComponent name="ChevronRight" className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
|
|
@ -113,7 +108,7 @@ export default function PaginatorComponent({
|
|||
}}
|
||||
>
|
||||
<span className="sr-only">Go to last page</span>
|
||||
<ChevronsRight className="h-4 w-4" strokeWidth={1.5} />
|
||||
<IconComponent name="ChevronsRight" className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@ export default function IconComponent({
|
|||
iconColor,
|
||||
}: IconComponentProps): JSX.Element {
|
||||
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
|
||||
return <TargetIcon className={className} style={{ color: iconColor }} />;
|
||||
return <TargetIcon strokeWidth={1.5} className={className} style={{ color: iconColor }} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useState } from "react";
|
||||
import * as Form from "@radix-ui/react-form";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { ConfirmationModalType, UserManagementType } from "../../types/components";
|
||||
import { UserManagementType } from "../../types/components";
|
||||
import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||
import BaseModal from "../baseModal";
|
||||
import * as Form from '@radix-ui/react-form';
|
||||
|
||||
export default function UserManagementModal({
|
||||
title,
|
||||
|
|
@ -19,8 +19,25 @@ export default function UserManagementModal({
|
|||
const Icon: any = nodeIconsLucide[icon];
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const [password, setPassword] = useState(data?.password ?? "");
|
||||
const [username, setUserName] = useState(data?.user ?? "");
|
||||
const [confirmPassword, setConfirmPassword] = useState(data?.password ?? "");
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) {
|
||||
resetForm();
|
||||
}
|
||||
}, [data, open]);
|
||||
|
||||
function resetForm() {
|
||||
setPassword("");
|
||||
setUserName("");
|
||||
setConfirmPassword("");
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseModal size="medium" open={open} setOpen={setOpen}>
|
||||
<BaseModal size="medium-h-full" open={open} setOpen={setOpen}>
|
||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
||||
<BaseModal.Header description={titleHeader}>
|
||||
<span className="pr-2">{title}</span>
|
||||
|
|
@ -31,64 +48,190 @@ export default function UserManagementModal({
|
|||
/>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
if (password !== confirmPassword) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const data = Object.fromEntries(new FormData(event.currentTarget));
|
||||
resetForm();
|
||||
onConfirm(index ?? -1, data);
|
||||
setOpen(false);
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<div className="grid gap-5">
|
||||
<Form.Field name="username">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Username{" "}
|
||||
<span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={(input) => {
|
||||
setUserName(input.target.value);
|
||||
}}
|
||||
value={username}
|
||||
className="primary-input"
|
||||
required
|
||||
/>
|
||||
</Form.Control>
|
||||
<Form.Message match="valueMissing" className="field-invalid">
|
||||
Please enter your username
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
|
||||
<Form.Root className="FormRoot">
|
||||
<Form.Field className="FormField" name="email">
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||
<Form.Label className="FormLabel">Email</Form.Label>
|
||||
<Form.Message className="FormMessage" match="valueMissing">
|
||||
Please enter your email
|
||||
</Form.Message>
|
||||
<Form.Message className="FormMessage" match="typeMismatch">
|
||||
Please provide a valid email
|
||||
</Form.Message>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input className="Input" type="email" required />
|
||||
</Form.Control>
|
||||
</Form.Field>
|
||||
<Form.Field className="FormField" name="question">
|
||||
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||
<Form.Label className="FormLabel">Question</Form.Label>
|
||||
<Form.Message className="FormMessage" match="valueMissing">
|
||||
Please enter a question
|
||||
</Form.Message>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<textarea className="Textarea" required />
|
||||
</Form.Control>
|
||||
</Form.Field>
|
||||
<Form.Submit asChild>
|
||||
<button className="Button" style={{ marginTop: 10 }}>
|
||||
Post question
|
||||
</button>
|
||||
</Form.Submit>
|
||||
</Form.Root>
|
||||
<div className="flex flex-row">
|
||||
<div className="mr-3 basis-1/2">
|
||||
<Form.Field
|
||||
name="password"
|
||||
serverInvalid={password != confirmPassword}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Password{" "}
|
||||
<span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={(input) => {
|
||||
setPassword(input.target.value);
|
||||
}}
|
||||
value={password}
|
||||
className="primary-input"
|
||||
required
|
||||
/>
|
||||
</Form.Control>
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please enter a password
|
||||
</Form.Message>
|
||||
|
||||
{password != confirmPassword && (
|
||||
<Form.Message className="field-invalid">
|
||||
Passwords do not match
|
||||
</Form.Message>
|
||||
)}
|
||||
</Form.Field>
|
||||
</div>
|
||||
|
||||
<div className="basis-1/2">
|
||||
<Form.Field
|
||||
name="confirmpassword"
|
||||
serverInvalid={password != confirmPassword}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Confirm password{" "}
|
||||
<span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={(input) => {
|
||||
setConfirmPassword(input.target.value);
|
||||
}}
|
||||
value={confirmPassword}
|
||||
className="primary-input"
|
||||
required
|
||||
/>
|
||||
</Form.Control>
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please confirm your password
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/*
|
||||
<Form.Field name="email">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Email <span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please enter your email
|
||||
</Form.Message>
|
||||
<Form.Message className="field-invalid" match="typeMismatch">
|
||||
Please provide a valid email
|
||||
</Form.Message>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input className="primary-input" type="email" required />
|
||||
</Form.Control>
|
||||
</Form.Field> */}
|
||||
|
||||
{/*
|
||||
<Form.Field name="birth">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Date of birth{" "}
|
||||
<span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please enter your date of birth
|
||||
</Form.Message>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
type="date"
|
||||
className="primary-input"
|
||||
required
|
||||
max={new Date().toISOString().split("T")[0]}
|
||||
/>
|
||||
</Form.Control>
|
||||
</Form.Field> */}
|
||||
</div>
|
||||
|
||||
<div className="float-right">
|
||||
<Form.Submit asChild>
|
||||
<Button className="mr-3 mt-8">{confirmationText}</Button>
|
||||
</Form.Submit>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{cancelText}
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Root>
|
||||
</BaseModal.Content>
|
||||
|
||||
<BaseModal.Footer>
|
||||
<Button
|
||||
className="ml-3"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
onConfirm(index, data);
|
||||
}}
|
||||
>
|
||||
{confirmationText}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{cancelText}
|
||||
</Button>
|
||||
</BaseModal.Footer>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ interface BaseModalProps {
|
|||
open?: boolean;
|
||||
setOpen?: (open: boolean) => void;
|
||||
disable?: boolean;
|
||||
size?: "x-small" | "smaller" | "small" | "medium" | "large" | "large-h-full";
|
||||
size?: "x-small" | "smaller" | "small" | "medium" | "large" | "large-h-full" | "small-h-full" | "medium-h-full";
|
||||
}
|
||||
function BaseModal({
|
||||
open,
|
||||
|
|
@ -85,10 +85,16 @@ function BaseModal({
|
|||
minWidth = "min-w-[40vw]";
|
||||
height = "h-[40vh]";
|
||||
break;
|
||||
case "small-h-full":
|
||||
minWidth = "min-w-[40vw]";
|
||||
break;
|
||||
case "medium":
|
||||
minWidth = "min-w-[60vw]";
|
||||
height = "h-[60vh]";
|
||||
break;
|
||||
case "medium-h-full":
|
||||
minWidth = "min-w-[60vw]";
|
||||
break;
|
||||
case "large":
|
||||
minWidth = "min-w-[80vw]";
|
||||
height = "h-[80vh]";
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import _ from "lodash";
|
|||
import { Pencil, Trash2, X } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import PaginatorComponent from "../../components/PaginatorComponent";
|
||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Input } from "../../components/ui/input";
|
||||
import {
|
||||
|
|
@ -14,6 +15,7 @@ import {
|
|||
} from "../../components/ui/table";
|
||||
import ConfirmationModal from "../../modals/ConfirmationModal";
|
||||
import UserManagementModal from "../../modals/UserManagementModal";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
|
||||
export default function AdminPage() {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
|
@ -223,7 +225,27 @@ export default function AdminPage() {
|
|||
resetFilter();
|
||||
}
|
||||
|
||||
function handleEditUser(index) {}
|
||||
function handleEditUser(index, user) {
|
||||
const newUser = _.cloneDeepWith(userList.current);
|
||||
newUser[index].password = user.password;
|
||||
newUser[index].user = user.username;
|
||||
userList.current = newUser;
|
||||
resetFilter();
|
||||
}
|
||||
|
||||
function handleNewUser(user) {
|
||||
const newUser = {
|
||||
user: user.username,
|
||||
email: generateRandomString(50) + "@example.com",
|
||||
password: user.password,
|
||||
register_date: generateRandomDate(),
|
||||
};
|
||||
|
||||
userList.current.unshift(newUser);
|
||||
console.log(userList.current);
|
||||
|
||||
resetFilter();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -274,32 +296,29 @@ export default function AdminPage() {
|
|||
)}
|
||||
</div>
|
||||
<div>
|
||||
<UserManagementModal
|
||||
title="New User"
|
||||
titleHeader={"Add a new user"}
|
||||
cancelText="Cancel"
|
||||
confirmationText="Save"
|
||||
icon={"UserPlus2"}
|
||||
index={index}
|
||||
onConfirm={(index, user) => {
|
||||
handleDeleteUser(index);
|
||||
}}
|
||||
>
|
||||
<Button>New User</Button>
|
||||
</UserManagementModal>
|
||||
<UserManagementModal
|
||||
title="New User"
|
||||
titleHeader={"Add a new user"}
|
||||
cancelText="Cancel"
|
||||
confirmationText="Save"
|
||||
icon={"UserPlus2"}
|
||||
onConfirm={(index, user) => {
|
||||
handleNewUser(user);
|
||||
}}
|
||||
>
|
||||
<Button>New User</Button>
|
||||
</UserManagementModal>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div
|
||||
className="overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted
|
||||
custom-scroll min-[320px]:h-[20rem] md:h-[25rem] xl:h-[25rem] 2xl:h-[30rem]"
|
||||
custom-scroll min-[320px]:h-[20rem] md:h-[25rem] xl:h-[26rem] 2xl:h-[30rem]"
|
||||
>
|
||||
<Table className="table-fixed bg-muted outline-1 ">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="h-10">User</TableHead>
|
||||
<TableHead className="h-10">E-mail</TableHead>
|
||||
<TableHead className="h-10">Register Date</TableHead>
|
||||
<TableHead className="h-10">Password</TableHead>
|
||||
<TableHead className="h-10 w-[100px] text-right"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
|
@ -310,13 +329,29 @@ export default function AdminPage() {
|
|||
{user.user}
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2">
|
||||
{user.email}
|
||||
</TableCell>
|
||||
<TableCell className="py-2">
|
||||
{user.register_date.toString()}
|
||||
{user.password}
|
||||
</TableCell>
|
||||
<TableCell className="flex w-[100px] py-2 text-right">
|
||||
<div className="flex">
|
||||
<UserManagementModal
|
||||
title="Edit"
|
||||
titleHeader={`${user.user}`}
|
||||
cancelText="Cancel"
|
||||
confirmationText="Edit"
|
||||
icon={"UserPlus2"}
|
||||
data={user}
|
||||
index={index}
|
||||
onConfirm={(index, user) => {
|
||||
handleEditUser(index, user);
|
||||
}}
|
||||
>
|
||||
<ShadTooltip content="Edit" side="top">
|
||||
<IconComponent name="Pencil"
|
||||
className="h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</UserManagementModal>
|
||||
|
||||
<ConfirmationModal
|
||||
title="Delete"
|
||||
titleHeader="Delete User"
|
||||
|
|
@ -331,31 +366,12 @@ export default function AdminPage() {
|
|||
handleDeleteUser(index);
|
||||
}}
|
||||
>
|
||||
<Trash2
|
||||
className="mr-2 h-4 w-4 cursor-pointer"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<ShadTooltip content="Delete" side="top">
|
||||
<IconComponent name="Trash2"
|
||||
className="ml-2 h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</ConfirmationModal>
|
||||
<UserManagementModal
|
||||
title="Edit"
|
||||
titleHeader={`${user.user}`}
|
||||
cancelText="Cancel"
|
||||
confirmationText="Edit"
|
||||
icon={"UserPlus2"}
|
||||
data={user}
|
||||
index={index}
|
||||
onConfirm={(index, user) => {
|
||||
handleDeleteUser(index);
|
||||
}}
|
||||
>
|
||||
<Pencil
|
||||
className="h-4 w-4 cursor-pointer"
|
||||
onClick={() => {
|
||||
handleEditUser(index);
|
||||
}}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</UserManagementModal>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
|
|||
|
|
@ -1019,4 +1019,12 @@
|
|||
.chat-message-highlight {
|
||||
@apply rounded-md bg-indigo-100 px-0.5 dark:bg-indigo-900;
|
||||
}
|
||||
|
||||
.field-invalid{
|
||||
@apply font-medium text-[0.8rem] text-destructive absolute
|
||||
}
|
||||
|
||||
.label-invalid{
|
||||
@apply text-destructive
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,9 @@ import {
|
|||
X,
|
||||
XCircle,
|
||||
Zap,
|
||||
Pencil,
|
||||
ChevronsRight,
|
||||
ChevronsLeft
|
||||
} from "lucide-react";
|
||||
import { AirbyteIcon } from "../icons/Airbyte";
|
||||
import { AnthropicIcon } from "../icons/Anthropic";
|
||||
|
|
@ -278,5 +281,8 @@ export const nodeIconsLucide = {
|
|||
MessageSquare,
|
||||
MoreHorizontal,
|
||||
UserMinus2,
|
||||
UserPlus2
|
||||
UserPlus2,
|
||||
Pencil,
|
||||
ChevronsRight,
|
||||
ChevronsLeft
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue