diff --git a/src/App.tsx b/src/App.tsx index a53b35ea233b9c443a9635bd31e9ba94cc2fb9d3..d974ca3a78f0901fc10127346f9698fc3d84ecad 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,7 +4,7 @@ import { Routes, Route, useNavigate, Navigate } from "react-router-dom"; import Home from "./pages/Home"; import Login from "./pages/Login"; import Register from "./pages/Register"; -import { CoursesList } from "./pages/admin/PremiumCourses"; +import Courses from "./pages/admin/PremiumCourses"; import Users from "./pages/admin/PremiumUsers"; import Request from "./pages/admin/Request"; import AdminRegister from "./pages/admin/AdminRegister"; @@ -33,7 +33,7 @@ function App() { <Route path="courses" element={ - <AdminLayout redirect="/not-found" children={<CoursesList />} /> + <AdminLayout redirect="/not-found" children={<Courses />} /> } /> <Route diff --git a/src/pages/admin/PremiumCourses.tsx b/src/pages/admin/PremiumCourses.tsx index 7bdf9e0c04039912000022b4e426c7bc268d4eb2..d79de874286591228f2bf9833e9b13c1c4730398 100644 --- a/src/pages/admin/PremiumCourses.tsx +++ b/src/pages/admin/PremiumCourses.tsx @@ -1,240 +1,183 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { - Box, - Heading, - Button, - ButtonGroup, - Container, - Flex, + ModalCloseButton, TableContainer, - Icon, - Modal, ModalOverlay, ModalContent, + FormControl, ModalHeader, ModalFooter, + ButtonGroup, + FormLabel, + Container, ModalBody, - ModalCloseButton, - Text, - Textarea, + useToast, + Heading, + Button, + Table, + Modal, + Thead, + Tbody, Input, - FormControl, - FormLabel, + Text, + Flex, + Icon, + Box, + Tr, + Th, + Td, } from "@chakra-ui/react"; -import { BiSolidTrash, BiSolidEdit, BiError } from "react-icons/bi"; -import { Link, useNavigate } from "react-router-dom"; -import "primereact/resources/themes/lara-light-indigo/theme.css"; import "primereact/resources/primereact.min.css"; -import { DataTable } from "primereact/datatable"; -import { Column } from "primereact/column"; -import { useDisclosure } from "@chakra-ui/react"; - -export const CoursesList = () => { - type courses = { - course_id: number; - title: string; - description: string; - course_password: number; - release_date: string; +import "primereact/resources/themes/lara-light-indigo/theme.css"; +import Loading from "../../components/loading/Loading"; +import ReactPaginate from "react-paginate"; +import config from "../../config/config"; +import axios from "axios"; +import { axiosConfig } from "../../utils/axios"; +import { IconContext } from "react-icons"; +import { Courses } from "../../types" +import { BiSolidTrash, BiError, BiChevronLeftCircle, BiChevronRightCircle, BiSolidEdit } from "react-icons/bi"; + +const CoursesList = () => { + const initialCourses: Courses[] = []; + const [courses, setCourses] = useState(initialCourses); + 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 n = 6; + + // FETCH DATA COURSES + useEffect(() => { + const getCourses = async (pageNumber: number) => { + try { + setIsLoading(true); + const res = await newAxiosInstance.get(`${config.REST_API_URL}/course?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 coursesData: Courses[] = res.data.data.map((course: any) => { + return { + id: course.id, + title: course.title, + description: course.description, + release_date: course.release_date, + image_path: course.image_path, + teacher_id: course.teacher_id, + }; + }); + setCourses(coursesData); + setIsLoading(false); + } catch (error) { + console.error('Axios Error:', error); + setIsLoading(false); + } + } + + getCourses(page); + }, [page, refresher]); + + // HANDLING ADD COURSE + const [isModalAddOpen, setIsModalAddOpen] = useState(false); + const openModalAdd = () => { + setIsModalAddOpen(true); + }; + const closeModalAdd = () => { + setIsModalAddOpen(false); }; + // HANDLING EDIT COURSE const [isModalEditOpen, setIsModalEditOpen] = useState(false); - const [editedTitle, setEditedTitle] = useState(""); - const [editedDescription, setEditedDescription] = useState(""); - const openModalEdit = (id: number, title: string, description: string) => { - setIsModalEditOpen(true); + const [editDescription, setEditedDescription] = useState(""); + const [editTeacher, setEditedTeacher] = useState(0); + const [editImage, setEditedImage] = useState(""); + const [editTitle, setEditedTitle] = useState(""); + const [editReleaseDate, setEditedReleaseDate] = useState(""); + const [editID, setEditID] = useState(0); + const openModalEdit = (id: number, title: string, description: string, image_path: string, release_date: string, teacher_id: number) => { + setEditID(id); setEditedTitle(title); setEditedDescription(description); + setEditedImage(image_path); + setEditedReleaseDate(release_date); + setEditedTeacher(teacher_id); + setIsModalEditOpen(true); }; const closeModalEdit = () => { setIsModalEditOpen(false); }; - const handleEdit = () => { - // Handle the editing action here, e.g., send an API request to update the data - // You can use the editedTitle and editedDescription state variables - // to send the updated data. - // After editing is complete, close the modal. - closeModalEdit(); + + const successEdit = () => { + setRefresher((prevRefresh) => !prevRefresh); }; + // HANDLING DELETE COURSE const [isModalDeleteOpen, setIsModalDeleteOpen] = useState(false); - const [deletedID, setDeletedID] = useState(0); - const openModalDelete = (id: number) => { + const [deleteID, setDeleteID] = useState(0); + const [deleteTitle, setDeleteTitle] = useState(""); + const openModalDelete = (id: number, title: string) => { + setDeleteID(id); + setDeleteTitle(title); setIsModalDeleteOpen(true); - setDeletedID(id); }; const closeModalDelete = () => { setIsModalDeleteOpen(false); }; - const handleDelete = () => { - // Handle the Deleteing action here, e.g., send an API request to update the data - // You can use the DeleteedTitle and DeleteedDescription state variables - // to send the updated data. - // After Deleteing is complete, close the modal. - closeModalDelete(); - }; + const handleDelete = async () => { + try { + setIsLoading(true); + const response = await newAxiosInstance.delete(`${config.REST_API_URL}/course/${deleteID}`); - const [isModalAddOpen, setIsModalAddOpen] = useState(false); - const openModalAdd = () => { - setIsModalAddOpen(true); - }; - const closeModalAdd = () => { - setIsModalAddOpen(false); - }; - const handleAdd = () => { - // Handle the Adding action here, e.g., send an API request to update the data - // You can use the AddedTitle and AddedDescription state variables - // to send the updated data. - // After Adding is complete, close the modal. - closeModalAdd(); - }; + console.log('Course deleted successfully:', response.data.message); - const [courses, setCourses] = useState([ - { - course_id: 1, - title: "Course 1", - description: "Course1", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 5, - title: "Course 5", - description: "Courszxcze5zxczxczxczxczx", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 2, - title: "Course 2", - description: "Course2", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 3, - title: "Course 3", - description: "Course3", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 5, - title: "Course 5", - description: "Courszxcze5zxczxczxczxczx", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 4, - title: "Course 4", - description: "Course4", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 5, - title: "Course 5", - description: "Course5", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 5, - title: "Course 5", - description: "xcCourse5", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 5, - title: "Course 5", - description: "Courszxcze5zxczxczxczxczx", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 5, - title: "Course 5", - description: "Courszxcze5zxczxczxczxczx", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 5, - title: "Course 5", - description: "Course5", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 5, - title: "Course 5", - description: "Czxczxourse5", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 5, - title: "Course 5", - description: "Course5", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 5, - title: "Course 5", - description: "Courzxczxce5czxc", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 5, - title: "Course 5", - description: "Course5", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 5, - title: "Course 5", - description: "Courzxcse5czxzxczxvzxczxc", - course_password: 12347, - release_date: "1-1-2023", - }, - { - course_id: 5, - title: "Course 5", - description: "Courszxcze5zxczxczxczxczx", - course_password: 12347, - release_date: "1-1-2023", - }, - ]); + setIsLoading(false); + } catch (error) { + console.error('Error deleting course:', error); + } + closeModalDelete(); + setRefresher((prevRefresh) => !prevRefresh) // lgsung request data baru tanpa hrus reload page (harusnya works) + }; return ( <Container overflow="auto" maxW={"100vw"} maxH={"100vh"}> - {/* Render the EditCourseModal component conditionally */} - <EditCourseModal + {/* Render the EditModal component conditionally */} + <Loading loading={isLoading} /> + <ModalEdit isOpen={isModalEditOpen} onClose={closeModalEdit} - title={editedTitle} - description={editedDescription} - handleEdit={handleEdit} + successEdit={successEdit} + editTitle={editTitle} + editDescription={editDescription} + courseId={editID} + image_path={editImage} + teacher_id={editTeacher} /> - {/* Render the DeleteCourseModal component conditionally */} - <DeleteCourseModal + {/* Render the DeleteModal component conditionally */} + <ModalDelete isOpen={isModalDeleteOpen} onClose={closeModalDelete} - course_id={deletedID} + title={deleteTitle} handleDelete={handleDelete} /> - {/* Render the DeleteCourseModal component conditionally */} - <AddCourseModal + <ModalAdd isOpen={isModalAddOpen} onClose={closeModalAdd} - handleAdd={handleAdd} + successAdd={successEdit} /> <Flex alignItems="center" justifyContent="center" mb="20px" mt="50px"> <Flex @@ -257,7 +200,7 @@ export const CoursesList = () => { <Button bg="#9d4bff" textColor="white" - _hover={{ bg: "#23004d " }} + _hover={{ bg: "#23004d" }} my="5" onClick={openModalAdd} > @@ -265,179 +208,674 @@ export const CoursesList = () => { </Button> </Box> </Box> - <TableContainer width="80vw"> - <DataTable - stripedRows - value={courses} - paginator - paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink JumpToPageInput" - rows={5} - selectionMode="single" - > - <Column - field="course_id" - header="CourseID" - headerClassName="custom-header" - ></Column> - <Column - field="title" - header="Title" - headerClassName="custom-header" - ></Column> - <Column - field="description" - header="Description" - headerClassName="custom-header" - ></Column> - <Column - field="release_date" - header="Release Date" - headerClassName="custom-header" - ></Column> - <Column - header="Action" - headerClassName="custom-header" - body={(rowData) => ( - <span> - <Icon - as={BiSolidEdit} - fontSize={"24"} - color={"#564c95"} - _hover={{ color: "green" }} - cursor={"pointer"} - onClick={() => - openModalEdit( - rowData.course_id, - rowData.title, - rowData.description - ) - } - ></Icon> - - <Icon - as={BiSolidTrash} - fontSize={"24"} - color={"#564c95"} - _hover={{ color: "red" }} - cursor={"pointer"} - onClick={() => openModalDelete(rowData.course_id)} - ></Icon> - </span> - )} - ></Column> - </DataTable> + <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%">CourseID</Th> + <Th textAlign={"center"} color="white" w="25%">Title</Th> + <Th textAlign={"center"} color="white" w="35%">Description</Th> + <Th textAlign={"center"} color="white" w="10%">Release Date</Th> + <Th textAlign={"center"} color="white" w="20%">Action</Th> + </Tr> + </Thead> + {courses + .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"} fontWeight={"bold"}>{item.title}</Td> + <Td textAlign={"center"}>{item.description}</Td> + <Td textAlign={"center"}>{item.release_date.substring(0, 10)}</Td> + <Td textAlign={"center"}> + <Icon + as={BiSolidEdit} + fontSize={"24"} + color={"#564c95"} + _hover={{ color: "green" }} + cursor={"pointer"} + onClick={() => + openModalEdit( + item.id, + item.title, + item.description, + item.image_path, + item.release_date, + item.teacher_id, + ) + } + ></Icon> + + <Icon + as={BiSolidTrash} + fontSize={"24"} + color={"#564c95"} + _hover={{ color: "red" }} + cursor={"pointer"} + onClick={() => openModalDelete(item.id, item.title)} + ></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 Add */ +} +interface ModalAddProps { + isOpen: boolean; + onClose: () => void; + successAdd: () => void; +} + +function ModalAdd({ + isOpen, + onClose, + successAdd, +}: ModalAddProps) { + const axiosInstance = axios.create(axiosConfig()); + const toast = useToast(); + const [isLoading, setIsLoading] = useState(false); + const [newTitle, setNewTitle] = useState<string>(''); + const [newDescription, setNewDescription] = useState<string>(''); + const [newTeacher, setNewTeacher] = useState(''); + const [fileName, setFileName] = useState<string>(''); + const [teacherError, setTeacherError] = useState<string>(''); + const [selectedFile, setSelectedFile] = useState<File | null>(null); + const [isAllValid, setIsAllValid] = useState({ + title: false, + description: false, + teacher: false, + file: false, + }); + + const handleChangeTitle = (e: React.ChangeEvent<HTMLInputElement>) => { + if (e.target.value.length > 0) { + setNewTitle(e.target.value); + setIsAllValid((prevState) => ({ + ...prevState, + title: true, + })); + } else { + setNewTitle(""); + setIsAllValid((prevState) => ({ + ...prevState, + title: false, + })); + } + }; + + const handleChangeDescription = (e: React.ChangeEvent<HTMLInputElement>) => { + if (e.target.value.length > 0) { + setNewDescription(e.target.value); + setIsAllValid((prevState) => ({ + ...prevState, + description: true, + })); + } else { + setNewDescription(""); + setIsAllValid((prevState) => ({ + ...prevState, + description: false, + })); + } + }; + + const handleChangeTeacher = (e: React.ChangeEvent<HTMLInputElement>) => { + if (e.target.value.length > 0) { + try { + const id = e.target.value.replace(/\s/g, ''); + axiosInstance + .get(`${config.REST_API_URL}/user/${id}`) + .then((res) => { + + if (res.data.status != 200) { + setTeacherError("Teacher with this ID is not available"); + setIsAllValid((prevState) => ({ + ...prevState, + teacher: false, + })); + } else { + setTeacherError(""); + setNewTeacher(e.target.value); + setIsAllValid((prevState) => ({ + ...prevState, + teacher: true, + })); + } + }); + } catch (error) { + console.log(error); + }; + } else { + setTeacherError("Teacher with this ID is not available"); + setIsAllValid((prevState) => ({ + ...prevState, + teacher: false, + })); + } + }; + + const handleChangeFile = (event: React.ChangeEvent<HTMLInputElement>) => { + const file = event.target.files?.[0]; + if (file) { + setSelectedFile(file); + } else { + setSelectedFile(null); + } + }; + useEffect(() => { + const checkFile = () => { + if (selectedFile) { + setFileName(selectedFile.name.replace(/\s/g, "")); + setIsAllValid({ ...isAllValid, file: true }); + } else { + setSelectedFile(null); + setIsAllValid({ ...isAllValid, file: false }); + } + } + checkFile(); + }, [selectedFile]); + + const handleClose = () => { + onClose(); + setTeacherError(""); + setIsAllValid({ ...isAllValid, title: false, description: false, teacher: false, file: false }) + } + + const handleAdd = async () => { + try { + setIsLoading(true); + const response = await axiosInstance.post(`${config.REST_API_URL}/course`, { + title: newTitle, + description: newDescription, + image_path: fileName, + teacher_id: parseInt(newTeacher), + }); + + console.log('User added successfully:', response.data.message); + setIsLoading(false); + successAdd(); + } catch (error) { + console.error('Error adding user:', error); + } + handleClose(); + }; + + return ( + <> + <Loading loading={isLoading} /> + <Modal isOpen={isOpen} onClose={handleClose}> + <ModalOverlay /> + <ModalContent> + <ModalHeader bg="#d78dff" textAlign={"center"}> + Edit Premium Course + </ModalHeader> + <ModalCloseButton /> + <ModalBody> + <FormControl> + <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> + New Title + </FormLabel> + <Input + isRequired + variant="outline" + bg="white" + borderRadius="15px" + mb="5" + fontSize="sm" + placeholder={"Insert New Title Course"} + size="lg" + onChange={handleChangeTitle} + /> + + <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> + New Description + </FormLabel> + <Input + isRequired + variant="outline" + bg="white" + borderRadius="15px" + mb="5" + fontSize="sm" + placeholder={"Insert Description"} + size="lg" + onChange={handleChangeDescription} + /> + + <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> + ID Teacher Assigned + </FormLabel> + <Input + isRequired + bg="white" + borderRadius="15px" + mb="12px" + fontSize="sm" + placeholder={"Assign Teacher"} + size="lg" + onKeyPress={(e) => { + if (e.key === ' ') { + e.preventDefault(); + } + }} + onChange={(e) => { + const sanitizedValue = e.target.value.replace(/[^0-9]/g, ''); + e.target.value = sanitizedValue; + handleChangeTeacher(e); + }} + /> + {newTeacher && !isAllValid.teacher && ( + <Text color="red.400" fontSize="12px" mt="-2" mb="2"> + {teacherError} + </Text> + )} + + <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> + Course Image + </FormLabel> + <Input + fontSize="sm" + border="none" + type="file" + accept="image/*" + size="lg" + onChange={handleChangeFile} + /> + + </FormControl> + </ModalBody> + + <ModalFooter justifyContent={"center"}> + <ButtonGroup> + <Button colorScheme="gray" flex="1" onClick={handleClose}> + Cancel + </Button> + <Button + colorScheme="purple" + flex="1" + ml={3} + onClick={handleAdd} + isDisabled={ + !( + isAllValid.title && + isAllValid.description && + isAllValid.file && + isAllValid.teacher + ) + } + > + Add + </Button> + </ButtonGroup> + </ModalFooter> + </ModalContent> + </Modal> + </> + ); +} + + + { /* Modal Edit */ } -interface EditCourseModalProps { +interface ModalEditProps { isOpen: boolean; onClose: () => void; - title: string; - description: string; - handleEdit: () => void; + successEdit: () => void; + editTitle: string; + editDescription: string; + courseId: number; + image_path: string; + teacher_id: number; } -function EditCourseModal({ +function ModalEdit({ isOpen, onClose, - title, - description, - handleEdit, -}: EditCourseModalProps) { + successEdit, + editTitle, + editDescription, + courseId, + image_path, + teacher_id, +}: ModalEditProps) { + const axiosInstance = axios.create(axiosConfig()); + const toast = useToast(); + const [isLoading, setIsLoading] = useState(false); + const [newTitle, setNewTitle] = useState<string>(''); + const [newDescription, setNewDescription] = useState<string>(''); + const [newTeacher, setNewTeacher] = useState(''); + const [fileName, setFileName] = useState<string>(''); + const [oldTitle, setOldTitle] = useState<string>(''); + const [oldDescription, setOldDescription] = useState<string>(''); + const [oldTeacher, setOldTeacher] = useState(''); + const [oldFileName, setOldFileName] = useState<string>(''); + const [teacherError, setTeacherError] = useState<string>(''); + const [selectedFile, setSelectedFile] = useState<File | null>(null); + const [isAllValid, setIsAllValid] = useState({ + title: false, + description: false, + teacher: false, + file: false, + }); + + const handleChangeTitle = (e: React.ChangeEvent<HTMLInputElement>) => { + if (e.target.value.length > 0) { + setNewTitle(e.target.value); + setIsAllValid((prevState) => ({ + ...prevState, + title: true, + })); + } else { + setNewTitle(oldTitle); + setIsAllValid((prevState) => ({ + ...prevState, + title: false, + })); + } + }; + + const handleChangeDescription = (e: React.ChangeEvent<HTMLInputElement>) => { + if (e.target.value.length > 0) { + setNewDescription(e.target.value); + setIsAllValid((prevState) => ({ + ...prevState, + description: true, + })); + } else { + setNewDescription(oldDescription); + setIsAllValid((prevState) => ({ + ...prevState, + description: false, + })); + } + }; + + const handleChangeTeacher = (e: React.ChangeEvent<HTMLInputElement>) => { + if (e.target.value.length > 0) { + try { + const id = e.target.value.replace(/\s/g, ''); + axiosInstance + .get(`${config.REST_API_URL}/user/${id}`) + .then((res) => { + + if (res.data.status != 200) { + setTeacherError("Teacher with this ID is not available"); + setNewTeacher(oldTeacher); + setIsAllValid((prevState) => ({ + ...prevState, + teacher: false, + })); + } else { + setTeacherError(""); + setNewTeacher(e.target.value); + setIsAllValid((prevState) => ({ + ...prevState, + teacher: true, + })); + } + }); + } catch (error) { + console.log(error); + }; + } else { + setTeacherError(""); + setNewTeacher(oldTeacher); + setIsAllValid((prevState) => ({ + ...prevState, + teacher: false, + })); + } + }; + + const handleChangeFile = (event: React.ChangeEvent<HTMLInputElement>) => { + const file = event.target.files?.[0]; + if (file) { + setSelectedFile(file); + } else { + setSelectedFile(null); + } + }; + useEffect(() => { + const checkFile = () => { + if (selectedFile) { + setFileName(selectedFile.name.replace(/\s/g, "")); + setIsAllValid({ ...isAllValid, file: true }); + } else { + setSelectedFile(null); + setIsAllValid({ ...isAllValid, file: false }); + } + } + checkFile(); + }, [selectedFile]); + + const handleClose = () => { + onClose(); + setTeacherError(""); + setIsAllValid({ ...isAllValid, title: false, description: false, teacher: false, file: false }) + } + + useEffect(() => { + // Fetch Data to Get Course Detail + const fetchData = async () => { + try { + setIsLoading(true); + const res = await axiosInstance.get( + `${config.REST_API_URL}/course/${courseId}` + ); + if (res.data.status === 200) { + setNewTitle(res.data.data.title); + setNewDescription(res.data.data.description); + setFileName(res.data.data.image_path); + setNewTeacher(res.data.data.teacher_id); + + setOldTeacher(res.data.data.teacher_id); + setOldTitle(res.data.data.title); + setOldDescription(res.data.data.description); + setOldFileName(res.data.data.material_path); + } else { + } + setIsLoading(false); + } catch (error) { + console.error("Error fetching material data:", error); + } + }; + fetchData(); + }, [courseId]); + + const handleEdit = async () => { + try { + setIsLoading(true); + const response = await axiosInstance.put(`${config.REST_API_URL}/course/${courseId}`, { + title: newTitle, + description: newDescription, + image_path: fileName, + teacher_id: newTeacher, + }); + + console.log('User edited successfully:', response.data.message); + setIsLoading(false); + successEdit(); + } catch (error) { + console.error('Error editing user:', error); + } + handleClose(); + }; + return ( - <Modal isOpen={isOpen} onClose={onClose}> - <ModalOverlay /> - <ModalContent> - <ModalHeader bg="#d78dff" textAlign={"center"}> - Edit Premium Course - </ModalHeader> - <ModalCloseButton /> - <ModalBody> - <FormControl> - <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> - Course Title - </FormLabel> - <Input - isRequired - variant="outline" - bg="white" - borderRadius="15px" - mb="5" - fontSize="sm" - placeholder={title} - size="lg" - /> - - <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> - Course Description - </FormLabel> - <Textarea - isRequired - h="50" - maxHeight={"150"} - bg="white" - borderRadius="15px" - mb="5" - fontSize="sm" - placeholder={description} - size="lg" - /> - <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> - Course Image (Optional) - </FormLabel> - <Input - fontSize="sm" - border="none" - type="file" - accept="image/*" - size="lg" - /> - </FormControl> - </ModalBody> + <> + <Loading loading={isLoading} /> + <Modal isOpen={isOpen} onClose={handleClose}> + <ModalOverlay /> + <ModalContent> + <ModalHeader bg="#d78dff" textAlign={"center"}> + Edit Premium Course + </ModalHeader> + <ModalCloseButton /> + <ModalBody> + <FormControl> + <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> + New Title + </FormLabel> + <Input + isRequired + variant="outline" + bg="white" + borderRadius="15px" + mb="5" + fontSize="sm" + placeholder={editTitle} + size="lg" + onChange={handleChangeTitle} + /> - <ModalFooter justifyContent={"center"}> - <ButtonGroup> - <Button colorScheme="gray" flex="1" onClick={onClose}> - Cancel - </Button> - <Button colorScheme="purple" flex="1" ml={3} onClick={handleEdit}> - Edit - </Button> - </ButtonGroup> - </ModalFooter> - </ModalContent> - </Modal> + <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> + New Description + </FormLabel> + <Input + isRequired + variant="outline" + bg="white" + borderRadius="15px" + mb="5" + fontSize="sm" + placeholder={editDescription} + size="lg" + onChange={handleChangeDescription} + /> + + <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> + ID Teacher Assigned + </FormLabel> + <Input + isRequired + bg="white" + borderRadius="15px" + mb="12px" + fontSize="sm" + placeholder={oldTeacher} + size="lg" + onKeyPress={(e) => { + if (e.key === ' ') { + e.preventDefault(); + } + }} + onChange={(e) => { + const sanitizedValue = e.target.value.replace(/[^0-9]/g, ''); + e.target.value = sanitizedValue; + handleChangeTeacher(e); + }} + /> + {newTeacher && !isAllValid.teacher && ( + <Text color="red.400" fontSize="12px" mt="-2" mb="2"> + {teacherError} + </Text> + )} + + <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> + Course Image + </FormLabel> + <Input + fontSize="sm" + border="none" + type="file" + accept="image/*" + size="lg" + onChange={handleChangeFile} + /> + + </FormControl> + </ModalBody> + + <ModalFooter justifyContent={"center"}> + <ButtonGroup> + <Button colorScheme="gray" flex="1" onClick={handleClose}> + Cancel + </Button> + <Button + colorScheme="purple" + flex="1" + ml={3} + onClick={handleEdit} + isDisabled={ + !( + isAllValid.title || + isAllValid.description || + isAllValid.file || + isAllValid.teacher + ) + } + > + Edit + </Button> + </ButtonGroup> + </ModalFooter> + </ModalContent> + </Modal> + </> ); } { /* Modal Delete */ } -interface DeleteCourseModalProps { +interface ModalDeleteProps { isOpen: boolean; onClose: () => void; - course_id: number; + title: string; handleDelete: () => void; } -function DeleteCourseModal({ +function ModalDelete({ isOpen, onClose, - course_id, + title, handleDelete, -}: DeleteCourseModalProps) { +}: ModalDeleteProps) { return ( <Modal isOpen={isOpen} onClose={onClose}> <ModalOverlay /> <ModalContent> - <ModalHeader textAlign={"center"}>Delete Course</ModalHeader> + <ModalHeader textAlign={"center"}>Delete User</ModalHeader> <ModalCloseButton /> <ModalBody textAlign={"center"}> <Box @@ -447,7 +885,7 @@ function DeleteCourseModal({ justifyContent="center" > <Text as={BiError} fontSize={"150px"} color="red" /> - <Text>Are you sure want to delete this course?</Text> + <Text>Are you sure want to delete {title} ?</Text> </Box> </ModalBody> @@ -466,80 +904,4 @@ function DeleteCourseModal({ ); } -{ - /* Modal Add */ -} -interface AddCourseModalProps { - isOpen: boolean; - onClose: () => void; - handleAdd: () => void; -} - -function AddCourseModal({ isOpen, onClose, handleAdd }: AddCourseModalProps) { - return ( - <Modal isOpen={isOpen} onClose={onClose}> - <ModalOverlay /> - <ModalContent> - <ModalHeader bg="#d78dff" textAlign={"center"}> - Add New Premium Course - </ModalHeader> - <ModalCloseButton /> - <ModalBody> - <FormControl> - <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> - Course Title - </FormLabel> - <Input - isRequired - variant="outline" - bg="white" - borderRadius="15px" - mb="5" - fontSize="sm" - size="lg" - placeholder={"Insert Title Here"} - /> - - <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> - Course Description - </FormLabel> - <Textarea - isRequired - h="50" - maxHeight={"150"} - bg="white" - borderRadius="15px" - mb="5" - fontSize="sm" - size="lg" - placeholder={"Insert Description Here"} - /> - - <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> - Course Image - </FormLabel> - <Input - isRequired - fontSize="sm" - border="none" - type="file" - accept="image/*" - size="lg" - /> - </FormControl> - </ModalBody> - - <ModalFooter justifyContent={"center"}> - <ButtonGroup> - <Button colorScheme="gray" flex="1" onClick={onClose}> - Cancel - </Button> - <Button colorScheme="purple" flex="1" ml={3} onClick={handleAdd}> - Add - </Button> - </ButtonGroup> - </ModalFooter> - </ModalContent> - </Modal> - ); -} \ No newline at end of file +export default CoursesList; diff --git a/src/types.tsx b/src/types.tsx index cd44794a48a1196350df5dfc1b6363c8a1d54ff9..352e93d0528218851d7d14b88aaccfb7edf2992d 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -1,9 +1,10 @@ export type Courses = { id: number; - title: String; - description: String; - image_path: String; + title: string; + description: string; + image_path: string; release_date: string; + teacher_id: number; } export type Modules = {