diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 50a8fb25bc073bf01efadc6b48240fad40cc125e..7d589577957e5717d2453f965f2b84a933fcc0be 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -32,7 +32,7 @@ const Home = () => { const newAxiosInstance = axios.create(axiosConfig()); const location = useLocation(); const queryParams = new URLSearchParams(location.search); - const page = parseInt(queryParams.get('page') || '1', 10); + const page = parseInt(queryParams.get("page") || "1", 10); const navigate = useNavigate(); const n = 4; @@ -40,20 +40,22 @@ const Home = () => { const getCourses = async (pageNumber: number) => { try { setIsLoading(true); - const res = await newAxiosInstance.get(`${config.REST_API_URL}/course/teacher?page=${pageNumber}`); - const {status} = res["data"]; - if(status === 401){ + const res = await newAxiosInstance.get( + `${config.REST_API_URL}/course/teacher?page=${pageNumber}` + ); + const { status } = res["data"]; + if (status === 401) { toast({ - title : "Unathorized user", - description : "You have to log in", - status : "error", - duration:3000, - isClosable : true, - position : "top" - }) + title: "Unathorized user", + description: "You have to log in", + status: "error", + duration: 3000, + isClosable: true, + position: "top", + }); navigate("/login"); } - setTotalPages(Math.ceil(res.data.total/n)); + setTotalPages(Math.ceil(res.data.total / n)); const coursesData: Courses[] = res.data.data.map((course: any) => { const releaseDate = new Date(course.release_date); @@ -69,16 +71,16 @@ const Home = () => { setCourses(coursesData); setIsLoading(false); } catch (error) { - console.error('Axios Error:', error); + console.error("Axios Error:", error); setIsLoading(false); } - } + }; getCourses(page); }, [page]); const gradientStyles = { - background: 'linear-gradient(to right, #fcbcd7, #ffcee6)', + background: "linear-gradient(to right, #fcbcd7, #ffcee6)", }; return ( @@ -93,9 +95,7 @@ const Home = () => { justifyContent={"space-between"} mt="10" > - <Heading size="lg"> - Welcome to LeMeS! - </Heading> + <Heading size="lg">Welcome to LeMeS!</Heading> {courses.length > 0 ? ( <SimpleGrid columns={{ base: 1, md: 2, lg: 4 }} @@ -108,7 +108,11 @@ const Home = () => { <CardBody style={gradientStyles}> <Image w="50" - src={item.image_path ? item.image_path.toString() : 'premiumlogo.png'} + src={ + item.image_path + ? item.image_path.toString() + : "premiumlogo.png" + } alt="Course Image" borderRadius="lg" border="1px" @@ -136,9 +140,12 @@ const Home = () => { </Card> ))} </SimpleGrid> - ) : (!isLoading && ( - <Text fontSize={"20"} mt="200px" mb="200px">Sorry, no courses available at the moment...</Text> - ) + ) : ( + !isLoading && ( + <Text fontSize={"20"} mt="200px" mb="200px"> + Sorry, no courses available at the moment... + </Text> + ) )} <Box mt={10} alignContent="center"> <div style={{ display: "flex", justifyContent: "center" }}> @@ -147,7 +154,7 @@ const Home = () => { pageClassName={"page-item"} activeClassName={"active-page"} onPageChange={(selectedItem) => { - if ('selected' in selectedItem) { + if ("selected" in selectedItem) { const nextPage = selectedItem.selected + 1; navigate(`/course?page=${nextPage}`); } diff --git a/src/pages/admin/Request.tsx b/src/pages/admin/Request.tsx index b480be72171577df0c111800bd348a17488cba11..79f9a398a966bb0e9cddaacc0d4fdf15b8f7e9da 100644 --- a/src/pages/admin/Request.tsx +++ b/src/pages/admin/Request.tsx @@ -1,34 +1,41 @@ import React, { useEffect, useState } from "react"; import { - Box, - Heading, - Container, - Flex, - TableContainer, - Icon, - Button, - ButtonGroup, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - ModalOverlay, - Text, - useToast, - Table, - Thead, - Tbody, - Tr, - Th, - Td, + Box, + Heading, + Container, + Flex, + TableContainer, + Icon, + Button, + ButtonGroup, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Text, + useToast, + Table, + Thead, + Tbody, + Tr, + Th, + Td, } from "@chakra-ui/react"; -import { BiSolidTrash, BiCheckCircle, BiError, BiUpvote, BiChevronLeftCircle, BiChevronRightCircle } from "react-icons/bi"; -import { useNavigate } from "react-router-dom"; +import { + BiSolidTrash, + BiCheckCircle, + BiError, + BiUpvote, + BiChevronLeftCircle, + BiChevronRightCircle, +} from "react-icons/bi"; +import { useNavigate } from "react-router-dom"; import "primereact/resources/themes/lara-light-indigo/theme.css"; import "primereact/resources/primereact.min.css"; -import { Users } from "../../types" +import { RequestType, Users } from "../../types"; import Loading from "../../components/loading/Loading"; import { axiosConfig } from "../../utils/axios"; import config from "../../config/config"; @@ -37,330 +44,388 @@ import ReactPaginate from "react-paginate"; import { IconContext } from "react-icons"; const Request = () => { - const initialUsers: Users[] = []; - const [users, setUsers] = useState(initialUsers); - const [refresher, setRefresher] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [totalPages, setTotalPages] = useState(1); - const [page, setPage] = useState(1); - const newAxiosInstance = axios.create(axiosConfig()); - const toast = useToast(); - const navigate = useNavigate(); - const n = 6; - + const initialUsers: Users[] = []; + const [requests, setRequests] = useState<RequestType[]>([]); + const [refresher, setRefresher] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [isError, setIsError] = useState(false); + const [totalPages, setTotalPages] = useState(1); + const [page, setPage] = useState(1); + const newAxiosInstance = axios.create(axiosConfig()); + const toast = useToast(); + const navigate = useNavigate(); + const axiosInstance = axios.create(axiosConfig()); + const n = 6; - // FETCHING THE USER DATA (INI MSIH MAKE DATA LIST SEMUA USER, TINGGAL GANTI KE USER YG LGI REQUEST) - useEffect(() => { - const getCourses = async (pageNumber: number) => { - try { - setIsLoading(true); - const res = await newAxiosInstance.get(`${config.REST_API_URL}/user?page=${pageNumber}`); - const { status } = res["data"]; - // if (status === 401) { - // toast({ - // title: "Unauthorized user", - // description: "You have to log in", - // status: "error", - // duration: 3000, - // isClosable: true, - // position: "top" - // }) - // navigate("/login"); - // } - setTotalPages(Math.ceil(res.data.total / n)); - - const usersData: Users[] = res.data.data.map((user: any) => { - return { - id: user.id, - username: user.username, - fullname: user.fullname, - isAdmin: user.isAdmin, - }; - }); - setUsers(usersData); - setIsLoading(false); - } catch (error) { - console.error('Axios Error:', error); - setIsLoading(false); - } + // FETCHING THE USER DATA (INI MSIH MAKE DATA LIST SEMUA USER, TINGGAL GANTI KE USER YG LGI REQUEST) + useEffect(() => { + const getTotalPage = async () => { + const res = await newAxiosInstance.get( + `${config.REST_API_URL}/premium/total` + ); + const { status } = res; + if (status === 200) { + const result = res["data"]; + const { status, data } = result; + if (status === 200) { + return data; } - - getCourses(page); - }, [page, refresher]); - - // HANDLING REJECT REQUEST - const [isModalRejectingOpen, setIsModalRejectingOpen] = useState(false); - const [rejectingID, setRejectingID] = useState(0); - const [rejectingUsername, setRejectingUsername] = useState(""); - const openModalRejecting = (id: number, username: string) => { - setIsModalRejectingOpen(true); - setRejectingID(id); - setRejectingUsername(username); - }; - const closeModalRejecting = () => { - setIsModalRejectingOpen(false); - }; - const handleRejecting = () => { - // Handle the Rejecting action here, e.g., send an API request to update the data - // After Rejecting is complete, close the modal. - closeModalRejecting(); - setRefresher((prevRefresh) => !prevRefresh) // lgsung request data baru tanpa hrus reload page (harusnya works) - }; - - // HANDLING ACC REQUEST - const [isModalAcceptingOpen, setIsModalAcceptingOpen] = useState(false); - const [acceptingID, setAcceptingID] = useState(0); - const [acceptingUsername, setAcceptingUsername] = useState(""); - const openModalAccepting = (id: number, username: string) => { - setIsModalAcceptingOpen(true); - setAcceptingID(id); - setAcceptingUsername(username); + } + return 0; }; - const closeModalAccepting = () => { - setIsModalAcceptingOpen(false); - }; - const handleAccepting = () => { - // Handle the Accepting action here, e.g., send an API request to update the data - // After Accepting is complete, close the modal. - closeModalAccepting(); - setRefresher((prevRefresh) => !prevRefresh) // lgsung request data baru tanpa hrus reload page (harusnya works) + const getCourses = async (pageNumber: number) => { + const totalRows = await getTotalPage(); + try { + setIsLoading(true); + const res = await newAxiosInstance.get( + `${config.REST_API_URL}/premium?page=${pageNumber}` + ); + const { status, data } = res["data"]; + if (status === 200) { + setTotalPages(Math.ceil(totalRows / n)); + setRequests(res.data.data); + setIsLoading(false); + }else{ + setIsError(true) + } + } catch (error) { + setIsError(true); + console.error("Axios Error:", error); + setIsLoading(false); + } }; + getCourses(page); + }, [page, refresher]); + // HANDLING REJECT REQUEST + const [isModalRejectingOpen, setIsModalRejectingOpen] = useState(false); + const [rejectingID, setRejectingID] = useState(0); + const openModalRejecting = (user_id: number) => { + setIsModalRejectingOpen(true); + setRejectingID(user_id); + }; + const closeModalRejecting = () => { + setIsModalRejectingOpen(false); + }; + const handleRejecting = () => { + // Handle the Rejecting action here, e.g., send an API request to update the data + // After Rejecting is complete, close the modal. + try { + axiosInstance + .delete(`${config.REST_API_URL}/premium/${rejectingID}`) + .then((res) => { + const response = res["data"]; + const { status, data } = response; + if (status === 200) { + toast({ + title: "Successfully delete a request!", + description: "Request has been deleted!", + status: "success", + isClosable: true, + duration: 3000, + position: "top", + }); + } else { + toast({ + title: "Delete failed!", + description: "Request has not been deleted!", + status: "error", + isClosable: true, + duration: 3000, + position: "top", + }); + } + }); + } catch (error) { + console.log(error); + } + closeModalRejecting(); + setRefresher((prevRefresh) => !prevRefresh); // lgsung request data baru tanpa hrus reload page (harusnya works) + }; + + // HANDLING ACC REQUEST + const [isModalAcceptingOpen, setIsModalAcceptingOpen] = useState(false); + const [acceptingID, setAcceptingID] = useState(0); + const openModalAccepting = (user_id: number) => { + setIsModalAcceptingOpen(true); + setAcceptingID(user_id); + }; + const closeModalAccepting = () => { + setIsModalAcceptingOpen(false); + }; + const handleAccepting = () => { + try { + axiosInstance + .put(`${config.REST_API_URL}/premium/${acceptingID}`, { + newStatus: "ACCEPTED", + }) + .then((res) => { + const response = res["data"]; + const { status, data } = response; + if (status === 200) { + toast({ + title: "Successfully upgrade user!", + description: "User has been upgraded to premium!", + status: "success", + isClosable: true, + duration: 3000, + position: "top", + }); + } else { + toast({ + title: "Upgrade failed!", + description: "User has not been upgraded to premium", + status: "error", + isClosable: true, + duration: 3000, + position: "top", + }); + } + }); + } catch (error) { + console.log(error); + } + // After Accepting is complete, close the modal. + closeModalAccepting(); + setRefresher((prevRefresh) => !prevRefresh); // lgsung request data baru tanpa hrus reload page (harusnya works) + }; + if(isError){ return ( - <Container overflow="auto" maxW={"100vw"} maxH={"100vh"}> - {/* Render the RejectingModal component conditionally */} - <Loading loading={isLoading} /> - <RejectingModal - isOpen={isModalRejectingOpen} - onClose={closeModalRejecting} - user_id={rejectingID} - username={rejectingUsername} - handleRejecting={handleRejecting} - /> + <Container> + Error... + </Container> + ) + } + + return ( + <Container overflow="auto" maxW={"100vw"} maxH={"100vh"}> + {/* Render the RejectingModal component conditionally */} + <Loading loading={isLoading} /> + <RejectingModal + isOpen={isModalRejectingOpen} + onClose={closeModalRejecting} + user_id={rejectingID} + handleRejecting={handleRejecting} + /> - {/* Render the AcceptingModal component conditionally */} - <AcceptingModal - isOpen={isModalAcceptingOpen} - onClose={closeModalAccepting} - user_id={acceptingID} - username={acceptingUsername} - handleAccepting={handleAccepting} - /> - <Flex alignItems="center" justifyContent="center" mb="20px" mt="50px"> - <Flex - direction="column" - background="transparent" - borderRadius="15px" - p="30px" - // mx={{ base: "100px" }} - bg={"#f2f2f2"} - boxShadow="0 20px 27px 0 rgb(0 0 0 / 15%)" - > - <Box - display="flex" - justifyContent="space-between" - alignItems="center" - mb="5" - > - <Heading as="h1">User Upgrade Request</Heading> - </Box> - <TableContainer width="80vw" border="1px" borderColor={"#564c95"} borderRadius="10"> - <Table colorScheme='purple' fontSize={"16"}> - <Thead bg="#564c95" textColor="white" fontWeight={"bold"}> - <Tr> - <Th textAlign={"center"} color="white" w="10%">UserID</Th> - <Th textAlign={"center"} color="white" w="25%">Username</Th> - <Th textAlign={"center"} color="white" w="35%">Fullname</Th> - <Th textAlign={"center"} color="white" w="10%">isAdmin</Th> - <Th textAlign={"center"} color="white" w="20%">Action</Th> - </Tr> - </Thead> - {users - .sort((a, b) => a.id - b.id) - .map((item, index) => ( - <Tbody> - <Tr key={item.id} bg={index % 2 === 0 ? 'white' : 'gray.100'}> - <Td textAlign={"center"}>{item.id}</Td> - <Td textAlign={"center"}>{item.username}</Td> - <Td textAlign={"center"}>{item.fullname}</Td> - {item.isAdmin ? ( - <Td textAlign={"center"} fontWeight={"bold"}> - Yes - </Td> - ) : ( - <Td textAlign={"center"} fontWeight={"bold"}> - No - </Td> - )} - <Td textAlign={"center"}> - <Icon - mr="1" - as={BiCheckCircle} - fontSize={"24"} - color={"#564c95"} - _hover={{ color: "green" }} - cursor={"pointer"} - onClick={() => - openModalAccepting(item.id, item.username) - } - ></Icon> + {/* Render the AcceptingModal component conditionally */} + <AcceptingModal + isOpen={isModalAcceptingOpen} + onClose={closeModalAccepting} + user_id={acceptingID} + handleAccepting={handleAccepting} + /> + <Flex alignItems="center" justifyContent="center" mb="20px" mt="50px"> + <Flex + direction="column" + background="transparent" + borderRadius="15px" + p="30px" + // mx={{ base: "100px" }} + bg={"#f2f2f2"} + boxShadow="0 20px 27px 0 rgb(0 0 0 / 15%)" + > + <Box + display="flex" + justifyContent="space-between" + alignItems="center" + mb="5" + > + <Heading as="h1">User Upgrade Request</Heading> + </Box> + <TableContainer + width="80vw" + border="1px" + borderColor={"#564c95"} + borderRadius="10" + > + <Table colorScheme="purple" fontSize={"16"}> + <Thead bg="#564c95" textColor="white" fontWeight={"bold"}> + <Tr> + <Th textAlign={"center"} color="white" w="25%"> + User ID + </Th> + <Th textAlign={"center"} color="white" w="35%"> + Status + </Th> - <Icon - ml="1" - as={BiSolidTrash} - fontSize={"24"} - color={"#564c95"} - _hover={{ color: "red" }} - cursor={"pointer"} - onClick={() => - openModalRejecting(item.id, item.username) - } - ></Icon> - </Td> - </Tr> - </Tbody> - ))} - </Table> - </TableContainer> - <Box mt={10} alignContent="center"> - <div style={{ display: "flex", justifyContent: "center" }}> - <ReactPaginate - containerClassName={"pagination"} - pageClassName={"page-item"} - activeClassName={"active-page"} - onPageChange={(selectedItem) => { - if ('selected' in selectedItem) { - const nextPage = selectedItem.selected + 1; - setPage(nextPage); - } - }} - pageCount={totalPages} - initialPage={page - 1} - breakLabel="..." - previousLabel={ - <IconContext.Provider value={{ size: "36px" }}> - <BiChevronLeftCircle color="gray" /> - </IconContext.Provider> - } - nextLabel={ - <IconContext.Provider value={{ size: "36px" }}> - <BiChevronRightCircle color="gray" /> - </IconContext.Provider> - } - /> - </div> - </Box> - </Flex> - </Flex> - </Container> - ); + <Th textAlign={"center"} color="white" w="20%"> + Action + </Th> + </Tr> + </Thead> + {requests && + requests + .sort((a, b) => a.id - b.id) + .map((item, index) => ( + <Tbody> + <Tr + key={item.id} + bg={index % 2 === 0 ? "white" : "gray.100"} + > + <Td textAlign={"center"}>{item.user_id}</Td> + <Td textAlign={"center"}>{item.status}</Td> + <Td textAlign={"center"}> + <Icon + mr="1" + as={BiCheckCircle} + fontSize={"24"} + color={"#564c95"} + _hover={{ color: "green" }} + cursor={"pointer"} + onClick={() => openModalAccepting(item.user_id)} + ></Icon> + + <Icon + ml="1" + as={BiSolidTrash} + fontSize={"24"} + color={"#564c95"} + _hover={{ color: "red" }} + cursor={"pointer"} + onClick={() => openModalRejecting(item.user_id)} + ></Icon> + </Td> + </Tr> + </Tbody> + ))} + </Table> + </TableContainer> + <Box mt={10} alignContent="center"> + <div style={{ display: "flex", justifyContent: "center" }}> + <ReactPaginate + containerClassName={"pagination"} + pageClassName={"page-item"} + activeClassName={"active-page"} + onPageChange={(selectedItem) => { + if ("selected" in selectedItem) { + const nextPage = selectedItem.selected + 1; + setPage(nextPage); + } + }} + pageCount={totalPages} + initialPage={page - 1} + breakLabel="..." + previousLabel={ + <IconContext.Provider value={{ size: "36px" }}> + <BiChevronLeftCircle color="gray" /> + </IconContext.Provider> + } + nextLabel={ + <IconContext.Provider value={{ size: "36px" }}> + <BiChevronRightCircle color="gray" /> + </IconContext.Provider> + } + /> + </div> + </Box> + </Flex> + </Flex> + </Container> + ); }; { - /* Modal Rejecting */ + /* Modal Rejecting */ } interface RejectingModalProps { - isOpen: boolean; - onClose: () => void; - user_id: number; - username: string; - handleRejecting: () => void; + isOpen: boolean; + onClose: () => void; + user_id: number; + handleRejecting: () => void; } function RejectingModal({ - isOpen, - onClose, - user_id, - username, - handleRejecting, + isOpen, + onClose, + user_id, + handleRejecting, }: RejectingModalProps) { - return ( - <Modal isOpen={isOpen} onClose={onClose}> - <ModalOverlay /> - <ModalContent> - <ModalHeader textAlign={"center"}>Reject Request</ModalHeader> - <ModalCloseButton /> - <ModalBody textAlign={"center"}> - <Box - display="flex" - flexDirection="column" - alignItems="center" - justifyContent="center" - > - <Text as={BiError} fontSize={"150px"} color="red" /> - <Text>Decline request from {username}?</Text> - </Box> - </ModalBody> + return ( + <Modal isOpen={isOpen} onClose={onClose}> + <ModalOverlay /> + <ModalContent> + <ModalHeader textAlign={"center"}>Reject Request</ModalHeader> + <ModalCloseButton /> + <ModalBody textAlign={"center"}> + <Box + display="flex" + flexDirection="column" + alignItems="center" + justifyContent="center" + > + <Text as={BiError} fontSize={"150px"} color="red" /> + <Text>Decline request from {user_id}?</Text> + </Box> + </ModalBody> - <ModalFooter justifyContent={"center"}> - <ButtonGroup> - <Button colorScheme="gray" flex="1" onClick={onClose}> - Cancel - </Button> - <Button colorScheme="red" flex="1" ml={3} onClick={handleRejecting}> - Decline - </Button> - </ButtonGroup> - </ModalFooter> - </ModalContent> - </Modal> - ); + <ModalFooter justifyContent={"center"}> + <ButtonGroup> + <Button colorScheme="gray" flex="1" onClick={onClose}> + Cancel + </Button> + <Button colorScheme="red" flex="1" ml={3} onClick={handleRejecting}> + Decline + </Button> + </ButtonGroup> + </ModalFooter> + </ModalContent> + </Modal> + ); } { - /* Modal Accepting */ + /* Modal Accepting */ } interface AcceptingModalProps { - isOpen: boolean; - onClose: () => void; - user_id: number; - username: string; - handleAccepting: () => void; + isOpen: boolean; + onClose: () => void; + user_id: number; + handleAccepting: () => void; } function AcceptingModal({ - isOpen, - onClose, - user_id, - username, - handleAccepting, + isOpen, + onClose, + user_id, + handleAccepting, }: AcceptingModalProps) { - return ( - <Modal isOpen={isOpen} onClose={onClose}> - <ModalOverlay /> - <ModalContent> - <ModalHeader textAlign={"center"}>Accept Request</ModalHeader> - <ModalCloseButton /> - <ModalBody textAlign={"center"}> - <Box - display="flex" - flexDirection="column" - alignItems="center" - justifyContent="center" - > - <Text as={BiUpvote} fontSize={"150px"} color="green" /> - <Text>Upgrade {username} to be user premium?</Text> - </Box> - </ModalBody> + return ( + <Modal isOpen={isOpen} onClose={onClose}> + <ModalOverlay /> + <ModalContent> + <ModalHeader textAlign={"center"}>Accept Request</ModalHeader> + <ModalCloseButton /> + <ModalBody textAlign={"center"}> + <Box + display="flex" + flexDirection="column" + alignItems="center" + justifyContent="center" + > + <Text as={BiUpvote} fontSize={"150px"} color="green" /> + <Text>Upgrade {user_id} to be user premium?</Text> + </Box> + </ModalBody> - <ModalFooter justifyContent={"center"}> - <ButtonGroup> - <Button colorScheme="gray" flex="1" onClick={onClose}> - Cancel - </Button> - <Button - colorScheme="purple" - flex="1" - ml={3} - onClick={handleAccepting} - > - Accept - </Button> - </ButtonGroup> - </ModalFooter> - </ModalContent> - </Modal> - ); + <ModalFooter justifyContent={"center"}> + <ButtonGroup> + <Button colorScheme="gray" flex="1" onClick={onClose}> + Cancel + </Button> + <Button + colorScheme="purple" + flex="1" + ml={3} + onClick={handleAccepting} + > + Accept + </Button> + </ButtonGroup> + </ModalFooter> + </ModalContent> + </Modal> + ); } export default Request; diff --git a/src/types.tsx b/src/types.tsx index 352e93d0528218851d7d14b88aaccfb7edf2992d..fefd4eca8ac3ce22d8d78ba79c8a28d4718746b5 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -32,3 +32,10 @@ export type Users = { fullname: string; isAdmin: string; }; + +export type RequestType={ + id : number; + user_id : number; + fullname : string; + status: string; +} \ No newline at end of file