📦 chore(frontend): update package.json to add @radix-ui/react-form dependency

🔨 refactor(PaginatorComponent): remove unnecessary whitespace in paginate prop
📝 docs(ConfirmationModal): add missing JSDoc comments and improve code readability
📝 docs(UserManagementModal): add missing JSDoc comments and improve code readability
🔨 refactor(baseModal): improve code readability by adding a div wrapper for headerChild

 feat(AdminPage/index.tsx): add Pencil icon from lucide-react library to the import statement to use it in the component
 feat(AdminPage/index.tsx): add ConfirmationModal and UserManagementModal components to import statements to use them in the component
🐛 fix(AdminPage/index.tsx): remove unused handleSaveClick function
 feat(AdminPage/index.tsx): add modalEditOpen and modalDeleteOpen states to handle the opening and closing of the modals
 feat(AdminPage/index.tsx): add handleEditUser function to handle the edit user functionality
🐛 fix(AdminPage/index.tsx): remove unused handleSaveClick function
 feat(AdminPage/index.tsx): add ConfirmationModal component to handle the delete user functionality
 feat(AdminPage/index.tsx): add UserManagementModal component to handle the edit user functionality
 feat(components/index.ts): add ConfirmationModalType and UserManagementType types to handle the props of ConfirmationModal and UserManagementModal components
🐛 fix(components/index.ts): add missing newline at the end of the file
 feat(utils/styleUtils.ts): add UserMinus2 and UserPlus2 icons from lucide-react library to the import statement
This commit is contained in:
Cristhian Zanforlin Lousa 2023-08-09 12:13:58 -03:00
commit 26345cc9a6
9 changed files with 290 additions and 43 deletions

View file

@ -17,6 +17,7 @@
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-form": "^0.0.3",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-menubar": "^1.0.3",
@ -1777,6 +1778,34 @@
}
}
},
"node_modules/@radix-ui/react-form": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.0.3.tgz",
"integrity": "sha512-kgE+Z/haV6fxE5WqIXj05KkaXa3OkZASoTDy25yX2EIp/x0c54rOH/vFr5nOZTg7n7T1z8bSyXmiVIFP9bbhPQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.1",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-context": "1.0.1",
"@radix-ui/react-id": "1.0.1",
"@radix-ui/react-label": "2.0.2",
"@radix-ui/react-primitive": "1.0.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-icons": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz",

View file

@ -12,6 +12,7 @@
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-form": "^0.0.3",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-menubar": "^1.0.3",

View file

@ -20,7 +20,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);

View file

@ -0,0 +1,66 @@
import { useState } from "react";
import { Button } from "../../components/ui/button";
import { ConfirmationModalType } from "../../types/components";
import { nodeIconsLucide } from "../../utils/styleUtils";
import BaseModal from "../baseModal";
export default function ConfirmationModal({
title,
titleHeader,
modalContent,
modalContentTitle,
cancelText,
confirmationText,
children,
icon,
data,
index,
onConfirm,
}: ConfirmationModalType) {
const Icon: any = nodeIconsLucide[icon];
const [open, setOpen] = useState(false);
return (
<BaseModal size="x-small" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={titleHeader}>
<span className="pr-2">{title}</span>
<Icon
name="icon"
className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
{modalContentTitle != "" && (
<>
<strong>{modalContentTitle}</strong>
<br></br>
</>
)}
<span>{modalContent}</span>
</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

@ -0,0 +1,94 @@
import { useState } from "react";
import { Button } from "../../components/ui/button";
import { ConfirmationModalType, 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,
titleHeader,
cancelText,
confirmationText,
children,
icon,
data,
index,
onConfirm,
}: UserManagementType) {
const Icon: any = nodeIconsLucide[icon];
const [open, setOpen] = useState(false);
return (
<BaseModal size="medium" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={titleHeader}>
<span className="pr-2">{title}</span>
<Icon
name="icon"
className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<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>
</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

@ -112,7 +112,9 @@ function BaseModal({
{triggerChild}
</DialogTrigger>
<DialogContent className={minWidth}>
{headerChild}
<div className="word-break-break-word truncate-doubleline">
{headerChild}
</div>
<div className={`mt-2 flex flex-col ${height} w-full `}>
{ContentChild}
</div>

View file

@ -1,5 +1,5 @@
import _ from "lodash";
import { Trash2, X } from "lucide-react";
import { Pencil, Trash2, X } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import PaginatorComponent from "../../components/PaginatorComponent";
import { Button } from "../../components/ui/button";
@ -12,6 +12,8 @@ import {
TableHeader,
TableRow,
} from "../../components/ui/table";
import ConfirmationModal from "../../modals/ConfirmationModal";
import UserManagementModal from "../../modals/UserManagementModal";
export default function AdminPage() {
const [inputValue, setInputValue] = useState("");
@ -150,16 +152,13 @@ export default function AdminPage() {
const [editUser, setEditUser] = useState(-1);
const [editedUser, setEditedUser] = useState("");
const [modalEditOpen, setModalEditOpen] = useState(false);
const [modalDeleteOpen, setModalDeleteOpen] = useState(false);
useEffect(() => {
resetFilter();
}, []);
const handleEditClick = (index, userEdit) => {
setEditUser(index);
setEditedUser(userEdit);
};
const handleInputChange = (event, index) => {
const user = _.cloneDeepWith(userList.current);
user[index].password = event.target.value;
@ -172,10 +171,6 @@ export default function AdminPage() {
setEditedUser(event.target.value);
};
const handleSaveClick = (index) => {
setEditUser(-1);
};
function handleChangePagination(pageIndex: number, pageSize: number) {
setPageIndex(pageIndex);
setPageSize(pageSize);
@ -228,6 +223,8 @@ export default function AdminPage() {
resetFilter();
}
function handleEditUser(index) {}
return (
<>
<div className="grid grid-cols-6 gap-4">
@ -266,6 +263,7 @@ export default function AdminPage() {
<Button
onClick={() => {
resetFilter();
setInputValue("");
}}
variant="ghost"
className="h-8 px-2 lg:px-3"
@ -275,6 +273,22 @@ export default function AdminPage() {
</Button>
)}
</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>
</div>
</div>
<div
className="overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted
@ -285,7 +299,6 @@ export default function AdminPage() {
<TableRow>
<TableHead className="h-10">User</TableHead>
<TableHead className="h-10">E-mail</TableHead>
<TableHead className="h-10">Password</TableHead>
<TableHead className="h-10">Register Date</TableHead>
<TableHead className="h-10 w-[100px] text-right"></TableHead>
</TableRow>
@ -299,39 +312,51 @@ export default function AdminPage() {
<TableCell className="truncate py-2">
{user.email}
</TableCell>
<TableCell className="truncate py-2">
{editUser === index ? (
<Input
className="h-6 truncate"
onBlur={() => {
setEditUser(-1);
}}
value={editedUser}
onChange={(e) => handleInputChange(e, index)}
autoFocus
/>
) : (
<div
className="h-6 truncate"
onClick={() =>
handleEditClick(index, user.password)
}
>
{user.password}
</div>
)}
</TableCell>
<TableCell className="py-2">
{user.register_date.toString()}
</TableCell>
<TableCell className="flex w-[100px] py-2 text-right">
<Trash2
className="h-4 w-4 cursor-pointer"
strokeWidth={1.5}
onClick={() => {
handleDeleteUser(index);
}}
/>
<div className="flex">
<ConfirmationModal
title="Delete"
titleHeader="Delete User"
modalContentTitle="Attention!"
modalContent="Are you sure you want to delete this user? This action cannot be undone."
cancelText="Cancel"
confirmationText="Delete"
icon={"UserMinus2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleDeleteUser(index);
}}
>
<Trash2
className="mr-2 h-4 w-4 cursor-pointer"
strokeWidth={1.5}
/>
</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

@ -178,4 +178,30 @@ export type PaginatorComponentType = {
rowsCount?: number[];
totalRowsCount: number;
paginate: (pageIndex: number, pageSize: number) => void;
};
};
export type ConfirmationModalType = {
title: string;
titleHeader: string;
modalContent: string;
modalContentTitle: string;
cancelText: string;
confirmationText: string;
children: ReactElement;
icon: string;
data: any;
index: number;
onConfirm: (index, data) => void;
}
export type UserManagementType = {
title: string;
titleHeader: string;
cancelText: string;
confirmationText: string;
children: ReactElement;
icon: string;
data?: any;
index?: number;
onConfirm: (index, data) => void;
}

View file

@ -57,6 +57,8 @@ import {
Trash2,
Undo,
Upload,
UserMinus2,
UserPlus2,
Users2,
Variable,
Wand2,
@ -275,4 +277,6 @@ export const nodeIconsLucide = {
Upload,
MessageSquare,
MoreHorizontal,
UserMinus2,
UserPlus2
};