🔧 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:
Cristhian Zanforlin Lousa 2023-08-09 19:33:46 -03:00
commit 5009715835
7 changed files with 291 additions and 117 deletions

View file

@ -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>

View file

@ -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 }} />;
}

View file

@ -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>
);
}

View file

@ -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]";

View file

@ -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>

View file

@ -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
}
}

View file

@ -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
};