diff --git a/.github/_workflows/run-checks.yml b/.github/workflows/run-checks.yml similarity index 74% rename from .github/_workflows/run-checks.yml rename to .github/workflows/run-checks.yml index 1c5d262c..4b8468ca 100644 --- a/.github/_workflows/run-checks.yml +++ b/.github/workflows/run-checks.yml @@ -2,9 +2,12 @@ name: Run Checks on: push: - branches: ["main"] + # branches: ["main"] + branches: ["dev"] + pull_request: - branches: ["main"] + # branches: ["main"] + branches: ["dev"] jobs: run-checks: @@ -22,7 +25,8 @@ jobs: - name: Install dependencies run: npm exec --workspaces -- npx rimraf node_modules && npx rimraf - node_modules && yarn install --frozen-lockfile + node_modules && yarn install + # node_modules && yarn install --frozen-lockfile - name: Build React project run: cd client && yarn run build diff --git a/client/package.json b/client/package.json index b8d85865..e2741849 100644 --- a/client/package.json +++ b/client/package.json @@ -14,6 +14,7 @@ "preview": "vite preview" }, "dependencies": { + "@chakra-ui/icons": "^2.0.0", "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.13.0", "@emotion/styled": "^11.13.0", @@ -32,8 +33,8 @@ "react-icons": "^5.3.0", "react-qr-code": "^2.0.15", "react-router-dom": "^6.26.1", - "zod": "^3.23.8", - "recharts": "^2.7.2" + "recharts": "^2.7.2", + "zod": "^3.23.8" }, "devDependencies": { "@eslint/js": "^9.8.0", diff --git a/client/public/card_images/ballet.svg b/client/public/card_images/ballet.svg new file mode 100644 index 00000000..fe10cd6b --- /dev/null +++ b/client/public/card_images/ballet.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/public/card_images/classical.svg b/client/public/card_images/classical.svg new file mode 100644 index 00000000..aafd0a33 --- /dev/null +++ b/client/public/card_images/classical.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/public/card_images/pop.svg b/client/public/card_images/pop.svg new file mode 100644 index 00000000..0b8b9d2a --- /dev/null +++ b/client/public/card_images/pop.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/client/public/card_images/pop2.svg b/client/public/card_images/pop2.svg new file mode 100644 index 00000000..2b44336a --- /dev/null +++ b/client/public/card_images/pop2.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/components/dashboard/cseLogo.png b/client/public/dashboard/cseLogo.png similarity index 100% rename from client/src/components/dashboard/cseLogo.png rename to client/public/dashboard/cseLogo.png diff --git a/client/src/components/dashboard/sidebarImgs/classes.svg b/client/public/dashboard/sidebarImgs/classes.svg similarity index 100% rename from client/src/components/dashboard/sidebarImgs/classes.svg rename to client/public/dashboard/sidebarImgs/classes.svg diff --git a/client/src/components/dashboard/sidebarImgs/dashboard.svg b/client/public/dashboard/sidebarImgs/dashboard.svg similarity index 100% rename from client/src/components/dashboard/sidebarImgs/dashboard.svg rename to client/public/dashboard/sidebarImgs/dashboard.svg diff --git a/client/src/components/dashboard/sidebarImgs/redirect.svg b/client/public/dashboard/sidebarImgs/redirect.svg similarity index 100% rename from client/src/components/dashboard/sidebarImgs/redirect.svg rename to client/public/dashboard/sidebarImgs/redirect.svg diff --git a/client/src/components/dashboard/sidebarImgs/settings.svg b/client/public/dashboard/sidebarImgs/settings.svg similarity index 100% rename from client/src/components/dashboard/sidebarImgs/settings.svg rename to client/public/dashboard/sidebarImgs/settings.svg diff --git a/client/src/components/dashboard/sidebarImgs/students.svg b/client/public/dashboard/sidebarImgs/students.svg similarity index 100% rename from client/src/components/dashboard/sidebarImgs/students.svg rename to client/public/dashboard/sidebarImgs/students.svg diff --git a/client/src/components/dashboard/sidebarImgs/teachers.svg b/client/public/dashboard/sidebarImgs/teachers.svg similarity index 100% rename from client/src/components/dashboard/sidebarImgs/teachers.svg rename to client/public/dashboard/sidebarImgs/teachers.svg diff --git a/client/src/components/login/logo.png b/client/public/logo.png similarity index 100% rename from client/src/components/login/logo.png rename to client/public/logo.png diff --git a/client/src/components/navbar/bookings_img.svg b/client/public/navbar/bookings_img.svg similarity index 100% rename from client/src/components/navbar/bookings_img.svg rename to client/public/navbar/bookings_img.svg diff --git a/client/src/components/navbar/dashboard_img.svg b/client/public/navbar/dashboard_img.svg similarity index 100% rename from client/src/components/navbar/dashboard_img.svg rename to client/public/navbar/dashboard_img.svg diff --git a/client/src/components/navbar/discovery_img.svg b/client/public/navbar/discovery_img.svg similarity index 100% rename from client/src/components/navbar/discovery_img.svg rename to client/public/navbar/discovery_img.svg diff --git a/client/src/components/navbar/profile_img.svg b/client/public/navbar/profile_img.svg similarity index 100% rename from client/src/components/navbar/profile_img.svg rename to client/public/navbar/profile_img.svg diff --git a/client/src/components/navbar/resources_img.svg b/client/public/navbar/resources_img.svg similarity index 100% rename from client/src/components/navbar/resources_img.svg rename to client/public/navbar/resources_img.svg diff --git a/client/src/components/profile/arrow.svg b/client/public/profile/arrow.svg similarity index 100% rename from client/src/components/profile/arrow.svg rename to client/public/profile/arrow.svg diff --git a/client/public/student.png b/client/public/student.png new file mode 100644 index 00000000..d53038a7 Binary files /dev/null and b/client/public/student.png differ diff --git a/client/public/teacher.png b/client/public/teacher.png new file mode 100644 index 00000000..81ceae34 Binary files /dev/null and b/client/public/teacher.png differ diff --git a/client/src/App.tsx b/client/src/App.tsx index 862ccc7a..09c3dc38 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -16,8 +16,8 @@ import ClassDashboard, { OverallClassDashboard, } from "./components/dashboard/classDashboard/ClassDashboard"; import ClassInfoDashboard from "./components/dashboard/classInfoDashboard/ClassInfoDashboard"; -import EventInfoDashboard from "./components/dashboard/eventInfoDashboard/EventInfoDashboard"; import { Dashboard, DashboardHome } from "./components/dashboard/Dashboard"; +import EventInfoDashboard from "./components/dashboard/eventInfoDashboard/EventInfoDashboard"; import SettingsDashboard from "./components/dashboard/settingsDashboard/SettingsDashboard"; import { StudentDashboard } from "./components/dashboard/studentDashboard/StudentDashboard"; import { StudentInfoDashboard } from "./components/dashboard/studentInfoDashboard/StudentInfoDashboard"; @@ -26,7 +26,6 @@ import { TeacherInfoDashboard } from "./components/dashboard/teacherInfoDashboar import { Discovery } from "./components/discovery/Discovery"; import { CreateEvent } from "./components/forms/createEvent"; import { Login } from "./components/login/Login"; -import { Landing } from "./components/signup/Landing"; import { L } from "./components/logout/Logout"; import { Playground } from "./components/playground/Playground"; import { Profile } from "./components/profile/Profile"; @@ -34,14 +33,15 @@ import { Settings } from "./components/profile/Settings"; import { ProtectedRoute } from "./components/ProtectedRoute"; import { Resources } from "./components/resources/Resources"; import { Reviews } from "./components/reviews/Reviews"; +import { Landing } from "./components/signup/Landing"; import { Signup } from "./components/signup/Signup"; import Request from "./components/teacher-signup/requests/Request"; import { TeacherSignup } from "./components/teacher-signup/TeacherSignup"; import { AuthProvider } from "./contexts/AuthContext"; import { BackendProvider } from "./contexts/BackendContext"; import { RoleProvider } from "./contexts/RoleContext"; -import { ForgotPasswordConfirmation } from "./utils/auth/ForgotPasswordConfirmation" import { ForgotPassword } from "./utils/auth/ForgotPassword"; +import { ForgotPasswordConfirmation } from "./utils/auth/ForgotPasswordConfirmation"; const App = () => { return ( diff --git a/client/src/components/CatchAll.tsx b/client/src/components/CatchAll.tsx index bea4831f..10a3edb0 100644 --- a/client/src/components/CatchAll.tsx +++ b/client/src/components/CatchAll.tsx @@ -6,7 +6,8 @@ export const CatchAll = () => { const navigate = useNavigate(); useEffect(() => { - navigate("/dashboard"); + // navigate("/dashboard"); + navigate("/profile"); }, [navigate]); return

Route not found... redirecting...

; diff --git a/client/src/components/ProtectedRoute.tsx b/client/src/components/ProtectedRoute.tsx index b186c633..70de22c8 100644 --- a/client/src/components/ProtectedRoute.tsx +++ b/client/src/components/ProtectedRoute.tsx @@ -20,7 +20,8 @@ export const ProtectedRoute = ({ return currentUser && isValidRole ? ( element ) : currentUser ? ( - + // + ) : ( ); diff --git a/client/src/components/bookings/Bookings.jsx b/client/src/components/bookings/Bookings.jsx index ae58a7c3..43dcc8fa 100644 --- a/client/src/components/bookings/Bookings.jsx +++ b/client/src/components/bookings/Bookings.jsx @@ -1,18 +1,14 @@ -import { memo, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { - Badge, Box, - Button, Card, CardBody, CardHeader, + Center, Flex, Heading, HStack, - Input, - InputGroup, - InputLeftElement, Modal, ModalBody, ModalContent, @@ -25,40 +21,40 @@ import { Tabs, Text, useDisclosure, - useToast, VStack, } from "@chakra-ui/react"; -import { - FaClock, - FaMapMarkerAlt, - FaMicrophoneAlt, - FaMusic, - FaSearch, - FaUser, -} from "react-icons/fa"; -import { - GiAbstract001, - GiBallerinaShoes, - GiBoombox, - GiCartwheel, - GiTambourine, -} from "react-icons/gi"; -import { MdAdd, MdArrowBackIosNew, MdMoreHoriz } from "react-icons/md"; +// import { +// FaClock, +// FaMapMarkerAlt, +// FaMicrophoneAlt, +// FaMusic, +// FaSearch, +// FaUser, +// } from "react-icons/fa"; +// import { +// GiAbstract001, +// GiBallerinaShoes, +// GiBoombox, +// GiCartwheel, +// GiTambourine, +// } from "react-icons/gi"; +import { MdArrowBackIosNew, MdMoreHoriz } from "react-icons/md"; import { useNavigate } from "react-router-dom"; import { useAuthContext } from "../../contexts/hooks/useAuthContext"; import { useBackendContext } from "../../contexts/hooks/useBackendContext"; -import { formatDate, formatTime } from "../../utils/formatDateTime"; +// import { formatDate, formatTime } from "../../utils/formatDateTime"; import { CreateClassForm } from "../forms/createClasses"; import CreateEvent from "../forms/createEvent"; import { Navbar } from "../navbar/Navbar"; +// import { InfoModal } from "./InfoModal"; import { SearchBar } from "../searchbar/SearchBar"; import { ClassCard } from "../shared/ClassCard"; import { EventCard } from "../shared/EventCard"; import { CancelModal } from "./CancelModal"; +import { ClassTeacherCard } from "./ClassTeacherCard"; import { ConfirmationModal } from "./ConfirmationModal"; -import { InfoModal } from "./InfoModal"; import { TeacherCancelModal } from "./TeacherCancelModal"; import { TeacherConfirmationModal } from "./TeacherConfirmationModal"; import { TeacherEditModal } from "./TeacherEditModal"; @@ -66,12 +62,10 @@ import { TeacherViewModal } from "./TeacherViewModal"; import { ViewModal } from "./ViewModal"; export const Bookings = () => { - const toast = useToast(); const navigate = useNavigate(); const { isOpen, onOpen, onClose } = useDisclosure(); const { currentUser, role } = useAuthContext(); const { backend } = useBackendContext(); - const [loading, setLoading] = useState(true); const [currentModal, setCurrentModal] = useState("view"); const [classes, setClasses] = useState([]); const [events, setEvents] = useState([]); @@ -89,99 +83,182 @@ export const Bookings = () => { const [coreqId, setCoreqId] = useState(); const [tags, setTags] = useState([]); const [tagFilter, setTagFilter] = useState({}); - const [lastToggledTag, setLastToggledTag] = useState(null); const [classTagsMap, setClassTagsMap] = useState({}); const [eventTagsMap, setEventTagsMap] = useState({}); - const [refresh, setRefresh] = useState(0); + const [refresh, setRefresh] = useState(-1); + const [magic, setMagic] = useState(-1); - const isTeacher = role === "teacher"; - const [activeTab, setActiveTab] = useState("classes"); + const reloadStudentClasses = async () => { + try { + const [classesRes, classTagsRes] = await Promise.all([ + backend.get(`/class-enrollments/student/${user_id}`), + backend.get(`/class-tags/enrolled-class-tags/${user_id}`), + ]); + setClasses(classesRes.data); + const newClassTagsMap = {}; + classTagsRes.data.forEach((item) => { + newClassTagsMap[item.classId] = item.tagArray; + }); + setClassTagsMap(newClassTagsMap); + } catch (error) { + console.error("Error reloading student classes:", error); + } + }; - const toggleClasses = () => { - setActiveTab("classes"); + const reloadStudentEvents = async () => { + try { + const [eventsRes, eventTagsRes] = await Promise.all([ + backend.get(`/event-enrollments/student/${user_id}`), + backend.get(`/event-tags/enrolled-event-tags/${user_id}`), + ]); + setEvents(eventsRes.data); + const newEventTagsMap = {}; + eventTagsRes.data.forEach((item) => { + newEventTagsMap[item.eventId] = item.tagArray; + }); + setEventTagsMap(newEventTagsMap); + } catch (error) { + console.error("Error reloading student events:", error); + } }; - const toggleEvents = () => { - setActiveTab("events"); + const reloadTeacherClasses = async () => { + try { + const [classesRes, classDraftsRes, classTagsRes] = await Promise.all([ + backend.get(`/classes/published`), + backend.get(`/classes/drafts`), + backend.get(`/class-tags/all-class-tags`), + ]); + setClasses(classesRes.data); + setDraftClasses(classDraftsRes.data); + const newClassTagsMap = {}; + classTagsRes.data.forEach((item) => { + newClassTagsMap[item.classId] = item.tagArray; + }); + setClassTagsMap(newClassTagsMap); + } catch (error) { + console.error("Error reloading teacher classes:", error); + } + }; + + const reloadTeacherEvents = async () => { + try { + const [eventsRes, eventDraftsRes, eventTagsRes] = await Promise.all([ + backend.get(`/events/published`), + backend.get(`/events/drafts`), + backend.get(`/event-tags/all-event-tags`), + ]); + setEvents(eventsRes.data); + setDraftEvents(eventDraftsRes.data); + const newEventTagsMap = {}; + eventTagsRes.data.forEach((item) => { + newEventTagsMap[item.eventId] = item.tagArray; + }); + setEventTagsMap(newEventTagsMap); + } catch (error) { + console.error("Error reloading teacher events:", error); + } }; useEffect(() => { + if (!role) return; if (currentUser && role !== "student") { - backend.get(`/events/published`).then((res) => setEvents(res.data)); - backend.get(`/classes/published`).then((res) => setClasses(res.data)); - backend.get(`/events/drafts`).then((res) => setDraftEvents(res.data)); - backend.get(`/classes/drafts`).then((res) => setDraftClasses(res.data)); - backend.get("/events").then((res) => setAllEvents(res.data)); + // First get all classes and events + const fetchData = async () => { + try { + // Get all the basic data + const [ + classesRes, + eventsRes, + draftEventsRes, + draftClassesRes, + allEventsRes, + ] = await Promise.all([ + backend.get(`/classes/published`), + backend.get(`/events/published`), + backend.get(`/events/drafts`), + backend.get(`/classes/drafts`), + backend.get("/events/all"), + ]); + + const allClasses = classesRes.data; + const allDraftClasses = draftClassesRes.data; + + const newClassTagsMap = {}; + const classTagsRes = await backend.get("/class-tags/all-class-tags"); + classTagsRes.data.forEach((item) => { + newClassTagsMap[item.classId] = item.tagArray; + }); + // console.log("Class Tags Map:", newClassTagsMap); + const newEventTagsMap = {}; + const eventTagsRes = await backend.get("/event-tags/all-event-tags"); + eventTagsRes.data.forEach((item) => { + newEventTagsMap[item.eventId] = item.tagArray; + }); + // console.log("Event Tags Map:", newEventTagsMap); + + // Set all the state + setClassTagsMap(newClassTagsMap); + setEventTagsMap(newEventTagsMap); + setClasses(allClasses); + setEvents(eventsRes.data); + setDraftEvents(draftEventsRes.data); + setDraftClasses(allDraftClasses); + setAllEvents(allEventsRes.data); + } catch (error) { + console.error("Error fetching data:", error); + } + }; + + fetchData(); } else if (currentUser && role === "student") { - backend - .get(`/users/${currentUser.uid}`) - .then((userRes) => { + const fetchData = async () => { + try { + const userRes = await backend.get(`/users/${currentUser.uid}`); const userId = userRes.data[0].id; setUserId(userId); - - backend - .get(`/class-enrollments/student/${userId}`) - .then(async (res) => { - const enrolledClasses = res.data; - setClasses(enrolledClasses); - - const tagsPromises = enrolledClasses.map((cls) => - backend.get(`/class-tags/tags/${cls.id}`) - ); - - const tagsResults = await Promise.all(tagsPromises); - const newClassTagsMap = {}; - - enrolledClasses.forEach((cls, index) => { - newClassTagsMap[cls.id] = tagsResults[index].data.map( - (tag) => tag.id - ); - }); - - setClassTagsMap(newClassTagsMap); - }) - .catch((err) => { - console.log("Error fetching class enrollments:", err); - }); - - backend - .get(`/event-enrollments/student/${userId}`) - .then(async (res) => { - const enrolledEvents = res.data; - setEvents(enrolledEvents); - - const tagsPromises = enrolledEvents.map((evt) => - backend.get(`/event-tags/tags/${evt.id}`) - ); - - const tagsResults = await Promise.all(tagsPromises); - const newEventTagsMap = {}; - - enrolledEvents.forEach((evt, index) => { - newEventTagsMap[evt.id] = tagsResults[index].data.map( - (tag) => tag.id - ); - }); - - setEventTagsMap(newEventTagsMap); - }) - .catch((err) => { - console.log("Error fetching event enrollments:", err); - }); - }) - .catch((err) => { - console.log("Error fetching user:", err); - }); + const [enrolledClassesRes, enrolledEventsRes] = await Promise.all([ + backend.get(`/class-enrollments/student/${userId}`), + backend.get(`/event-enrollments/student/${userId}`), + ]); + const enrolledClasses = enrolledClassesRes.data; + const enrolledEvents = enrolledEventsRes.data; + setClasses(enrolledClasses); + setEvents(enrolledEvents); + + const newClassTagsMap = {}; + const newEventTagsMap = {}; + const classTagsRes = await backend.get( + `/class-tags/enrolled-class-tags/${userId}` + ); + classTagsRes.data.forEach((item) => { + newClassTagsMap[item.classId] = item.tagArray; + }); + // console.log("Student Class Tags Map:", newClassTagsMap); + const eventTagsRes = await backend.get( + `/event-tags/enrolled-event-tags/${userId}` + ); + eventTagsRes.data.forEach((item) => { + newEventTagsMap[item.eventId] = item.tagArray; + }); + // console.log("Student Event Tags Map:", newEventTagsMap); + setClassTagsMap(newClassTagsMap); + setEventTagsMap(newEventTagsMap); + } catch (error) { + console.error("Error fetching data:", error); + } + }; + fetchData(); } - }, [backend, currentUser, isTeacher, refresh]); + }, [backend, currentUser, refresh, role]); useEffect(() => { const attendedClasses = classes.filter((c) => c.attendance !== null); const attendedEvents = events.filter((e) => e.attendance !== null); setAttended([...attendedClasses, ...attendedEvents]); setDrafts([...draftClasses, ...draftEvents]); - }, [classes, events]); + }, [classes, events, draftClasses, draftEvents]); useEffect(() => { const fetchTags = async () => { @@ -197,15 +274,14 @@ export const Bookings = () => { setTagFilter(initialTagFilter); setTags(initialTags); - // console.log(initialTags); } catch (error) { console.error("Error fetching tags:", error); } }; fetchTags(); - }, []); + }, [backend]); - const handleFilterToggle = (id) => { + const handleFilterToggle = (id) => () => { setTagFilter((prev) => ({ ...prev, [id]: !prev[id], @@ -222,10 +298,8 @@ export const Bookings = () => { if (data.length > 0) { const eventId = data[0].eventId; - console.log("Fetched coreqId:", eventId); setCoreqId(eventId); } else { - console.log("No corequisite found for class:", selectedCard.id); setCoreqId(null); } } catch (err) { @@ -238,15 +312,11 @@ export const Bookings = () => { }, [backend, selectedCard, isOpen]); const onCloseModal = (type) => { - setSelectedCard(null); - setCurrentModal("view"); - onClose(); - reloadClassesAndDrafts(); if (type !== 3) { toast({ - title: type == 0 ? "Class Published." : "Event Published.", // 0 == "class" || 1 == "event" + title: tabIndex === 0 ? "Class Published." : "Event Published.", // 0 == "class" || 1 == "event" description: - type == 0 + type === 0 ? "Class is visible to students." : "Event is visible to students.", status: "success", @@ -255,55 +325,162 @@ export const Bookings = () => { position: "top", }); } + if (tabIndex === 0) { + if (role !== "student") { + Promise.all([reloadTeacherClasses()]).then(() => { + setCurrentModal("view"); + onClose(); + }); + } else { + Promise.all([reloadStudentClasses()]).then(() => { + setCurrentModal("view"); + onClose(); + }); + } + } else if (tabIndex === 1) { + if (role !== "student") { + Promise.all([reloadTeacherEvents()]).then(() => { + setSelectedCard(null); + setCurrentModal("view"); + onClose(); + }); + } else { + Promise.all([reloadStudentEvents()]).then(() => { + setSelectedCard(null); + setCurrentModal("view"); + onClose(); + }); + } + } else { + if (role !== "student") { + Promise.all([reloadTeacherClasses(), reloadTeacherEvents()]).then( + () => { + setSelectedCard(null); + setCurrentModal("view"); + onClose(); + } + ); + } else { + Promise.all([reloadStudentClasses(), reloadStudentEvents()]).then( + () => { + setSelectedCard(null); + setCurrentModal("view"); + onClose(); + } + ); + } + } + console.log(classTagsMap); }; - const onOpenModal = (data) => { - setClassData(data); - onOpen(); - }; - - const triggerRefresh = () => { - setRefresh(refresh + 1); - console.log("Refresh triggered"); - }; - // https://dmitripavlutin.com/how-to-compare-objects-in-javascript/#4-deep-equality - const deepEquality = (object1, object2) => { - if (object1 === null || object2 === null) return object1 === object2; - const keys1 = Object.keys(object1); - const keys2 = Object.keys(object2); - if (keys1.length !== keys2.length) { - return false; + const onCloseEditModal = () => { + if (tabIndex === 0) { + if (role !== "student") { + Promise.all([reloadTeacherClasses()]).then(() => { + setCurrentModal("view"); + setMagic((prev) => -1 * prev); + }); + } else { + Promise.all([reloadStudentClasses()]).then(() => { + setCurrentModal("view"); + setMagic((prev) => -1 * prev); + }); + } + } else if (tabIndex === 1) { + if (role !== "student") { + Promise.all([reloadTeacherEvents()]).then(() => { + setCurrentModal("view"); + setMagic((prev) => -1 * prev); + }); + } else { + Promise.all([reloadStudentEvents()]).then(() => { + setCurrentModal("view"); + setMagic((prev) => -1 * prev); + }); + } + } else { + if (role !== "student") { + Promise.all([reloadTeacherClasses(), reloadTeacherEvents()]).then( + () => { + setCurrentModal("view"); + setMagic((prev) => -1 * prev); + } + ); + } else { + Promise.all([reloadStudentClasses(), reloadStudentEvents()]).then( + () => { + setCurrentModal("view"); + setMagic((prev) => -1 * prev); + } + ); + } } + }; - for (const key of keys1) { - const val1 = object1[key]; - const val2 = object2[key]; - const areObjects = typeof val1 === "object" && typeof val2 === "object"; - if ( - (areObjects && !deepEquality(val1, val2)) || - (!areObjects && val1 !== val2) - ) { - return false; + const triggerRefresh = () => { + if (tabIndex === 0) { + if (role !== "student") { + Promise.all([reloadTeacherClasses()]).then(() => { + setRefresh((prev) => -1 * prev); + }); + } else { + Promise.all([reloadStudentClasses()]).then(() => { + setRefresh((prev) => -1 * prev); + }); + } + } else if (tabIndex === 1) { + if (role !== "student") { + Promise.all([reloadTeacherEvents()]).then(() => { + setRefresh((prev) => -1 * prev); + }); + } else { + Promise.all([reloadStudentEvents()]).then(() => { + setRefresh((prev) => -1 * prev); + }); + } + } else { + if (role !== "student") { + Promise.all([reloadTeacherClasses(), reloadTeacherEvents()]).then( + () => { + setRefresh((prev) => -1 * prev); + } + ); + } else { + Promise.all([reloadStudentClasses(), reloadStudentEvents()]).then( + () => { + setRefresh((prev) => -1 * prev); + } + ); } } - - return true; }; - const updateModal = (item) => { - const type = - classes.some((e) => deepEquality(e, item)) || - draftClasses.some((e) => deepEquality(e, item)) - ? "class" - : "event"; - console.log( - "update", - item, - classes, - draftClasses, - type, - deepEquality(classes[0], item) - ); + // https://dmitripavlutin.com/how-to-compare-objects-in-javascript/#4-deep-equality + // const deepequality = (object1, object2) => { + // if (object1 === null || object2 === null) return object1 === object2; + // const keys1 = Object.keys(object1); + // const keys2 = Object.keys(object2); + + // if (keys1.length !== keys2.length) { + // return false; + // } + + // for (const key of keys1) { + // const val1 = object1[key]; + // const val2 = object2[key]; + // const areObjects = typeof val1 === "object" && typeof val2 === "object"; + // if ( + // (areObjects && !deepEquality(val1, val2)) || + // (!areObjects && val1 !== val2) + // ) { + // return false; + // } + // } + + // return true; + // }; + + const updateModal = (item, type = "class") => { if (type === "class") loadCorequisites(item.id); setSelectedCard(item); setCardType(type); @@ -346,60 +523,84 @@ export const Bookings = () => { } }; - // const isFilterActive = Object.values(tagFilter).some(Boolean); - const loadCorequisites = async (classId) => { try { const response = await backend.get(`classes/corequisites/${classId}`); if (response.status === 200) { setCoEvents(response.data); - console.log(coEvents); } } catch (error) { console.error("Error fetching corequisite enrollment:", error); } }; - const handleClassSearch = async (query) => { - if (currentUser && role === "student") { - // For students, search within their enrolled classes - const enrolledRes = await backend.get( - `/class-enrollments/student/${user_id}` - ); - const allEnrolledClasses = enrolledRes.data; + try { + if (!query || query.trim() === "") { + // For empty search, reload the appropriate data based on role + if (role === "student") { + await reloadStudentClasses(); + } else { + await reloadTeacherClasses(); + } + return; + } - // Client-side search filtering - const filteredClasses = allEnrolledClasses.filter((cls) => - cls.title.toLowerCase().includes(query.toLowerCase()) - ); + if (currentUser && role === "student") { + // For students, search within their enrolled classes + const enrolledRes = await backend.get( + `/class-enrollments/student/${user_id}` + ); + const allEnrolledClasses = enrolledRes.data; - setClasses(filteredClasses); - } else { - // For teachers/admin, keep the existing server-side search - const searchRes = await backend.get(`/classes/search/${query}`); - setClasses(searchRes.data); + // Client-side search filtering + const filteredClasses = allEnrolledClasses.filter((cls) => + cls.title.toLowerCase().includes(query.trim().toLowerCase()) + ); + + setClasses(filteredClasses); + } else { + // For teachers/admin, use server-side search + const searchRes = await backend.get(`/classes/search/${query.trim()}`); + setClasses(searchRes.data); + } + } catch (error) { + console.error("Error searching classes:", error); } }; const handleEventSearch = async (query) => { - if (currentUser && role === "student") { - // For students, search within their enrolled events - const enrolledRes = await backend.get( - `/event-enrollments/student/${user_id}` - ); - const allEnrolledEvents = enrolledRes.data; + try { + if (!query || query.trim() === "") { + // For empty search, reload the appropriate data based on role + if (role === "student") { + await reloadStudentEvents(); + } else { + await reloadTeacherEvents(); + } + return; + } - // Client-side search filtering - const filteredEvents = allEnrolledEvents.filter((evt) => - evt.title.toLowerCase().includes(query.toLowerCase()) - ); + if (currentUser && role === "student") { + // For students, search within their enrolled events + const enrolledRes = await backend.get( + `/event-enrollments/student/${user_id}` + ); + const allEnrolledEvents = enrolledRes.data; - setEvents(filteredEvents); - } else { - // For teachers/admin, keep the existing server-side search - const searchRes = await backend.get(`/events/search/${query}`); - setEvents(searchRes.data); + // Client-side search filtering + const filteredEvents = allEnrolledEvents.filter((evt) => + evt.title.toLowerCase().includes(query.trim().toLowerCase()) + ); + + setEvents(filteredEvents); + } else { + // For teachers/admin, use server-side search + const searchRes = await backend.get(`/events/search/${query.trim()}`); + setEvents(searchRes.data); + } + } catch (error) { + console.error("Error searching events:", error); } }; @@ -417,164 +618,61 @@ export const Bookings = () => { setAttended([...attendedClasses, ...attendedEvents]); setDrafts([...draftClasses, ...draftEvents]); if (selectedCard) loadCorequisites(selectedCard.id); + // console.log(attended); } catch (error) { console.error("Error reloading classes:", error); } }; - const reloadClasses = async () => { - await backend.get(`/classes/published`).then((res) => { - setClasses(res.data); - }); - - const attendedClasses = classes.filter((c) => c.attendance !== null); - const attendedEvents = events.filter((e) => e.attendance !== null); - setAttended([...attendedClasses, ...attendedEvents]); - - if (selectedCard) { - loadCorequisites(selectedCard.id); - } - }; - - const reloadEvents = async () => { - await backend.get(`/events/published`).then((res) => { - setEvents(res.data); - }); - - const attendedClasses = classes.filter((c) => c.attendance !== null); - const attendedEvents = events.filter((e) => e.attendance !== null); - setAttended([...attendedClasses, ...attendedEvents]); - - if (selectedCard) { - loadCorequisites(selectedCard.id); - } - }; - - // useEffect(() => { - // console.log("selectedCard", selectedCard); - // }, [selectedCard]); - - const fetchClassData = async () => { - try { - const [classesResponse, classDataResponse] = await Promise.all([ - backend.get("/scheduled-classes"), - backend.get("/classes"), - ]); - - const classDataDict = new Map(); - classDataResponse.data.forEach((cls) => classDataDict.set(cls.id, cls)); - - console.log("Fetching tags for class:", clsId); - const response = await backend.get(`/class-tags/tags/${clsId}`); - console.log("Raw tag data:", response.data); - - const formattedData = classesResponse.data - .map((cls) => { - const fullData = classDataDict.get(cls.classId); - - return fullData - ? { - classId: cls.classId, - date: stringToDate(cls.date), - startTime: stringToTime(cls.startTime), - endTime: stringToTime(cls.endTime), - title: fullData.title, - description: fullData.description, - location: fullData.location, - capacity: fullData.capacity, - level: fullData.level, - costume: fullData.costume, - isDraft: fullData.isDraft, - } - : null; - }) - .filter(Boolean); - - setClasses(formattedData); - } catch (error) { - console.error("Error fetching class data:", error); - } - }; - - const stringToDate = (date) => { - return new Date(date); - }; - - const stringToTime = (time) => { - const [hours, minutes] = time.split(":"); - const d = new Date(); - d.setHours(hours, minutes, 0); - - return d; - }; - return ( - - + setTabIndex(index)} > - - - Classes - - - Events - - - {role !== "student" ? "Drafts" : "Attended"} - - - {/* - - - - - setSearchInput(e.target.value)} - onKeyDown={handleKeyDown} - /> - - */} +
+ + + Classes + + + Events + + + {role !== "student" ? "Drafts" : "Attended"} + + +
+ { > {role !== "student" && ( { setSelectedCard(null); @@ -620,28 +719,43 @@ export const Bookings = () => { )} {role !== "student" ? ( classes.length > 0 ? ( - classes.map((classItem, index) => ( - - )) + classes.map((classItem, index) => { + const isFilterActive = + Object.values(tagFilter).some(Boolean); + const classTags = classTagsMap[classItem.id] || []; + + if ( + !isFilterActive || + classTags.some((tag) => tagFilter[tag.id]) + ) { + return ( + { + updateModal(classItem, "class"); + }} + tags={classTags} + /> + ); + } + return null; + }) ) : ( No classes available. ) ) : classes.length > 0 ? ( classes.map((classItem) => { - const isFilterActive = - Object.values(tagFilter).some(Boolean); const classTags = classTagsMap[classItem.id] || []; if ( - !isFilterActive || - classTags.some((tagId) => tagFilter[tagId]) + !Object.values(tagFilter).some(Boolean) || + classTags.some((tag) => tagFilter[tag.id]) ) { return ( { > updateModal(classItem)} + onClick={() => { + updateModal(classItem, "class"); + }} triggerRefresh={triggerRefresh} onCloseModal={onCloseModal} + tags={classTags} /> ); @@ -683,7 +800,8 @@ export const Bookings = () => { > {role !== "student" && ( { setSelectedCard(null); @@ -708,24 +826,26 @@ export const Bookings = () => { - )} + )}{" "} {events.length > 0 ? ( events.map((eventItem) => { - const isFilterActive = - Object.values(tagFilter).some(Boolean); const eventTags = eventTagsMap[eventItem.id] || []; if ( - !isFilterActive || - eventTags.some((tagId) => tagFilter[tagId]) + !Object.values(tagFilter).some(Boolean) || + eventTags.some((tag) => tagFilter[tag.id]) ) { return ( updateModal(eventItem)} + onClick={() => { + updateModal(eventItem, "event"); + }} + magic={refresh} triggerRefresh={triggerRefresh} onCloseModal={onCloseModal} + tags={eventTags} /> ); } @@ -745,56 +865,78 @@ export const Bookings = () => { mb={20} justifyContent={"center"} > + {" "} {role !== "student" ? ( drafts.length > 0 ? ( - drafts.map((item) => - !item.callTime ? ( - updateModal(item)} - /> - ) : ( - updateModal(item)} - triggerRefresh={triggerRefresh} - onCloseModal={onCloseModal} - // setRefresh={reloadClassesAndDrafts} - /> - ) - ) + drafts.map((item) => { + const classTags = classTagsMap[item.id] || []; + + if (!item.callTime) { + return ( + updateModal(item, "class")} + setSelectedCard={setSelectedCard} + performance={coEvents} + onOpen={updateModal} + tags={classTags} + /> + ); + } else if (item.callTime) { + return ( + { + updateModal(item, "event"); + }} + magic={refresh} + triggerRefresh={triggerRefresh} + onCloseModal={onCloseModal} + tags={eventTagsMap[item.id] || []} + /> + ); + } + return null; + }) ) : ( No draft events or classes ) ) : attended.length > 0 ? ( - attended.map((item) => - item.class_id ? ( + attended.map((item) => { + return !item.callTime ? ( updateModal(item)} + onClick={() => { + updateModal(item, "class"); + }} + tags={classTagsMap[item.id] || []} /> ) : ( updateModal(item)} + onClick={() => { + updateModal(item, "event"); + }} + magic={refresh} triggerRefresh={triggerRefresh} + tags={eventTagsMap[item.id] || []} /> - ) - ) + ); + }) ) : ( No attended classes or events. )} @@ -802,306 +944,114 @@ export const Bookings = () => {
-
- {role !== "student" ? ( + {role !== "student" ? ( + currentModal === "view" ? ( + // this is always going to be the view for classes + // events view modal is handled in the event card component + + ) : currentModal === "confirmation" ? ( + + ) : currentModal === "edit" ? ( + + ) : currentModal === "create" ? ( + + + + + + onCloseModal(3)} /> + + {tabIndex === 0 ? "New Class" : "New Event"} + {" "} + {/* Will add from prop */} + + + + + {tabIndex === 0 ? ( + + ) : ( + + )} + + + + ) : ( + + ) + ) : // STUDENT VIEW HERE currentModal === "view" ? ( - ) : currentModal === "confirmation" ? ( - - ) : currentModal === "edit" ? ( - - ) : currentModal === "create" ? ( - - - - - - onCloseModal(3)} /> - - {tabIndex === 0 ? "New Class" : "New Event"} - {" "} - {/* Will add from prop */} - - - - - {tabIndex === 0 ? ( - - ) : ( - - )} - - - ) : ( - handleCancelEnrollment(selectedCard.id)} + type={cardType} /> - ) - ) : // STUDENT VIEW HERE - currentModal === "view" ? ( - - ) : currentModal === "confirmation" ? ( - - ) : ( - handleCancelEnrollment(selectedCard.id)} - type={cardType} - /> - )} + )} +
); }; - -const ClassTeacherCard = memo( - ({ - id, - title, - location, - date, - description, - capacity, - level, - costume, - performance, - rsvpCount, - isDraft, - recurrencePattern, - isRecurring, - startDate, - endDate, - startTime, - endTime, - navigate, - setSelectedCard, - tagId, - onOpen, - }) => { - const formattedDate = date ? formatDate(date) : null; - const formattedStartTime = startTime ? formatTime(startTime) : null; - const formattedEndTime = endTime ? formatTime(endTime) : null; - const getIcon = () => { - const iconSize = 50; - switch (tagId) { - case 1: - return ; - case 2: - return ; - case 3: - return ; - case 4: - return ; - case 5: - return ; - case 6: - return ; - case 7: - return ; - default: - return ; - } - }; - return ( - - { - const modalData = { - id, - title, - location, - date, - description, - capacity, - level, - costume, - performances: performance, - isRecurring, - recurrencePattern, - startDate, - endDate, - isDraft, - rsvpCount, - startTime, - endTime, - }; - setSelectedCard(modalData); - onOpen({ - id, - title, - location, - date, - description, - capacity, - isRecurring, - recurrencePattern, - startDate, - endDate, - level, - costume, - isDraft, - startTime, - endTime, - }); - } - : () => { - const modalData = { - id, - title, - location, - date, - description, - capacity, - level, - costume, - isRecurring, - recurrencePattern, - performances: performance, - isDraft, - startDate, - endDate, - rsvpCount, - startTime, - endTime, - }; - setSelectedCard(modalData); - onOpen({ - id, - title, - location, - date, - description, - capacity, - level, - isRecurring, - recurrencePattern, - costume, - startDate, - endDate, - isDraft, - startTime, - endTime, - }); - } - // : () => navigate(`/dashboard/classes/${classId}`) - } - > - - - - {rsvpCount ?? 0} {(rsvpCount ?? 0) === 1 ? "Person" : "People"}{" "} - Enrolled - - - - {getIcon()} - - - {title} - - - - - {location ? `${location}` : "No location"} - - - - - {formattedDate - ? `${formattedDate} · ${formattedStartTime} - ${formattedEndTime}` - : "No date"} - - - - - - - - ); - } -); diff --git a/client/src/components/bookings/CancelModal.jsx b/client/src/components/bookings/CancelModal.jsx index 06930e25..bf8d0039 100644 --- a/client/src/components/bookings/CancelModal.jsx +++ b/client/src/components/bookings/CancelModal.jsx @@ -1,51 +1,93 @@ -import { Button, Modal, ModalOverlay, ModalHeader, ModalContent, ModalBody, ModalFooter, Flex, Text } from "@chakra-ui/react"; +import { + Button, + Flex, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Text, +} from "@chakra-ui/react"; -export const CancelModal = ({ isOpen, onClose, setCurrentModal, card, handleEvent, type }) => { - const onGoBack = () => { - setCurrentModal("view"); - }; - const onConfirm = () => { - setCurrentModal("confirmation"); - handleEvent(); // sql query is dependent on card type (class or event) - }; +export const CancelModal = ({ + isOpen, + onClose, + setCurrentModal, + card, + handleEvent, + type, +}) => { + const onGoBack = () => { + setCurrentModal("view"); + }; + const onConfirm = () => { + setCurrentModal("confirmation"); + handleEvent(); // sql query is dependent on card type (class or event) + }; - return ( - - - - Delete RSVP? - - You're cancelling {card ? card.title : "N/A"}. This action can't be undone. - + return ( + + + + + {" "} + Delete RSVP? + + + You're cancelling{" "} + + {card ? card.title : "N/A"} + + . This action can't be undone. + - - - - - - - - - + + + + + + + + ); }; diff --git a/client/src/components/bookings/ClassTeacherCard.jsx b/client/src/components/bookings/ClassTeacherCard.jsx new file mode 100644 index 00000000..bbf0dd29 --- /dev/null +++ b/client/src/components/bookings/ClassTeacherCard.jsx @@ -0,0 +1,185 @@ +import { memo, useEffect, useState } from "react"; + +import { + Badge, + Box, + Card, + Flex, + Heading, + HStack, + Image, + Text, + VStack, +} from "@chakra-ui/react"; + +import { FaMicrophoneAlt, FaMusic } from "react-icons/fa"; +import { + GiAbstract001, + GiBallerinaShoes, + GiBoombox, + GiCartwheel, + GiTambourine, +} from "react-icons/gi"; + +import { useAuthContext } from "../../contexts/hooks/useAuthContext"; +import { useBackendContext } from "../../contexts/hooks/useBackendContext"; +import { formatDate, formatTime } from "../../utils/formatDateTime"; + +export const ClassTeacherCard = memo( + ({ + id, + title, + location, + date, + description, + capacity, + level, + costume, + performance, + attendeeCount = 0, + isDraft, + recurrencePattern, + isRecurring, + startDate, + endDate, + startTime, + endTime, + navigate, + setSelectedCard, + tags, + onOpen, + }) => { + const [openTeacherModal, setOpenTeacherModal] = useState(false); + + // const closeTeacherModal = () => { + // setOpenTeacherModal(false); + // }; + + const handleClickModal = () => { + const modalData = { + id, + title, + location, + date, + description, + capacity, + level, + costume, + performances: performance, + isDraft, + recurrencePattern, + isRecurring, + startDate, + endDate, + attendeeCount, + startTime, + endTime, + }; + setOpenTeacherModal(true); + setSelectedCard(modalData); + onOpen(modalData); + }; + + const formattedDate = date ? formatDate(date) : null; + const formattedStartTime = startTime ? formatTime(startTime) : null; + const formattedEndTime = endTime ? formatTime(endTime) : null; + const getIcon = () => { + const iconSize = 50; + switch (tags[0]?.id) { + case 1: + return ; + case 2: + return ; + case 3: + return ; + case 4: + return ; + case 5: + return ; + case 6: + return ; + case 7: + return ; + default: + return ; + } + }; + + return ( + + + {attendeeCount} {attendeeCount === 1 ? "Person" : "People"} RSVP'd + + + + {getIcon()} + + + + {title} + + + {location} + + + {formattedDate} · {formattedStartTime} – {formattedEndTime} + + + + + ); + } +); diff --git a/client/src/components/bookings/CompletedIndicator.png b/client/src/components/bookings/CompletedIndicator.png deleted file mode 100644 index 9310fc72..00000000 Binary files a/client/src/components/bookings/CompletedIndicator.png and /dev/null differ diff --git a/client/src/components/bookings/ConfirmationModal.jsx b/client/src/components/bookings/ConfirmationModal.jsx index 38595707..c297b469 100644 --- a/client/src/components/bookings/ConfirmationModal.jsx +++ b/client/src/components/bookings/ConfirmationModal.jsx @@ -1,38 +1,68 @@ -import { Button, Modal, ModalOverlay, ModalHeader, ModalContent, ModalCloseButton, ModalBody, ModalFooter, Box, Icon, VStack } from "@chakra-ui/react"; -import CompletedIndicator from "./CompletedIndicator.png" - +import { + Box, + Button, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalOverlay, + Text, + VStack, +} from "@chakra-ui/react"; +import { MdCheckCircle } from "react-icons/md"; export const ConfirmationModal = ({ isOpen, onClose, card }) => { return ( - - - - - - - - Completed Indicator - + {}} + > + + + + + + + + + + Sorry to See You Go! + + + Your RSVP has been removed for {card ? card.title : "N/A"} + - - You've successfully cancelled {card ? card.title : "N/A"}. - - - - - - + + + + + + ); }; + +export default ConfirmationModal; diff --git a/client/src/components/bookings/InfoModal.jsx b/client/src/components/bookings/InfoModal.jsx index 434ee57b..7e9b79d8 100644 --- a/client/src/components/bookings/InfoModal.jsx +++ b/client/src/components/bookings/InfoModal.jsx @@ -12,8 +12,8 @@ import { ModalOverlay, Text, } from "@chakra-ui/react"; -import { formatDate } from "../../utils/formatDateTime"; +import { formatDate } from "../../utils/formatDateTime"; export const InfoModal = ({ isOpen, diff --git a/client/src/components/bookings/TeacherCancelModal.jsx b/client/src/components/bookings/TeacherCancelModal.jsx index 9949a6fd..fc23f363 100644 --- a/client/src/components/bookings/TeacherCancelModal.jsx +++ b/client/src/components/bookings/TeacherCancelModal.jsx @@ -38,6 +38,7 @@ export const TeacherCancelModal = ({ status: "error", duration: 4000, isClosable: true, + position: "top", }); } }; @@ -51,16 +52,36 @@ export const TeacherCancelModal = ({ Delete class? - You are deleting {classData.title}. This action can't be undone. + + You are deleting {classData.title}. This action can't be undone. + - - - diff --git a/client/src/components/bookings/TeacherConfirmationModal.jsx b/client/src/components/bookings/TeacherConfirmationModal.jsx index 8a5cd486..eab021ff 100644 --- a/client/src/components/bookings/TeacherConfirmationModal.jsx +++ b/client/src/components/bookings/TeacherConfirmationModal.jsx @@ -1,23 +1,57 @@ -import { Icon, Button, Modal, ModalOverlay, ModalContent, ModalFooter, Flex, Text, VStack } from "@chakra-ui/react"; +import { + Button, + Flex, + Icon, + Modal, + ModalContent, + ModalFooter, + ModalOverlay, + Text, + VStack, +} from "@chakra-ui/react"; import { BsCheck } from "react-icons/bs"; export const TeacherConfirmationModal = ({ isOpen, onClose }) => { return ( - + - - + + - Class Deleted - - - + + Class Deleted + + + + ); -}; \ No newline at end of file +}; diff --git a/client/src/components/bookings/TeacherEditModal.jsx b/client/src/components/bookings/TeacherEditModal.jsx index 57017a61..f3778e06 100644 --- a/client/src/components/bookings/TeacherEditModal.jsx +++ b/client/src/components/bookings/TeacherEditModal.jsx @@ -66,6 +66,9 @@ export const TeacherEditModal = ({ performances, setRefresh, coreqId, + tags = [], + magic, + setMagic, }) => { const { backend } = useBackendContext(); const [isPublishing, setIsPublishing] = useState(false); @@ -77,24 +80,41 @@ export const TeacherEditModal = ({ classData?.instructor ?? "" ); const formRef = useRef(null); - const [tags, setTags] = useState([]); + const [tagTypes, setTagTypes] = useState([]); const toast = useToast(); - const [classType, setClassType] = useState(classData?.classType ?? "1"); + console.log("type", tags[0]?.id); + const [classType, setClassType] = useState(tags[0]?.id ?? "-1"); - useMemo(() => { - if (backend) { - backend.get("/tags").then((response) => { - setTags(response.data); - }); - backend.get("/teachers/activated").then((response) => { - setTeachers(response.data); - }); + useEffect(() => { + if (backend && classData?.id) { + Promise.all([ + backend.get("/tags"), + backend.get("/teachers/activated"), + backend.get(`/classes-taught/instructor/${classData.id}`), + ]) + .then( + ([tagResponse, activatedTeachersResponse, instructorResponse]) => { + setTagTypes(tagResponse.data); + setTeachers(activatedTeachersResponse.data); + const teacher = instructorResponse.data; + if (teacher && Array.isArray(teacher) && teacher[0]) { + setSelectedInstructor(teacher[0]?.id); + } else { + setSelectedInstructor(""); + } + } + ) + .catch((error) => { + console.log("Error fetching data:", error); + }); } - }, [backend]); + }, [backend, classData?.id]); + const onBack = () => { setCurrentModal("view"); }; + const onSave = async (draft) => { try { // PUT request to save class data @@ -111,26 +131,30 @@ export const TeacherEditModal = ({ is_recurring: recurrencePattern !== "none", isDraft: draft, }; - console.log(classData); + // console.log(classData); await backend.put(`/classes/${classData.id}`, updatedData); - console.log("Updating class", classData.id, updatedData); + // console.log("Updating class", classData.id, updatedData); await backend.delete(`/corequisites/class/${classData.id}`); - await backend.put(`/classes-taught`, { - classId: classData.id, - teacherId: selectedInstructor, - }); - console.log("Updating instructor", classData.id, selectedInstructor); - if (performanceId) { + if (selectedInstructor && parseInt(selectedInstructor) !== -1) { + await backend.put(`/classes-taught`, { + classId: classData.id, + teacherId: selectedInstructor, + }); + } else { + await backend.delete(`/classes-taught/${classData.id}`); + } + // console.log("Updating instructor", classData.id, selectedInstructor); + if (performanceId && parseInt(performanceId) !== -1) { await backend.put( `/corequisites/${classData.id}/${performanceId}`, updatedData ); - console.log( - "Updating corequisites", - classData.id, - performanceId, - updatedData - ); + // console.log( + // "Updating corequisites", + // classData.id, + // performanceId, + // updatedData + // ); } if (recurrencePattern !== "none") { @@ -140,7 +164,7 @@ export const TeacherEditModal = ({ stringToDate(endDate), recurrencePattern ); - console.log("classdates", classDates); + // console.log("classdates", classDates); for (const classDate of classDates) { if (classDate && startTime && endTime) { const scheduledClassBody = { @@ -182,7 +206,14 @@ export const TeacherEditModal = ({ isDraft: draft, })); - if (classType !== "") { + if (classData?.id && classType !== "-1") { + tags.map(async (tag) => { + await backend + .delete(`/class-tags/${classData.id}/${tag.id}`) + .catch((err) => { + console.error(err); + }); + }); await backend .post("/class-tags", { classId: classData.id, @@ -230,7 +261,8 @@ export const TeacherEditModal = ({ const [classTitle, setClassTitle] = useState(classData?.title); const [location, setLocation] = useState(classData?.location); const [startDate, setStartDate] = useState( - isDefaultDate(classData?.startDate) || isDefaultDate(classData?.date) + (isDefaultDate(classData?.startDate) && !classData?.date) || + (isDefaultDate(classData?.date) && !classData?.startDate) ? "" : classData?.startDate ? formatDate(classData.startDate) @@ -270,9 +302,6 @@ export const TeacherEditModal = ({ const onStartTimeChange = (e) => setStartTime(e.target.value); const onEndTimeChange = (e) => setEndTime(e.target.value); - useEffect(() => { - console.log(); - }, []); return ( setSelectedInstructor(e.target.value)} maxWidth="300px" bg="white" - color="black" + color={selectedInstructor === "" ? "gray.400" : "black"} + sx={{ + "& option": { + color: "black", + backgroundColor: "white", + }, + "& option[disabled]": { + color: "gray.400", + }, + }} > {teachers.map((teacher) => ( + + ClassType + + Performances setTitle(e.target.value)} - bg="white" - color="black" - /> - {isDraft && validationSchema.title && ( - - Class title is required - - )} - - - - Location - setLocation(e.target.value)} - bg="white" - color="black" - boxShadow="sm" - /> - - - { + e.preventDefault(); + if (displayToast()) { + toast({ + title: "Class Not Published", + description: "Fill out missing fields.", + status: "error", + duration: 9000, + isClosable: true, + position: "top", + }); + } else { + onConfirmationOpen(); + } + }} > - - Start Date + + + Class Title + setDate(e.target.value)} + boxShadow="sm" + type="text" + value={title} + onChange={(e) => setTitle(e.target.value)} bg="white" color="black" - boxShadow="sm" - sx={{ - "&::-webkit-calendar-picker-indicator": { - backgroundColor: "gray.100", - padding: "2px", - borderRadius: "4px", - cursor: "pointer", - }, - }} /> + {isDraft && validationSchema.title && ( + + Class title is required + + )} - - End Date + + Location setEndDate(e.target.value)} - min={date} - isDisabled={recurrencePattern === "none"} - opacity={recurrencePattern === "none" ? 0.4 : 1} + borderColor="gray.200" + type="text" + required + value={location} + onChange={(e) => setLocation(e.target.value)} bg="white" color="black" boxShadow="sm" /> - - - - Start Time - + + Start Date + setDate(e.target.value)} + bg="white" + color={date === "" ? "gray.400" : "black"} + boxShadow="sm" + sx={{ + "&::-webkit-calendar-picker-indicator": { + backgroundColor: "gray.100", + padding: "2px", + borderRadius: "4px", + cursor: "pointer", + }, + }} + /> + + + + End Date + setEndDate(e.target.value)} + min={date} + isDisabled={recurrencePattern === "none"} + opacity={recurrencePattern === "none" ? 0.4 : 1} + bg="white" + color={endDate === "" ? "gray.400" : "black"} + boxShadow="sm" + /> + + + + + + Start Time + setStartTime(e.target.value)} + bg="white" + color={startTime === "" ? "gray.400" : "black"} + /> + + + + End Time + setEndTime(e.target.value)} + bg="white" + color={endTime === "" ? "gray.400" : "black"} + /> + + + + + Recurrence + - - End Time - + Instructor + + + + + Description +