diff --git a/src/components/modals/material.tsx b/src/components/modals/material.tsx index 8bf7a8a751f23cb9f5e1a3ab24f7fca7df0be370..d33ff039c804d0e9ba2085f87dc80a223eabb14a 100644 --- a/src/components/modals/material.tsx +++ b/src/components/modals/material.tsx @@ -1,211 +1,494 @@ -import { Box, Text, Modal, ModalOverlay, ModalContent, ModalHeader, ModalCloseButton, ModalBody, FormControl, FormLabel, Input, Textarea, ModalFooter, ButtonGroup, Button } from "@chakra-ui/react"; +import { + Box, + Text, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalCloseButton, + ModalBody, + FormControl, + FormLabel, + Input, + Textarea, + ModalFooter, + ButtonGroup, + Button, +} from "@chakra-ui/react"; +import { ChangeEvent, createRef, useEffect, useState } from "react"; import { BiError } from "react-icons/bi"; +import { axiosConfig } from "../../utils/axios"; +import axios from "axios"; +import config from "../../config/config"; +import Loading from "../loading/Loading"; -{ /* Modal Edit */ } -interface EditMaterialModalProps { +{/* Modal Add Material*/ } +interface AddMaterialModalProps { isOpen: boolean; onClose: () => void; - title: string; - description: string; - handleEdit: () => void; + successAdd: () => void; + moduleId: number; } -export function EditMaterialModal({ +export function AddMaterialModal({ isOpen, onClose, - title, - description, - handleEdit, -}: EditMaterialModalProps) { + successAdd, + moduleId, +}: AddMaterialModalProps) { + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const newAxiosInstance = axios.create(axiosConfig()); + const [fileType, setFileType] = useState(""); + const [nameFile, setNameFile] = useState(""); + const [selectedFile, setSelectedFile] = useState<File | null>(null); + const [isAllValid, setIsAllValid] = useState({ + title: false, + description: false, + file: false, + }); + const handleAddMaterial = async () => { + try { + setIsLoading(true); + try { + upload(); + } catch (error) { + console.error('Error uploading:', error); + } finally { + const response = await newAxiosInstance.post(`${config.REST_API_URL}/material/`, { + title: title, + description: description, + source_type: fileType, + material_path: nameFile, + modul_id: moduleId, + }); + + console.log('Material added successfully:', response.data.message); + + // Clear the form after successful submission if needed + setTitle(''); + setDescription(''); + setIsLoading(false); + successAdd(); // Refresh new data without reloading page + } + } catch (error) { + console.error('Error adding material:', error); + } finally { + setIsAllValid(prevState => ({ + ...prevState, + title: false, + description: false, + file: false, + })); + } + // window.location.reload(); // refresh to see new material added (should change to not reloading) + }; + + const upload = () => { + const formData = new FormData() + if (selectedFile) { + formData.append('file', selectedFile) + } + newAxiosInstance.post(`${config.REST_API_URL}/material/upload`, formData) + .then(res => { }) + .catch(er => console.log(er)) + } + + const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => { + if (event.target.files) { + const file = event.target.files?.[0]; + + if (file) { + // const name = file.name; + // const type = file.type; + setSelectedFile(file); + if (file.type.startsWith('video')) { + setFileType('VIDEO'); + } else { + setFileType('PDF'); + } + setNameFile(file.name.replace(/\s/g, '')); + setIsAllValid({...isAllValid, file: true}); + } else { + setIsAllValid({...isAllValid, file: false}); + } + } else { + setSelectedFile(null); + setIsAllValid({...isAllValid, file: false}); + } + }; + + const handleClose = () => { + setTitle(""); + setDescription(""); + setIsAllValid(prevState => ({ + ...prevState, + title: false, + description: false, + file: false, + })); + onClose(); + }; + + const checkTitle = () => { + setTitle((prevTitle) => { + const isValid = prevTitle.trim().length > 0; + setIsAllValid((prev) => ({ ...prev, title: isValid })); + return prevTitle; + }); + }; + + const checkDescription = () => { + setDescription((prevDescription) => { + const isValid = prevDescription.trim().length > 0; + setIsAllValid((prev) => ({ ...prev, description: isValid })); + return prevDescription; + }); + }; + + const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => { + setTitle(e.target.value); + checkTitle(); + }; + + const handleDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { + setDescription(e.target.value); + checkDescription(); + }; + return ( - <Modal isOpen={isOpen} onClose={onClose}> - <ModalOverlay /> - <ModalContent> - <ModalHeader bg="#d78dff" textAlign={"center"}> - Edit Material - </ModalHeader> - <ModalCloseButton /> - <ModalBody> - <FormControl> - <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> - Material 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"> - Material 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"> - Material File - </FormLabel> - <Input - 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={handleEdit}> - Edit - </Button> - </ButtonGroup> - </ModalFooter> - </ModalContent> - </Modal> + <> + <Loading loading={isLoading} /> + <Modal isOpen={isOpen} onClose={handleClose}> + <ModalOverlay /> + <ModalContent> + <ModalHeader bg="#d78dff" textAlign={"center"}> + Add New Material + </ModalHeader> + <ModalCloseButton /> + <ModalBody> + <FormControl> + <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> + Material Title + </FormLabel> + <Input + isRequired + variant="outline" + bg="white" + borderRadius="15px" + mb="5" + fontSize="sm" + size="lg" + placeholder={"Insert Title Here"} + value={title} + onChange={handleTitleChange} + /> + + <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> + Material Description + </FormLabel> + <Textarea + isRequired + h="50" + maxHeight={"150"} + bg="white" + borderRadius="15px" + mb="5" + fontSize="sm" + size="lg" + placeholder={"Insert Description Here"} + value={description} + onChange={handleDescriptionChange} + /> + + <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> + Material File + </FormLabel> + <Input + fontSize="sm" + border="none" + type="file" + accept=".pdf, video/*" + size="lg" + onChange={handleFileChange} + /> + </FormControl> + </ModalBody> + + <ModalFooter justifyContent={"center"}> + <ButtonGroup> + <Button colorScheme="gray" flex="1" onClick={handleClose}> + Cancel + </Button> + <Button colorScheme="purple" flex="1" ml={3} + onClick={handleAddMaterial} + isDisabled={ + !( + isAllValid.title && + isAllValid.description && + isAllValid.file + ) + } + > + Add + </Button> + </ButtonGroup> + </ModalFooter> + </ModalContent> + </Modal> + </> ); } -{ /* Modal Delete */ } -interface DeleteMaterialModalProps { +{ /* Modal Edit Material */ } +interface EditMaterialModalProps { isOpen: boolean; onClose: () => void; - course_id: number; - handleDelete: () => void; + successEdit: () => void; + materialId: number } -export function DeleteMaterialModal({ +export function EditMaterialModal({ isOpen, onClose, - course_id, - handleDelete, -}: DeleteMaterialModalProps) { + successEdit, + materialId, +}: EditMaterialModalProps) { + const [editedTitle, setEditedTitle] = useState(''); + const [editedDescription, setEditedDescription] = useState(''); + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const newAxiosInstance = axios.create(axiosConfig()); + const [isAllValid, setIsAllValid] = useState({ + title: false, + description: false, + file: false, + }); + + useEffect(() => { // Fetch Data to Get Material Detail + const fetchData = async () => { + try { + setIsLoading(true); + const res = await newAxiosInstance.get(`${config.REST_API_URL}/modul/${materialId}`); + if (res.data.status === 200) { + setEditedTitle(res.data.data.title); + setEditedDescription(res.data.data.description); + setTitle(res.data.data.title); + setDescription(res.data.data.description); + } else { } + setIsLoading(false); + } catch (error) { + console.error('Error fetching material data:', error); + } + }; + fetchData(); + }, [materialId]); + + const handleEditMaterial = async () => { + try { + setIsLoading(true); + const response = await newAxiosInstance.put(`${config.REST_API_URL}/modul/${materialId}`, { + title: title, + description: description, + }); + + console.log('Material edited successfully:', response.data.message); + setIsLoading(false); + successEdit(); // Refresh new data without reloading page + } catch (error) { + console.error('Error editing material:', error); + } + // window.location.reload(); // refresh to see new material added (should change to not reloading) + setIsAllValid(prevState => ({ + ...prevState, + title: false, + description: false, + file: false, + })); + }; + + const handleClose = () => { + setTitle(editedTitle); + setDescription(editedDescription); + setIsAllValid(prevState => ({ + ...prevState, + title: false, + description: false, + file: false, + })); + onClose(); + }; + + const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => { + if (e.target.value.length > 0) { + setTitle(e.target.value); + setIsAllValid(prevState => ({ + ...prevState, + title: true, + })); + } else { + setTitle(editedTitle); + setIsAllValid(prevState => ({ + ...prevState, + title: false, + })); + } + }; + + const handleDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { + if (e.target.value.length > 0) { + setDescription(e.target.value); + setIsAllValid(prevState => ({ + ...prevState, + description: true, + })); + } else { + setDescription(editedDescription); + setIsAllValid(prevState => ({ + ...prevState, + description: false, + })); + } + }; + return ( - <Modal isOpen={isOpen} onClose={onClose}> - <ModalOverlay /> - <ModalContent> - <ModalHeader textAlign={"center"}>Delete Material</ModalHeader> - <ModalCloseButton /> - <ModalBody textAlign={"center"}> - <Box - display="flex" - flexDirection="column" - alignItems="center" - justifyContent="center" - > - <Text as={BiError} fontSize={"150px"} color="red" /> - <Text>Are you sure want to delete this Material?</Text> - </Box> - </ModalBody> - - <ModalFooter justifyContent={"center"}> - <ButtonGroup> - <Button colorScheme="gray" flex="1" onClick={onClose}> - Cancel - </Button> - <Button colorScheme="red" flex="1" ml={3} onClick={handleDelete}> - Delete - </Button> - </ButtonGroup> - </ModalFooter> - </ModalContent> - </Modal> + <> + <Loading loading={isLoading} /> + <Modal isOpen={isOpen} onClose={handleClose}> + <ModalOverlay /> + <ModalContent> + <ModalHeader bg="#d78dff" textAlign={"center"}> + Edit Material + </ModalHeader> + <ModalCloseButton /> + <ModalBody> + <FormControl> + <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> + Material Title + </FormLabel> + <Input + isRequired + variant="outline" + bg="white" + borderRadius="15px" + mb="5" + fontSize="sm" + placeholder={title} + size="lg" + // value={editedTitle} + onChange={handleTitleChange} + /> + + <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> + Material Description + </FormLabel> + <Textarea + isRequired + h="50" + maxHeight={"150"} + bg="white" + borderRadius="15px" + mb="5" + fontSize="sm" + placeholder={description} + size="lg" + // value={editedDescription} + onChange={handleDescriptionChange} + /> + </FormControl> + </ModalBody> + + <ModalFooter justifyContent={"center"}> + <ButtonGroup> + <Button colorScheme="gray" flex="1" onClick={handleClose}> + Cancel + </Button> + <Button colorScheme="purple" flex="1" ml={3} + onClick={handleEditMaterial} + isDisabled={ + !(( + (isAllValid.title && isAllValid.description) || + (isAllValid.title || isAllValid.description)) + ) + } + > + Edit + </Button> + </ButtonGroup> + </ModalFooter> + </ModalContent> + </Modal> + </> ); } -{/* Modal Add Material*/} -interface AddMaterialModalProps { +{ /* Modal Delete */ } +interface DeleteMaterialModalProps { isOpen: boolean; onClose: () => void; - handleAddMaterial: () => void; + successDelete: () => void; + materialId: number; } -export function AddMaterialModal({ +export function DeleteMaterialModal({ isOpen, onClose, - handleAddMaterial, -}: AddMaterialModalProps) { + successDelete, + materialId, +}: DeleteMaterialModalProps) { + const [isLoading, setIsLoading] = useState(false); + const newAxiosInstance = axios.create(axiosConfig()); + + const handleDeleteMaterial = async () => { + try { + setIsLoading(true); + const response = await newAxiosInstance.delete(`${config.REST_API_URL}/modul/${materialId}`); + + console.log('Material Deleted successfully:', response.data.message); + + setIsLoading(false); + successDelete(); // Refresh new data without reloading page + } catch (error) { + console.error('Error deleting material:', error); + } + // window.location.reload(); // refresh to see new material added (should change to not reloading) + }; return ( - <Modal isOpen={isOpen} onClose={onClose}> - <ModalOverlay /> - <ModalContent> - <ModalHeader bg="#d78dff" textAlign={"center"}> - Add New Material - </ModalHeader> - <ModalCloseButton /> - <ModalBody> - <FormControl> - <FormLabel ms="4px" fontSize="sm" fontWeight="bold"> - Material 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"> - Material 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"> - File - </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={handleAddMaterial}> - Add - </Button> - </ButtonGroup> - </ModalFooter> - </ModalContent> - </Modal> + <> + <Loading loading={isLoading} /> + <Modal isOpen={isOpen} onClose={onClose}> + <ModalOverlay /> + <ModalContent> + <ModalHeader textAlign={"center"}>Delete Material</ModalHeader> + <ModalCloseButton /> + <ModalBody textAlign={"center"}> + <Box + display="flex" + flexDirection="column" + alignItems="center" + justifyContent="center" + > + <Text as={BiError} fontSize={"150px"} color="red" /> + <Text>Are you sure want to delete this material?</Text> + </Box> + </ModalBody> + + <ModalFooter justifyContent={"center"}> + <ButtonGroup> + <Button colorScheme="gray" flex="1" onClick={onClose}> + Cancel + </Button> + <Button colorScheme="red" flex="1" ml={3} + onClick={handleDeleteMaterial} + > + Delete + </Button> + </ButtonGroup> + </ModalFooter> + </ModalContent> + </Modal> + </> ); -} \ No newline at end of file +} diff --git a/src/pages/Materials.tsx b/src/pages/Materials.tsx index 2329c08a0db100598cbb96c6d57dc49e1977f1fa..8979e62bbf2b0bfd928617800d1a54ba30165599 100644 --- a/src/pages/Materials.tsx +++ b/src/pages/Materials.tsx @@ -28,7 +28,7 @@ import { import { useParams } from "react-router-dom"; import { Modules, Materials } from "../types" import { EditModuleModal, AddModuleModal, DeleteModuleModal } from "../components/modals/module"; -// import { } from "../components/modals/material"; +import { EditMaterialModal, AddMaterialModal, DeleteMaterialModal } from "../components/modals/material"; import { axiosConfig } from "../utils/axios"; import config from "../config/config"; import axios from "axios"; @@ -88,6 +88,10 @@ const ModuleMaterials = () => { }, [refreshModule]); const handleClickModule = (id: number) => { + setIdSelectedModules((prevId) => { + console.log(prevId); // This will log the previous state + return id; + }); getMaterials(id); } @@ -145,10 +149,13 @@ const ModuleMaterials = () => { const initialMaterials: Materials[] = []; const [materials, setMaterials] = useState(initialMaterials); + const [refreshMaterial, setRefreshMaterial] = useState(false); + + // FETCH DATA FROM SERVER const getMaterials = async (module_id: number) => { try { setIsLoading(true); - const res = await newAxiosInstance.get(`${config.REST_API_URL}/modul/course/${module_id}`); + const res = await newAxiosInstance.get(`${config.REST_API_URL}/material/module/${module_id}`); const MaterialsData: Materials[] = res.data.data.map((module: any) => { return { id: module.id, @@ -165,6 +172,58 @@ const ModuleMaterials = () => { } } + + // HANDLING ADD MATERIAL + const [isModalAddMaterialOpen, setIsModalAddMaterialOpen] = useState(false); + const openModalAddMaterial = () => { + setIsModalAddMaterialOpen(true); + }; + const closeModalAddMaterial = () => { + setIsModalAddMaterialOpen(false); + }; + const successAddMaterial = () => { + setIsModalAddMaterialOpen(false); + setRefreshMaterial((prevRefresh) => !prevRefresh); + console.log("ngeteesssssss", idSelectedModules); + getMaterials(idSelectedModules); + } + + // // HANDLING EDIT MATERIAL + // const [isModalEditMaterialOpen, setIsModalEditMaterialOpen] = useState(false); + + // const handleOpenEditMaterial = (id: number) => { + // setIdSelectedMaterials(id); + // openModalEditMaterial(); + // } + // const openModalEditMaterial = () => { + // setIsModalEditMaterialOpen(true); + // }; + // const closeModalEditMaterial = () => { + // setIsModalEditMaterialOpen(false); + // setRefreshMaterial(true); + // }; + // const successEditMaterial = () => { + // setIsModalEditMaterialOpen(false); + // setRefreshMaterial((prevRefresh) => !prevRefresh); + // } + + // // HANDLING DELETE MATERIAL + // const [isModalDeleteOpen, setIsModalDeleteMaterialOpen] = useState(false); + // const handleOpenDeleteMaterial = (id: number) => { + // setIdSelectedMaterials(id); + // openModalDeleteMaterial(); + // } + // const openModalDeleteMaterial = () => { + // setIsModalDeleteMaterialOpen(true); + // }; + // const closeModalDeleteMaterial = () => { + // setIsModalDeleteMaterialOpen(false); + // }; + // const successDeleteMaterial = () => { + // setIsModalDeleteMaterialOpen(false); + // setRefreshMaterial((prevRefresh) => !prevRefresh); + // } + return ( <Container overflow="auto" maxW={"100vw"} maxH={"100vh"}> <Loading loading={isLoading} /> @@ -181,7 +240,7 @@ const ModuleMaterials = () => { onClose={closeModalEditModule} successEdit={successEditModule} moduleId={idSelectedModules} - /> + /> <DeleteModuleModal isOpen={isModalDeleteOpen} @@ -192,11 +251,12 @@ const ModuleMaterials = () => { {/* --------------- MATERIAL POPUPS -------------------- */} - {/* <AddMaterialModal - isOpen={isModalAddOpen} - onClose={closeModalAdd} - handleAdd={handleAdd} - /> */} + <AddMaterialModal + isOpen={isModalAddMaterialOpen} + onClose={closeModalAddMaterial} + successAdd={successAddMaterial} + moduleId={idSelectedModules} + /> <HStack align="start" justify="center"> @@ -333,7 +393,7 @@ const ModuleMaterials = () => { color={"#564c95"} _hover={{ color: "red" }} cursor={"pointer"} - // onClick={() => openModalDelete(item.id)} + // onClick={() => openModalDelete(item.id)} ></Icon> </AccordionButton> <AccordionPanel> @@ -364,7 +424,7 @@ const ModuleMaterials = () => { color={"#564c95"} _hover={{ color: "green" }} cursor={"pointer"} - // onClick={() => openModalAdd(item.module_id, item.title)} + onClick={() => openModalAddMaterial()} ></Icon> </VStack> </HStack>