From 29178dbad0592b608fb05cedb67a7bcb43fe1ad1 Mon Sep 17 00:00:00 2001 From: Ethan Ho Date: Tue, 10 Jun 2025 13:17:52 -0700 Subject: [PATCH] merge dev into toast branch --- .../{_workflows => workflows}/run-checks.yml | 10 +- client/package.json | 5 +- client/public/card_images/ballet.svg | 38 + client/public/card_images/classical.svg | 35 + client/public/card_images/pop.svg | 2 + client/public/card_images/pop2.svg | 79 + .../dashboard/cseLogo.png | Bin .../dashboard/sidebarImgs/classes.svg | 0 .../dashboard/sidebarImgs/dashboard.svg | 0 .../dashboard/sidebarImgs/redirect.svg | 0 .../dashboard/sidebarImgs/settings.svg | 0 .../dashboard/sidebarImgs/students.svg | 0 .../dashboard/sidebarImgs/teachers.svg | 0 .../{src/components/login => public}/logo.png | Bin .../navbar/bookings_img.svg | 0 .../navbar/dashboard_img.svg | 0 .../navbar/discovery_img.svg | 0 .../navbar/profile_img.svg | 0 .../navbar/resources_img.svg | 0 .../components => public}/profile/arrow.svg | 0 client/public/student.png | Bin 0 -> 20555 bytes client/public/teacher.png | Bin 0 -> 11518 bytes client/src/App.tsx | 6 +- client/src/components/CatchAll.tsx | 3 +- client/src/components/ProtectedRoute.tsx | 3 +- client/src/components/bookings/Bookings.jsx | 1302 ++++++++--------- .../src/components/bookings/CancelModal.jsx | 134 +- .../components/bookings/ClassTeacherCard.jsx | 185 +++ .../bookings/CompletedIndicator.png | Bin 3437 -> 0 bytes .../components/bookings/ConfirmationModal.jsx | 90 +- client/src/components/bookings/InfoModal.jsx | 2 +- .../bookings/TeacherCancelModal.jsx | 33 +- .../bookings/TeacherConfirmationModal.jsx | 54 +- .../components/bookings/TeacherEditModal.jsx | 124 +- .../components/bookings/TeacherViewModal.jsx | 598 ++++---- client/src/components/bookings/ViewModal.jsx | 260 +++- .../teacherView/DeleteConfirmModal.jsx | 108 +- .../teacherView/TeacherEventViewModal.jsx | 166 ++- .../teacherView/qrcode/CheckInHandler.jsx | 17 - .../qrcode/ClassCheckInHandler.jsx | 59 +- .../qrcode/EventCheckInHandler.jsx | 29 +- .../bookings/teacherView/qrcode/QRCode.jsx | 7 +- client/src/components/dashboard/Dashboard.tsx | 31 +- .../dashboard/NavigationSidebar.jsx | 30 +- .../dashboard/NotificationPanel.jsx | 134 +- .../src/components/dashboard/RoleSelect.tsx | 2 + .../dashboard/TeacherNotification.jsx | 6 +- .../classDashboard/ClassDashboard.tsx | 13 +- .../ClassDeleteConfirmationModal.jsx | 159 +- .../classDashboard/ConfirmDeleteModal.jsx | 60 +- .../EventDeleteConfirmationModal.jsx | 157 +- .../classInfoDashboard/ClassInfoDashboard.tsx | 13 +- .../eventInfoDashboard/EventInfoDashboard.tsx | 25 +- .../settingsDashboard/SettingsDashboard.tsx | 15 +- .../studentDashboard/StudentDashboard.jsx | 13 +- .../StudentInfoDashboard.jsx | 13 +- .../teacherDashboard/TeacherDashboard.tsx | 13 +- .../TeacherInfoDashboard.jsx | 13 +- .../components/discovery/ClassInfoModal.jsx | 332 +++-- .../discovery/CoReqWarningModal.jsx | 199 ++- .../discovery/CompletedIndicator.png | Bin 3437 -> 0 bytes .../discovery/ConfirmationModal.jsx | 89 +- client/src/components/discovery/Discovery.jsx | 611 ++++---- .../components/discovery/EventInfoModal.jsx | 277 ++-- .../components/discovery/SignUpController.jsx | 128 +- .../discovery/SuccessSignupModal.jsx | 78 +- .../discovery/confirmationModalStyle.js | 27 + client/src/components/forms/createClasses.jsx | 711 +++++---- client/src/components/forms/createEvent.jsx | 382 ++--- client/src/components/forms/editDraft.jsx | 377 +++-- .../src/components/forms/modals/saveClass.jsx | 91 +- client/src/components/login/Login.tsx | 97 +- client/src/components/logout/Logout.jsx | 5 +- client/src/components/navbar/Navbar.jsx | 107 +- .../playground/LinkOrImageModal.jsx | 80 +- .../src/components/playground/Playground.jsx | 27 +- client/src/components/profile/Profile.jsx | 74 +- client/src/components/profile/Settings.jsx | 310 +++- client/src/components/resources/LevelCard.jsx | 14 +- client/src/components/resources/LevelCard.tsx | 18 +- client/src/components/resources/NewsCard.jsx | 100 +- .../resources/ResourceFlow/CardModal.jsx | 21 +- .../resources/ResourceFlow/FormModal.jsx | 3 +- .../resources/ResourceFlow/ProgressBar.jsx | 2 +- .../ResourceFlow/ResourceFlowController.jsx | 9 +- .../ResourceFlow/SelectClassModal.jsx | 4 +- client/src/components/resources/Resources.jsx | 189 ++- .../src/components/resources/StatusCard.jsx | 12 +- .../components/resources/UploadComponent.jsx | 31 +- client/src/components/resources/VideoCard.jsx | 116 +- client/src/components/resources/baseURL.jsx | 2 +- .../reviewModals/reviewFailureModal.jsx | 58 +- .../components/reviewModals/reviewModal.jsx | 172 ++- .../reviewModals/reviewSubmittedModal.jsx | 55 +- client/src/components/reviews/Reviews.jsx | 42 +- client/src/components/reviews/classReview.jsx | 28 +- client/src/components/reviews/reviewCard.jsx | 22 +- .../src/components/reviews/studentReview.jsx | 99 +- client/src/components/rsvp/classRsvp.jsx | 157 +- client/src/components/rsvp/eventRsvp.jsx | 150 +- client/src/components/searchbar/SearchBar.jsx | 149 +- client/src/components/shared/ClassCard.jsx | 297 ++-- client/src/components/shared/EventCard.jsx | 341 +++-- .../src/components/signup/AuthorityModal.tsx | 192 +++ client/src/components/signup/Landing.tsx | 93 +- client/src/components/signup/Signup.tsx | 252 ++-- client/src/components/signup/logo.png | Bin 38507 -> 0 bytes .../teacher-signup/TeacherSignup.tsx | 268 ++-- .../teacher-signup/requests/Request.jsx | 97 +- .../requests/center-stage-logo.png | Bin 467463 -> 0 bytes .../teacher-signup/requests/cse-logo.png | Bin 38507 -> 0 bytes client/src/contexts/AuthContext.tsx | 10 +- client/src/main.tsx | 10 +- client/src/types/attendance.ts | 7 +- client/src/types/legacy/event.ts | 23 +- client/src/utils/auth/ForgotPassword.tsx | 217 +-- .../utils/auth/ForgotPasswordConfirmation.tsx | 81 +- client/src/utils/auth/authUtils.jsx | 7 +- client/src/utils/auth/cookie.ts | 2 +- client/src/utils/auth/firebase.ts | 4 +- client/src/utils/formFormatDateTime.ts | 5 +- client/src/utils/formatDateTime.ts | 1 + server/common/transporter.ts | 1 - server/db/schema/articles.sql | 4 +- server/db/schema/class_videos.sql | 4 +- server/routes/article_tags.js | 38 +- server/routes/articles.ts | 55 +- server/routes/class_enrollments.js | 11 +- server/routes/class_tags.js | 122 +- server/routes/class_videos.js | 71 +- server/routes/classes.js | 77 +- server/routes/classes_taught.js | 32 +- server/routes/corequisites.js | 101 +- server/routes/event_enrollments.ts | 15 +- server/routes/event_tags.js | 119 +- server/routes/events.js | 55 +- server/routes/reviews.js | 35 +- server/routes/tags.js | 13 +- server/routes/users.ts | 12 +- server/routes/video_tags.js | 83 +- server/src/app.ts | 29 +- 141 files changed, 7438 insertions(+), 4847 deletions(-) rename .github/{_workflows => workflows}/run-checks.yml (74%) create mode 100644 client/public/card_images/ballet.svg create mode 100644 client/public/card_images/classical.svg create mode 100644 client/public/card_images/pop.svg create mode 100644 client/public/card_images/pop2.svg rename client/{src/components => public}/dashboard/cseLogo.png (100%) rename client/{src/components => public}/dashboard/sidebarImgs/classes.svg (100%) rename client/{src/components => public}/dashboard/sidebarImgs/dashboard.svg (100%) rename client/{src/components => public}/dashboard/sidebarImgs/redirect.svg (100%) rename client/{src/components => public}/dashboard/sidebarImgs/settings.svg (100%) rename client/{src/components => public}/dashboard/sidebarImgs/students.svg (100%) rename client/{src/components => public}/dashboard/sidebarImgs/teachers.svg (100%) rename client/{src/components/login => public}/logo.png (100%) rename client/{src/components => public}/navbar/bookings_img.svg (100%) rename client/{src/components => public}/navbar/dashboard_img.svg (100%) rename client/{src/components => public}/navbar/discovery_img.svg (100%) rename client/{src/components => public}/navbar/profile_img.svg (100%) rename client/{src/components => public}/navbar/resources_img.svg (100%) rename client/{src/components => public}/profile/arrow.svg (100%) create mode 100644 client/public/student.png create mode 100644 client/public/teacher.png create mode 100644 client/src/components/bookings/ClassTeacherCard.jsx delete mode 100644 client/src/components/bookings/CompletedIndicator.png delete mode 100644 client/src/components/discovery/CompletedIndicator.png create mode 100644 client/src/components/discovery/confirmationModalStyle.js create mode 100644 client/src/components/signup/AuthorityModal.tsx delete mode 100644 client/src/components/signup/logo.png delete mode 100644 client/src/components/teacher-signup/requests/center-stage-logo.png delete mode 100644 client/src/components/teacher-signup/requests/cse-logo.png 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 0000000000000000000000000000000000000000..d53038a791a6c1e84eb2a6b3a08866d03639d8a3 GIT binary patch literal 20555 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 enS6m>$o~TXQaIq-Ke*HY0000 literal 0 HcmV?d00001 diff --git a/client/public/teacher.png b/client/public/teacher.png new file mode 100644 index 0000000000000000000000000000000000000000..81ceae34774a2d0f20f479e633caf641e34a7e26 GIT binary patch literal 11518 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 literal 0 HcmV?d00001 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 9310fc7240fd998ead03ed157576352c6bc87a0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3437 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 { 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 +