diff --git a/backend/yarn.lock b/backend/yarn.lock index 06554171..c05c655b 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1264,11 +1264,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@panva/asn1.js@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6" - integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== - "@pdf-lib/standard-fonts@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz#8ba691c4421f71662ed07c9a0294b44528af2d7f" diff --git a/frontend/src/components/course_viewing/CourseViewingPage.tsx b/frontend/src/components/course_viewing/CourseViewingPage.tsx index 85a017de..30b53ab7 100644 --- a/frontend/src/components/course_viewing/CourseViewingPage.tsx +++ b/frontend/src/components/course_viewing/CourseViewingPage.tsx @@ -4,13 +4,60 @@ import MenuOpenIcon from "@mui/icons-material/MenuOpen"; import UnitSidebar from "./UnitSidebar"; import { CourseUnit } from "../../types/CourseTypes"; import CourseAPIClient from "../../APIClients/CourseAPIClient"; +import CreateUnitModal from "./modals/CreateUnitModal"; +import DeleteUnitModal from "./modals/DeleteUnitModal"; +import EditUnitModal from "./modals/EditUnitModals"; export default function CourseUnitsPage() { const theme = useTheme(); const [courseUnits, setCourseUnits] = useState([]); - + const [createUnitName, setCreateUnitName] = useState(""); + const [editUnitName, setEditUnitName] = useState(""); + const [openCreateUnitModal, setOpenCreateUnitModal] = useState(false); + const [openDeleteUnitModal, setOpenDeleteUnitModal] = useState(false); + const [openEditUnitModal, setOpenEditUnitModal] = useState(false); + const [selectedCourseId, setSelectedCourseId] = useState(""); const [open, setOpen] = useState(true); + const handleOpenCreateUnitModal = () => { + setOpenCreateUnitModal(true); + }; + + const handleCloseCreateUnitModal = () => { + setOpenCreateUnitModal(false); + }; + + const handleCloseDeleteUnitModal = () => { + setOpenDeleteUnitModal(false); + }; + + const handleCloseEditUnitModal = () => { + setOpenEditUnitModal(false); + }; + + const handleOpenDeleteUnitModal = () => { + setOpenDeleteUnitModal(true); + }; + + const handleOpenEditUnitModal = () => { + setOpenEditUnitModal(true); + }; + + const createUnit = () => { + // dummy function + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const unitName = createUnitName; + }; + const deleteUnit = () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const courseId = selectedCourseId; + }; + + const editUnit = () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const courseId = editUnitName; + }; + const handleDrawerOpen = () => { setOpen(true); }; @@ -29,9 +76,32 @@ export default function CourseUnitsPage() { return ( + + + + + {!open && ( diff --git a/frontend/src/components/course_viewing/UnitSidebar.tsx b/frontend/src/components/course_viewing/UnitSidebar.tsx index 1daf167a..181bae65 100644 --- a/frontend/src/components/course_viewing/UnitSidebar.tsx +++ b/frontend/src/components/course_viewing/UnitSidebar.tsx @@ -2,30 +2,157 @@ import React, { useState } from "react"; import { Box, Button, + Divider, Drawer, + IconButton, List, ListItem, ListItemButton, + ListItemIcon, ListItemText, + Menu, + MenuItem, + Typography, useTheme, } from "@mui/material"; +import MoreVertIcon from "@mui/icons-material/MoreVert"; +import EditOutlinedIcon from "@mui/icons-material/EditOutlined"; +import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; +import MoveDownIcon from "@mui/icons-material/MoveDown"; import MenuOpenIcon from "@mui/icons-material/MenuOpen"; +import AddIcon from "@mui/icons-material/Add"; import { CourseUnit } from "../../types/CourseTypes"; import { useUser } from "../../hooks/useUser"; +import { isAdministrator } from "../../types/UserTypes"; +enum ModalType { + Create = "Create", + Delete = "Delete", + Edit = "Edit", +} interface UnitSideBarProps { courseUnits: CourseUnit[]; handleClose: () => void; + handleOpenCreateUnitModal: () => void; + handleOpenDeleteUnitModal: () => void; + handleOpenEditUnitModal: () => void; + setSelectedCourseId: (value: React.SetStateAction) => void; open: boolean; } export default function UnitSidebar(props: UnitSideBarProps) { const theme = useTheme(); const user = useUser(); - const { courseUnits, handleClose, open } = props; + const { + courseUnits, + handleClose, + open, + handleOpenCreateUnitModal, + handleOpenDeleteUnitModal, + handleOpenEditUnitModal, + setSelectedCourseId, + } = props; const [selectedIndex, setSelectedIndex] = useState(0); + const [anchorEl, setAnchorEl] = React.useState(null); + + const openContextMenu = Boolean(anchorEl); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const handleContextMenuOpen = (event: any, courseId: string) => { + setAnchorEl(event.currentTarget); + setSelectedCourseId(courseId); + }; + const handleContextMenuClose = () => { + setAnchorEl(null); + }; + const handleOpenModal = (action: string) => { + handleContextMenuClose(); + switch (action) { + case ModalType.Create: + handleOpenCreateUnitModal(); + break; + case ModalType.Delete: + handleOpenDeleteUnitModal(); + break; + case ModalType.Edit: + handleOpenEditUnitModal(); + break; + default: + } + }; + + const ContextMenu = () => { + return ( + + handleOpenModal(ModalType.Edit)} + > + + + + Edit unit title + + + + + + + Move + + + handleOpenModal(ModalType.Delete)} + sx={{ + height: "48px", + }} + > + + + + + Delete + + + + ); + }; const handleListItemClick = ( event: React.MouseEvent, index: number, @@ -36,7 +163,8 @@ export default function UnitSidebar(props: UnitSideBarProps) { return ( + {courseUnits.map((course, index) => { return ( @@ -116,11 +245,51 @@ export default function UnitSidebar(props: UnitSideBarProps) { : theme.typography.bodyLarge } /> + {isAdministrator(user) && ( + { + event.stopPropagation(); // Prevent triggering the list item click + handleContextMenuOpen(event, course.id); // Custom function to handle button click + }} + sx={{ marginLeft: "16px" }} + > + + + )} ); })} + {isAdministrator(user) && ( + + )} ); diff --git a/frontend/src/components/course_viewing/modals/CreateUnitModal.tsx b/frontend/src/components/course_viewing/modals/CreateUnitModal.tsx new file mode 100644 index 00000000..5950a7e2 --- /dev/null +++ b/frontend/src/components/course_viewing/modals/CreateUnitModal.tsx @@ -0,0 +1,174 @@ +import React from "react"; +import CloseIcon from "@mui/icons-material/Close"; +import { PersonOutlineOutlined } from "@mui/icons-material"; +import { + Box, + Button, + Dialog, + DialogContent, + DialogTitle, + IconButton, + InputAdornment, + TextField, + Typography, + useTheme, +} from "@mui/material"; +import { useUser } from "../../../hooks/useUser"; + +interface CreateUnitModalProps { + openCreateUnitModal: boolean; + handleCloseCreateUnitModal: () => void; + setCreateUnitName: (value: React.SetStateAction) => void; + createUnit: () => void; +} + +export default function CreateUnitModal(props: CreateUnitModalProps) { + const { + openCreateUnitModal, + handleCloseCreateUnitModal, + setCreateUnitName, + createUnit, + } = props; + + const theme = useTheme(); + const user = useUser(); + + return ( + + + + + + + + + + + Create a new unit + + + + + + + setCreateUnitName(event.target.value)} + variant="outlined" + sx={{ + display: "flex", + flexDirection: "column", + alignSelf: "stretch", + height: "56px", + width: "100%", + }} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + + + + + + + + + + ); +} diff --git a/frontend/src/components/course_viewing/modals/DeleteUnitModal.tsx b/frontend/src/components/course_viewing/modals/DeleteUnitModal.tsx new file mode 100644 index 00000000..95bd13fe --- /dev/null +++ b/frontend/src/components/course_viewing/modals/DeleteUnitModal.tsx @@ -0,0 +1,143 @@ +import React from "react"; +import CloseIcon from "@mui/icons-material/Close"; +import { + Box, + Button, + Dialog, + DialogContent, + DialogContentText, + DialogTitle, + IconButton, + Typography, + useTheme, +} from "@mui/material"; +import { useUser } from "../../../hooks/useUser"; + +interface DeleteUnitModalProps { + openDeleteUnitModal: boolean; + handleCloseDeleteUnitModal: () => void; + deleteUnit: () => void; +} + +export default function CreateUnitModal(props: DeleteUnitModalProps) { + const { openDeleteUnitModal, handleCloseDeleteUnitModal, deleteUnit } = props; + + const theme = useTheme(); + const user = useUser(); + + return ( + + + + + + + + + + + Delete Unit? + + + + + + This action can't be undone. A deleted unit cannot be + recovered. + + + + + + + + + + + ); +} diff --git a/frontend/src/components/course_viewing/modals/EditUnitModals.tsx b/frontend/src/components/course_viewing/modals/EditUnitModals.tsx new file mode 100644 index 00000000..a0947233 --- /dev/null +++ b/frontend/src/components/course_viewing/modals/EditUnitModals.tsx @@ -0,0 +1,174 @@ +import React from "react"; +import CloseIcon from "@mui/icons-material/Close"; +import { PersonOutlineOutlined } from "@mui/icons-material"; +import { + Box, + Button, + Dialog, + DialogContent, + DialogTitle, + IconButton, + InputAdornment, + TextField, + Typography, + useTheme, +} from "@mui/material"; +import { useUser } from "../../../hooks/useUser"; + +interface EditUnitModalProps { + openEditUnitModal: boolean; + handleCloseEditUnitModal: () => void; + setEditUnitName: (value: React.SetStateAction) => void; + editUnit: () => void; +} + +export default function EditUnitModal(props: EditUnitModalProps) { + const { + openEditUnitModal, + handleCloseEditUnitModal, + setEditUnitName, + editUnit, + } = props; + + const theme = useTheme(); + const user = useUser(); + + return ( + + + + + + + + + + + Edit unit + + + + + + + setEditUnitName(event.target.value)} + variant="outlined" + sx={{ + display: "flex", + flexDirection: "column", + alignSelf: "stretch", + height: "56px", + width: "100%", + }} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + + + + + + + + + + ); +}