diff --git a/src/App.tsx b/src/App.tsx index 9e8017a16fbcb9fc7953db21992516a23483841b..bb708f553468a0ebcc671b6d0f06f8c1673c9f46 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,6 +19,7 @@ import { OrganizationDashboard } from "./components/Dashboard/OrganizationDashbo import { UniversityDashboard } from "./components/Dashboard/UniversityDashboard" import { handleGetInfo } from "./utils/auth" import UniversityHome from "./components/Home/UniversityHome" +import Acceptance from "./components/Acceptance/Acceptance" const ROLES = { Organization: "organization", @@ -40,13 +41,17 @@ function App() { }) const getInfo = async () => { - const response = await handleGetInfo() - setUserInfo({ - user_id: response?.data.user_id, - name: response?.data.name, - email: response?.data.email, - role: response?.data.roles - }) + try { + const response = await handleGetInfo() + setUserInfo({ + user_id: response?.data.user_id, + name: response?.data.name, + email: response?.data.email, + role: response?.data.roles + }) + } catch (error) { + console.log(error) + } } useEffect(() => { @@ -191,7 +196,7 @@ function App() { path="scholarships/:scholarshipid/acceptance" element={ <Sidebar> - <Scholarships /> + <Acceptance /> </Sidebar> } /> diff --git a/src/components/Acceptance/Acceptance.tsx b/src/components/Acceptance/Acceptance.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c809ece38482ba99afd6d1916cf5b17ba798f7bb --- /dev/null +++ b/src/components/Acceptance/Acceptance.tsx @@ -0,0 +1,148 @@ +import React, { useState } from "react"; +import { + Button, + Icon, + InputGroup, + InputLeftElement, + Input, + Box, + Heading, + Stack, + TableContainer, + Table, + Thead, + Tr, + Th, + Tbody, + Td, + useToast, + useDisclosure, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalCloseButton, + ModalBody, + ModalFooter, +} from "@chakra-ui/react"; +import { Search2Icon, CheckIcon, CloseIcon } from "@chakra-ui/icons"; +import { useParams } from "react-router-dom"; + +const Acceptance = () => { + const { scholarshipid } = useParams(); + const toast = useToast(); + const { isOpen: acceptModalIsOpen, onOpen: onAcceptModalOpen, onClose: onAcceptModalClose } = useDisclosure(); + const { isOpen: rejectModalIsOpen, onOpen: onRejectModalOpen, onClose: onRejectModalClose } = useDisclosure(); + + const acceptAction = () => { + onAcceptModalOpen(); + }; + + const rejectAction = () => { + onRejectModalOpen(); + }; + + const handleAcceptConfirm = () => { + toast({ + title: "Acceptance set", + description: "This applicant has been accepted", + status: "success", + duration: 9000, + isClosable: true, + }); + onAcceptModalClose(); + }; + + const handleRejectConfirm = () => { + toast({ + title: "Rejection set", + description: "This applicant has been rejected", + status: "success", + duration: 9000, + isClosable: true, + }); + onRejectModalClose(); + }; + + return ( + <Box> + <Heading>Scholarship Acceptance</Heading> + <Stack> + {/* Search Bar */} + <InputGroup borderRadius={10} size="sm"> + <InputLeftElement pointerEvents="none" children={<Search2Icon color="gray.600" />} /> + <Input type="text" borderRadius={10} placeholder="Search Name..." border="1px solid #949494" /> + </InputGroup> + </Stack> + <Box> + <TableContainer> + <Table variant="simple"> + <Thead> + <Tr> + <Th>Student Name</Th> + <Th>Email</Th> + <Th>Action</Th> + </Tr> + </Thead> + <Tbody> + <Tr> + <Td>Nomen</Td> + <Td>nomen@gmail.com</Td> + <Td> + <Button variant="ghost" colorScheme="green" leftIcon={<Icon as={CheckIcon} />} onClick={acceptAction}> + Accept + </Button> + <Button variant="ghost" colorScheme="red" leftIcon={<Icon as={CloseIcon} />} onClick={rejectAction}> + Reject + </Button> + </Td> + </Tr> + </Tbody> + </Table> + </TableContainer> + </Box> + + {/* Accept Confirmation Modal */} + <Modal isOpen={acceptModalIsOpen} onClose={onAcceptModalClose}> + <ModalOverlay /> + <ModalContent> + <ModalHeader>Confirm Acceptance</ModalHeader> + <ModalCloseButton /> + <ModalBody> + Are you sure you want to accept this applicant? + </ModalBody> + <ModalFooter> + <Button colorScheme="green" mr={3} onClick={handleAcceptConfirm}> + Confirm + </Button> + <Button variant="ghost" onClick={onAcceptModalClose}> + Cancel + </Button> + </ModalFooter> + </ModalContent> + </Modal> + + {/* Reject Confirmation Modal */} + <Modal isOpen={rejectModalIsOpen} onClose={onRejectModalClose}> + <ModalOverlay /> + <ModalContent> + <ModalHeader>Confirm Rejection</ModalHeader> + <ModalCloseButton /> + <ModalBody> + Are you sure you want to reject this applicant? + </ModalBody> + <ModalFooter> + <Button colorScheme="red" mr={3} onClick={handleRejectConfirm}> + Confirm + </Button> + <Button variant="ghost" onClick={onRejectModalClose}> + Cancel + </Button> + </ModalFooter> + </ModalContent> + </Modal> + </Box> + ); +}; + +export default Acceptance; diff --git a/src/components/Dashboard/UniversityDashboard.tsx b/src/components/Dashboard/UniversityDashboard.tsx index 177225de00cf045cf5dd7183970a5a57993db3f2..863341caba70f18daf89074d8aa653a000e0a12b 100644 --- a/src/components/Dashboard/UniversityDashboard.tsx +++ b/src/components/Dashboard/UniversityDashboard.tsx @@ -39,7 +39,7 @@ export const UniversityDashboard = () => { process.env.REACT_APP_API_URL + "/api/university/stats/" + response?.data.user_id ); - setStudentCount(students.data.data[0].applicant_count) + setStudentCount(students.data.data.data[0].applicant_count) } catch (error) { console.error("Error in useEffect:", error); } diff --git a/src/components/Login/Login.tsx b/src/components/Login/Login.tsx index 30e4f38516867583009ec578a384cd5e071ac12d..72c54432704cdd9773c52f19ea4e43d8d3ce8c8a 100644 --- a/src/components/Login/Login.tsx +++ b/src/components/Login/Login.tsx @@ -40,7 +40,7 @@ const Login = () => { const res = await handleLogin(email, password); Cookies.set("accToken", res?.accToken); if (res && res.status === "success") { - navigate("/dashboard"); + navigate("/"); } else { setErrMsg(res?.message || "Credentials not match"); } diff --git a/src/components/Report/Report.tsx b/src/components/Report/Report.tsx index 005eeceb31113987d95f1a4fdaf8e96828a13055..5d36c8939bcd3d841967517e0da196fe01eadf30 100644 --- a/src/components/Report/Report.tsx +++ b/src/components/Report/Report.tsx @@ -1,69 +1,211 @@ -import { Flex, Box, Text, Heading, TableContainer, Table, Th, Thead, Tr, Tbody, Td } from "@chakra-ui/react" -import axios from "axios"; -import { useState, useEffect } from "react"; -import { handleGetInfo } from "../../utils/auth"; +/* eslint-disable react-hooks/exhaustive-deps */ +import { + Box, + Heading, + TableContainer, + Table, + Th, + Thead, + Tr, + Tbody, + Td, + Stack, + Input, + InputGroup, + InputLeftElement, + MenuItemOption, + MenuOptionGroup, + Menu, + Button, + MenuButton, + MenuList, + HStack, + Icon +} from "@chakra-ui/react" +import axios from "axios" +import React, { useState, useEffect } from "react" +import { handleGetInfo } from "../../utils/auth" +import { + ArrowBackIcon, + ArrowForwardIcon, + ChevronDownIcon, + Search2Icon +} from "@chakra-ui/icons" +import { FaFilter } from "react-icons/fa" +import { debounce, get } from "lodash" -const Report = () => { +const Report: React.FC = () => { const [userInfo, setUserInfo] = useState({ user_id: 0, name: "", email: "", role: "" }); - - const [students, setStudents] = useState([]) - + + const [userId, setUserId] = useState(0); + const [students, setStudents] = useState([]); + const [currentPage, setCurrentPage] = useState(1); + const [itemsPerPage, setItemsPerPage] = useState(5); + const [search, setSearch] = useState(""); + const [numberOfPages, setNumberOfPages] = useState(0); + const MAX_PAGE_BUTTONS = 3; + + const fetchStudents = async () => { + const url = new URL( + process.env.REACT_APP_API_URL + "/api/university/stats/" + userId + ); + const params = new URLSearchParams(); + params.append("name", search); + params.append("itemsperpage", String(itemsPerPage)); + params.append("currentPage", String(currentPage)); + console.log(params.toString()); + url.search = params.toString(); + const response = await axios.get(url.toString()); + const students = await response.data; + setNumberOfPages(Math.ceil(students.data.total / itemsPerPage || 100)); + setStudents(students.data.data); + }; + + const debounceFetch = debounce(fetchStudents, 500); + useEffect(() => { - const getInfo = async () => { - try { - const response = await handleGetInfo(); - setUserInfo({ - user_id: response?.data.user_id, - name: response?.data.name, - email: response?.data.email, - role: response?.data.roles - }); - - const students = await axios.get( - process.env.REACT_APP_API_URL + "/api/university/stats/" + response?.data.user_id - ); - - setStudents(students.data.data) - } catch (error) { - console.error("Error in useEffect:", error); + const getInfoAndFetchStudents = async () => { + const response = await handleGetInfo(); + setUserInfo({ + user_id: response?.data.user_id, + name: response?.data.name, + email: response?.data.email, + role: response?.data.roles + }); + + if (userId === 0 && response?.data.user_id !== 0) { + setUserId(response?.data.user_id); } + + debounceFetch(); }; + + getInfoAndFetchStudents(); + }, [search, itemsPerPage, currentPage, userId]); - getInfo(); - }, []); + useEffect(() => { + setCurrentPage(1); + }, [itemsPerPage]) + const startPage = + currentPage <= Math.floor(MAX_PAGE_BUTTONS / 2) + ? 1 + : Math.max(1, currentPage - Math.floor(MAX_PAGE_BUTTONS / 2)) + + const endPage = + startPage + MAX_PAGE_BUTTONS - 1 <= numberOfPages + ? startPage + MAX_PAGE_BUTTONS - 1 + : numberOfPages + return ( <Box p="12"> - <Heading size="sm" as="h1" mb="6" fontSize={48}> - List of Students - </Heading> - <Box> - <TableContainer> - <Table variant="striped" colorScheme="facebook"> - <Thead> - <Tr> - <Th>Student Name</Th> - <Th>Email</Th> + <Heading size="sm" as="h1" mb="6" fontSize={48}> + List of Students + </Heading> + <Stack> + {/* Search Bar */} + <InputGroup borderRadius={10} size="sm"> + <InputLeftElement + pointerEvents="none" + children={<Search2Icon color="gray.600" />} + /> + <Input + type="text" + borderRadius={10} + placeholder="Search..." + border="1px solid #949494" + onChange={(event) => setSearch(event.target.value)} + /> + </InputGroup> + + <Menu closeOnSelect={false}> + <MenuButton + as={Button} + leftIcon={<FaFilter />} + rightIcon={<ChevronDownIcon />} + size={"md"} + minWidth={"150px"} + > + Filter + </MenuButton> + <MenuList minWidth="240px"> + {/* Items per Page */} + <MenuOptionGroup + title="Items per Page" + type="radio" + defaultValue="5" + onChange={(value) => setItemsPerPage(Number(value))} + > + <MenuItemOption value="5">5</MenuItemOption> + <MenuItemOption value="10">10</MenuItemOption> + <MenuItemOption value="15">15</MenuItemOption> + <MenuItemOption value="20">20</MenuItemOption> + </MenuOptionGroup> + </MenuList> + </Menu> + </Stack> + <Box> + <TableContainer> + <Table variant="striped" colorScheme="facebook"> + <Thead> + <Tr> + <Th>Student Name</Th> + <Th>Email</Th> + </Tr> + </Thead> + <Tbody> + {students.map((student: any) => ( + <Tr key={student.applicant_email}> + <Td>{student.applicant_name}</Td> + <Td>{student.applicant_email}</Td> </Tr> - </Thead> - <Tbody> - {students.map((student: any) => ( - <Tr key={student}> - <Td>{student.applicant_name}</Td> - <Td>{student.applicant_email}</Td> - </Tr> - ))} - </Tbody> - </Table> - </TableContainer> - </Box> + ))} + </Tbody> + </Table> + </TableContainer> + </Box> + {students.length > 0 && ( + <HStack spacing="24px" justifyContent="center" mt={"20px"}> + {/* Prev Button */} + <Button + onClick={() => setCurrentPage(currentPage - 1)} + isDisabled={currentPage === 1} // disable if on first page + > + <Icon as={ArrowBackIcon} /> + </Button> + + {/* Page Buttons */} + {Array.from( + { length: endPage - startPage + 1 }, + (_, index) => startPage + index + ).map((pageNumber) => ( + <Button + variant="outline" + colorScheme={currentPage === pageNumber ? "teal" : "gray"} + borderRadius="full" + w="40px" + key={pageNumber} + onClick={() => setCurrentPage(pageNumber)} + > + {pageNumber} + </Button> + ))} + + {/* Next Button */} + <Button + onClick={() => setCurrentPage(currentPage + 1)} + isDisabled={currentPage === numberOfPages} + > + <Icon as={ArrowForwardIcon} /> + </Button> + </HStack> + )} </Box> - ) }