diff --git a/src/App.tsx b/src/App.tsx index f971b6cdf9838712df12e7ddd45c2c7c5400fa66..9ec669048c6caa879190711600022a0604d06f46 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,61 +5,24 @@ import { ChakraProvider, extendTheme } from "@chakra-ui/react" import Login from "./components/Login/Login" import RegisterOrg from "./components/Register/RegisterOrg" import RegisterUni from "./components/Register/RegisterUniversity" -import Home from "./components/Home/Home" import Unauthorized from "./components/Error/Unauthorized" -import Report from "./components/Report/Report" +import Report from "./components/Report/OrganizationReport" import PageNotFound from "./components/Error/PageNotFound" import Scholarships from "./components/Scholarships/Scholarships" import Sidebar from "./components/Sidebar/Sidebar" import RequireAuth from "./utils/RequireAuth" import Layout from "./components/layout" import AssignmentDetails from "./components/Assignment/AssignmentDetails" -import useRefreshToken from "./hooks/useRefreshToken" -import { useEffect, useState } from "react" -import { OrganizationDashboard } from "./components/Dashboard/OrganizationDashboard" -import { UniversityDashboard } from "./components/Dashboard/UniversityDashboard" -import UniversityHome from "./components/Home/UniversityHome" import Acceptance from "./components/Acceptance/Acceptance" import { Submissions } from "./components/Assignment/Submission" -import { handleGetInfo } from "./utils/auth" +import Dashboard from "./components/Dashboard/Dashboard" +import Home from "./components/Home/Home" const ROLES = { Organization: "organization", University: "university" } function App() { - const refresh = useRefreshToken() - useEffect(() => { - refresh().catch((error) => { - // console.error("An error occurred while refreshing the token:", error) - }) - }, [refresh]) - - const [userInfo, setUserInfo] = useState({ - user_id: 0, - name: "", - email: "", - role: "" - }) - - const getInfo = async () => { - try { - refresh() - 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(() => {}) - useEffect(() => { - getInfo() - }, [userInfo.role]) const activeLabelStyles = { transform: "scale(0.85) translateY(-24px)" } @@ -83,7 +46,6 @@ function App() { left: 0, zIndex: 2, position: "absolute", - // backgroundColor: "white", pointerEvents: "none", mx: 3, px: 1, @@ -119,11 +81,7 @@ function App() { path="/" element={ <Sidebar> - {userInfo.role === "organization" ? ( - <Home /> - ) : ( - <UniversityHome /> - )} + <Home /> </Sidebar> } /> @@ -140,11 +98,7 @@ function App() { path="dashboard" element={ <Sidebar> - {userInfo.role === "organization" ? ( - <OrganizationDashboard /> - ) : ( - <UniversityDashboard /> - )} + <Dashboard /> </Sidebar> } /> diff --git a/src/components/Dashboard/Dashboard.tsx b/src/components/Dashboard/Dashboard.tsx new file mode 100644 index 0000000000000000000000000000000000000000..33e4d19810a5640b963250a2b2659cf6c33c0fb6 --- /dev/null +++ b/src/components/Dashboard/Dashboard.tsx @@ -0,0 +1,41 @@ +import { useEffect, useState } from "react" +import { handleGetInfo } from "../../utils/auth" +import { UniversityDashboard } from "./UniversityDashboard" +import { OrganizationDashboard } from "./OrganizationDashboard" + + +const Dashboard = () => { + const [userInfo, setUserInfo] = useState({ + user_id: 0, + name: "", + email: "", + role: "" + }) + 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 + }) + } + + useEffect(() => { + getInfo() + }, []) + + return ( + <> + {userInfo.role === "university" ? ( + <UniversityDashboard /> + ) : userInfo.role === "organization" ? ( + <OrganizationDashboard /> + ) : ( + <></> + )} + </> + ) +} + +export default Dashboard \ No newline at end of file diff --git a/src/components/Home/Home.tsx b/src/components/Home/Home.tsx index 3dc61df641c1b46a95cc91d92701b571ad90b8d2..5f3c5cfd5370d4ebe5d62a9604d0e7678eb4ca1f 100644 --- a/src/components/Home/Home.tsx +++ b/src/components/Home/Home.tsx @@ -1,87 +1,39 @@ -import { - Flex, - Box, - SimpleGrid, - Heading, - Text, - useColorModeValue, - useColorMode -} from "@chakra-ui/react" -import { ArrowForwardIcon } from "@chakra-ui/icons" -import { Link } from "react-router-dom" +import { useEffect, useState } from "react" +import { handleGetInfo } from "../../utils/auth" +import UniversityHome from "./UniversityHome" +import OrganizationHome from "./OrganizationHome" const Home = () => { - const { colorMode, toggleColorMode } = useColorMode() - const boxColor = useColorModeValue("green.200", "green.800") + const [userInfo, setUserInfo] = useState({ + user_id: 0, + name: "", + email: "", + role: "" + }) + 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 + }) + } - return ( - <Flex direction="column" alignItems="center" justifyContent="center"> - <Heading as="h1" size="2xl"> - Home - </Heading> - - <Text - as="h5" - fontSize="lg" - fontWeight="normal" - fontStyle="italic" - textAlign="center" - mt={4} - > - What do you want to do? - </Text> + useEffect(() => { + getInfo() + }, []) - <SimpleGrid columns={2} spacing={10} mt={8}> - <Box - bg={boxColor} - height="80px" - borderRadius="md" - p={4} - display="flex" - alignItems="center" - justifyContent="center" - > - <Link to={`/scholarships`}> - <ArrowForwardIcon w={6} h={6} mr={2} /> View Scholarships - </Link> - </Box> - <Box - bg={boxColor} - height="80px" - borderRadius="md" - p={4} - display="flex" - alignItems="center" - justifyContent="center" - > - <ArrowForwardIcon w={6} h={6} mr={2} /> View Applications - </Box> - <Box - bg={boxColor} - height="80px" - borderRadius="md" - p={4} - display="flex" - alignItems="center" - justifyContent="center" - > - <ArrowForwardIcon w={6} h={6} mr={2} /> View Assignments - </Box> - <Box - bg={boxColor} - height="80px" - borderRadius="md" - p={4} - display="flex" - alignItems="center" - justifyContent="center" - > - <Link to={`/dashboard`}> - <ArrowForwardIcon w={6} h={6} mr={2} /> View Dashboard - </Link> - </Box> - </SimpleGrid> - </Flex> + return ( + <> + {userInfo.role === "university" ? ( + <UniversityHome /> + ) : userInfo.role === "organization" ? ( + <OrganizationHome /> + ) : ( + <></> + )} + </> ) } diff --git a/src/components/Home/OrganizationHome.tsx b/src/components/Home/OrganizationHome.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d823fc91bd977c84e98ec261df7c12df713e583c --- /dev/null +++ b/src/components/Home/OrganizationHome.tsx @@ -0,0 +1,88 @@ +import { + Flex, + Box, + SimpleGrid, + Heading, + Text, + useColorModeValue, + useColorMode +} from "@chakra-ui/react" +import { ArrowForwardIcon } from "@chakra-ui/icons" +import { Link } from "react-router-dom" + +const OrganizationHome = () => { + const { colorMode, toggleColorMode } = useColorMode() + const boxColor = useColorModeValue("green.200", "green.800") + + return ( + <Flex direction="column" alignItems="center" justifyContent="center"> + <Heading as="h1" size="2xl"> + Home + </Heading> + + <Text + as="h5" + fontSize="lg" + fontWeight="normal" + fontStyle="italic" + textAlign="center" + mt={4} + > + What do you want to do? + </Text> + + <SimpleGrid columns={2} spacing={10} mt={8}> + <Box + bg={boxColor} + height="80px" + borderRadius="md" + p={4} + display="flex" + alignItems="center" + justifyContent="center" + > + <Link to={`/scholarships`}> + <ArrowForwardIcon w={6} h={6} mr={2} /> View Scholarships + </Link> + </Box> + <Box + bg={boxColor} + height="80px" + borderRadius="md" + p={4} + display="flex" + alignItems="center" + justifyContent="center" + > + <ArrowForwardIcon w={6} h={6} mr={2} /> View Applications + </Box> + <Box + bg={boxColor} + height="80px" + borderRadius="md" + p={4} + display="flex" + alignItems="center" + justifyContent="center" + > + <ArrowForwardIcon w={6} h={6} mr={2} /> View Assignments + </Box> + <Box + bg={boxColor} + height="80px" + borderRadius="md" + p={4} + display="flex" + alignItems="center" + justifyContent="center" + > + <Link to={`/dashboard`}> + <ArrowForwardIcon w={6} h={6} mr={2} /> View Dashboard + </Link> + </Box> + </SimpleGrid> + </Flex> + ) +} + +export default OrganizationHome diff --git a/src/components/Report/OrganizationReport.tsx b/src/components/Report/OrganizationReport.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b4dd0bdf4af027612b902352e4e25ca30a5982cf --- /dev/null +++ b/src/components/Report/OrganizationReport.tsx @@ -0,0 +1,26 @@ +import { Flex, Heading, Text } from "@chakra-ui/react" + +const OrganizationReport = () => { + return ( + <> + <Flex + direction="column" + justifyContent="center" + alignItems="center" + mt="2rem" + > + <Heading as="h1" size="2xl" mb="1rem"> + Report + </Heading> + <Text fontSize="xl" mb="1rem"> + Organization Report + </Text> + <Text fontSize="lg" mb="1rem"> + This is the Organization Report page. + </Text> + </Flex> + </> + ) +} + +export default OrganizationReport diff --git a/src/components/Report/Report.tsx b/src/components/Report/Report.tsx index 93edbd60c92b819fa136bc27a367e3b6f7f49aea..01a2ad8c84828858876e4392ed0eaed0387f11b9 100644 --- a/src/components/Report/Report.tsx +++ b/src/components/Report/Report.tsx @@ -1,211 +1,39 @@ -/* 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 React, { useState, useEffect } from "react" -import { - ArrowBackIcon, - ArrowForwardIcon, - ChevronDownIcon, - Search2Icon -} from "@chakra-ui/icons" -import { FaFilter } from "react-icons/fa" -import { debounce } from "lodash" -import useAxiosPrivate from "../../hooks/axiosPrivate" +import { useEffect, useState } from "react" import { handleGetInfo } from "../../utils/auth" +import OrganizationReport from "./OrganizationReport" +import UniversityReport from "./UniversityReport" -const Report: React.FC = () => { - const [UserInfo, setUserInfo] = useState({ +const Report = () => { + const [userInfo, setUserInfo] = useState({ user_id: 0, name: "", email: "", role: "" }) - const axiosInstance = useAxiosPrivate() - - 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)) - url.search = params.toString() - const response = await axiosInstance.get(url.toString()) - const students = await response.data - setNumberOfPages(Math.ceil(students.data.total / itemsPerPage || 100)) - setStudents(students.data.data) + 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 + }) } - const debounceFetch = debounce(fetchStudents, 500) - useEffect(() => { - 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]) - - 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 + getInfo() + }, []) return ( - <Box p="12"> - <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> - ))} - </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> + <> + {userInfo.role === "university" ? ( + <UniversityReport /> + ) : userInfo.role === "organization" ? ( + <OrganizationReport /> + ) : ( + <></> )} - </Box> + </> ) } diff --git a/src/components/Report/UniversityReport.tsx b/src/components/Report/UniversityReport.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7f2971f918a583e382515fe113386c97eed44191 --- /dev/null +++ b/src/components/Report/UniversityReport.tsx @@ -0,0 +1,212 @@ +/* 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 React, { useState, useEffect } from "react" +import { + ArrowBackIcon, + ArrowForwardIcon, + ChevronDownIcon, + Search2Icon +} from "@chakra-ui/icons" +import { FaFilter } from "react-icons/fa" +import { debounce } from "lodash" +import useAxiosPrivate from "../../hooks/axiosPrivate" +import { handleGetInfo } from "../../utils/auth" + +const UniversityReport: React.FC = () => { + const [UserInfo, setUserInfo] = useState({ + user_id: 0, + name: "", + email: "", + role: "" + }) + const axiosInstance = useAxiosPrivate() + + 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)) + url.search = params.toString() + const response = await axiosInstance.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 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]) + + 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> + <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> + ))} + </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> + ) +} + +export default UniversityReport