📦 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:
parent
a88bf657a7
commit
26345cc9a6
9 changed files with 290 additions and 43 deletions
29
src/frontend/package-lock.json
generated
29
src/frontend/package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
66
src/frontend/src/modals/ConfirmationModal/index.tsx
Normal file
66
src/frontend/src/modals/ConfirmationModal/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
94
src/frontend/src/modals/UserManagementModal/index.tsx
Normal file
94
src/frontend/src/modals/UserManagementModal/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue