From 75fb8e6e007db0a9097928f5a0a713381c18c4ca Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:50:27 -0400 Subject: [PATCH 01/81] format desktop semester block for shorter width --- .../DesktopSemesterBlock.tsx | 73 +++++++++++++++++++ src/pages/Planner.tsx | 35 ++++++--- 2 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 src/components/PlannerComponents/DesktopSemesterBlock.tsx diff --git a/src/components/PlannerComponents/DesktopSemesterBlock.tsx b/src/components/PlannerComponents/DesktopSemesterBlock.tsx new file mode 100644 index 0000000..d05331a --- /dev/null +++ b/src/components/PlannerComponents/DesktopSemesterBlock.tsx @@ -0,0 +1,73 @@ +import React, { Dispatch, SetStateAction } from "react"; +import { SemesterType } from "../../types/interfaces/Semester.interface"; +import { Droppable } from "@hello-pangea/dnd"; +import PlannerCourseHolder from "./PlannerCourseHolder"; +import DraggableItem from "../DraggableItem"; +import { CourseEntry } from "../../types/interfaces/Course.interface"; + +interface SemesterBlockProps { + semester: SemesterType; + index: number; + setPlannerCourses: Dispatch>; + setToolboxCourses: Dispatch>; +} + +const DesktopSemesterBlock: React.FC = ({ + semester, + index, + setPlannerCourses, + setToolboxCourses, +}) => { + return ( +
+
+ + Semester {semester.semesterNumber} + +
+ +
{semester.creditsTotal} credits
+
+
+ + + {(provided, snapshot) => { + const isEmpty = semester.courseList.length === 0; + const isHovering = snapshot.isDraggingOver; + + return ( +
+ {isEmpty && !isHovering && ( + + )} + {isEmpty && isHovering && } + {!isEmpty && + semester.courseList.map((course, courseIndex) => ( + + ))} + {provided.placeholder} +
+ ); + }} +
+
+ ); +}; + +export default DesktopSemesterBlock; diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index 470cb2b..1bdb49d 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -1,6 +1,9 @@ import React from "react"; import { Dispatch, SetStateAction } from "react"; +import useIsDesktop from "../hooks/useIsDesktop"; + import SemesterBlock from "../components/PlannerComponents/SemesterBlock"; +import DesktopSemesterBlock from "../components/PlannerComponents/DesktopSemesterBlock"; import { SemesterType } from "../types/interfaces/Semester.interface"; import { CourseEntry } from "../types/interfaces/Course.interface"; import AddSemester from "../components/PlannerComponents/AddSemester"; @@ -18,21 +21,35 @@ const Planner: React.FC = ({ setPlannerCourses, setToolboxCourses, }) => { + const isDesktop = useIsDesktop(); + return ( <>
{plannerCourses.map((semester, index) => { - return ( - - ); + if (isDesktop) { + return ( + + ); + } else { + return ( + + ); + } })}
From 62327c7d714806b31a74d58e4c62ae59d74cb8e6 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:54:18 -0400 Subject: [PATCH 02/81] prettier check --- src/App.tsx | 16 ++--- .../DesktopSemesterBlock.tsx | 2 +- src/hooks/useIsDesktop.ts | 2 +- src/pages/Planner.tsx | 58 +++++++++---------- 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 686cc93..becf035 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -40,7 +40,7 @@ function App() { const reorder = ( list: T[], startIndex: number, - endIndex: number + endIndex: number, ): T[] => { const result = [...list]; const [removed] = result.splice(startIndex, 1); @@ -71,7 +71,7 @@ function App() { if (sInd === "toolbox" && dInd === "toolbox") { setToolboxCourses((prev) => - reorder(prev, source.index, destination.index) + reorder(prev, source.index, destination.index), ); return; } @@ -95,11 +95,11 @@ function App() { courseList: reorder( semester.courseList, source.index, - destination.index + destination.index, ), } - : semester - ) + : semester, + ), ); return; } @@ -180,15 +180,15 @@ function App() { }; } return semester; - }) + }), ); setToolboxCourses((prev) => prev .map((c) => - c.name === courseToClone.name ? { ...c, count: c.count - 1 } : c + c.name === courseToClone.name ? { ...c, count: c.count - 1 } : c, ) - .filter((c) => c.count > 0) + .filter((c) => c.count > 0), ); } diff --git a/src/components/PlannerComponents/DesktopSemesterBlock.tsx b/src/components/PlannerComponents/DesktopSemesterBlock.tsx index d05331a..278dabb 100644 --- a/src/components/PlannerComponents/DesktopSemesterBlock.tsx +++ b/src/components/PlannerComponents/DesktopSemesterBlock.tsx @@ -19,7 +19,7 @@ const DesktopSemesterBlock: React.FC = ({ setToolboxCourses, }) => { return ( -
+
Semester {semester.semesterNumber} diff --git a/src/hooks/useIsDesktop.ts b/src/hooks/useIsDesktop.ts index f3bbc81..f960527 100644 --- a/src/hooks/useIsDesktop.ts +++ b/src/hooks/useIsDesktop.ts @@ -3,7 +3,7 @@ import { useState, useEffect } from "react"; const DESKTOP_BREAKPOINT = 1024; const useIsDesktop = () => { const [isDesktop, setIsDesktop] = useState( - window.innerWidth > DESKTOP_BREAKPOINT + window.innerWidth > DESKTOP_BREAKPOINT, ); useEffect(() => { diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index 1bdb49d..6223c4e 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -24,36 +24,34 @@ const Planner: React.FC = ({ const isDesktop = useIsDesktop(); return ( - <> -
- {plannerCourses.map((semester, index) => { - if (isDesktop) { - return ( - - ); - } else { - return ( - - ); - } - })} - -
- +
+ {plannerCourses.map((semester, index) => { + if (isDesktop) { + return ( + + ); + } else { + return ( + + ); + } + })} + +
); }; From aa12c4334b03a4966e34f4b7463f9eccffe9a19c Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:59:41 -0400 Subject: [PATCH 03/81] format semester blocks to be in columns --- .../DesktopSemesterBlock.tsx | 2 +- src/pages/Planner.tsx | 54 ++++++++++--------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/components/PlannerComponents/DesktopSemesterBlock.tsx b/src/components/PlannerComponents/DesktopSemesterBlock.tsx index 278dabb..2c2dfa7 100644 --- a/src/components/PlannerComponents/DesktopSemesterBlock.tsx +++ b/src/components/PlannerComponents/DesktopSemesterBlock.tsx @@ -19,7 +19,7 @@ const DesktopSemesterBlock: React.FC = ({ setToolboxCourses, }) => { return ( -
+
Semester {semester.semesterNumber} diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index 6223c4e..63bfad2 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -24,32 +24,34 @@ const Planner: React.FC = ({ const isDesktop = useIsDesktop(); return ( -
- {plannerCourses.map((semester, index) => { - if (isDesktop) { - return ( - - ); - } else { - return ( - - ); - } - })} +
+
+ {plannerCourses.map((semester, index) => { + if (isDesktop) { + return ( + + ); + } else { + return ( + + ); + } + })} +
); From 1f668dbbd0cebfccf1fc7b134c0f76c4c17069c0 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 30 Sep 2025 17:07:01 -0400 Subject: [PATCH 04/81] create semester block selector for mobile and desktop --- .../PlannerComponents/SemesterBlock.tsx | 89 ------------------- .../DesktopSemesterBlock.tsx | 16 ++-- .../SemesterBlocks/MobileSemesterBlock.tsx | 81 +++++++++++++++++ .../SemesterBlocks/SemesterBlock.tsx | 49 ++++++++++ src/pages/Planner.tsx | 36 +++----- 5 files changed, 145 insertions(+), 126 deletions(-) delete mode 100644 src/components/PlannerComponents/SemesterBlock.tsx rename src/components/PlannerComponents/{ => SemesterBlocks}/DesktopSemesterBlock.tsx (80%) create mode 100644 src/components/PlannerComponents/SemesterBlocks/MobileSemesterBlock.tsx create mode 100644 src/components/PlannerComponents/SemesterBlocks/SemesterBlock.tsx diff --git a/src/components/PlannerComponents/SemesterBlock.tsx b/src/components/PlannerComponents/SemesterBlock.tsx deleted file mode 100644 index 0a4ed29..0000000 --- a/src/components/PlannerComponents/SemesterBlock.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import React, { Dispatch, SetStateAction } from "react"; -import { SemesterType } from "../../types/interfaces/Semester.interface"; -import { Droppable } from "@hello-pangea/dnd"; -import PlannerCourseHolder from "./PlannerCourseHolder"; -import DraggableItem from "../DraggableItem"; -import { CourseEntry } from "../../types/interfaces/Course.interface"; -interface SemesterBlockProps { - semester: SemesterType; - index: number; - setPlannerCourses: Dispatch>; - setToolboxCourses: Dispatch>; -} - -const SemesterBlock: React.FC = ({ - semester, - index, - setPlannerCourses, - setToolboxCourses, -}) => { - return ( - <> -
-
-
-
- SEM - - {semester.semesterNumber} - -
-
-
- -
-
- - -
- credits:{" "} - {semester.creditsTotal} -
-
- - {(provided, snapshot) => { - const isEmpty = semester.courseList.length === 0; - const isHovering = snapshot.isDraggingOver; - - return ( -
- {isEmpty && !isHovering && ( - - )} - {isEmpty && isHovering && ( - - )} - {!isEmpty && - semester.courseList.map((course, courseIndex) => ( - - ))} - {provided.placeholder} -
- ); - }} -
- -
-
-
- - ); -}; - -export default SemesterBlock; diff --git a/src/components/PlannerComponents/DesktopSemesterBlock.tsx b/src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx similarity index 80% rename from src/components/PlannerComponents/DesktopSemesterBlock.tsx rename to src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx index 2c2dfa7..9e9a271 100644 --- a/src/components/PlannerComponents/DesktopSemesterBlock.tsx +++ b/src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx @@ -1,16 +1,10 @@ -import React, { Dispatch, SetStateAction } from "react"; -import { SemesterType } from "../../types/interfaces/Semester.interface"; +import React from "react"; + import { Droppable } from "@hello-pangea/dnd"; -import PlannerCourseHolder from "./PlannerCourseHolder"; -import DraggableItem from "../DraggableItem"; -import { CourseEntry } from "../../types/interfaces/Course.interface"; +import PlannerCourseHolder from "../PlannerCourseHolder"; +import DraggableItem from "../../DraggableItem"; -interface SemesterBlockProps { - semester: SemesterType; - index: number; - setPlannerCourses: Dispatch>; - setToolboxCourses: Dispatch>; -} +import { SemesterBlockProps } from "./SemesterBlock"; const DesktopSemesterBlock: React.FC = ({ semester, diff --git a/src/components/PlannerComponents/SemesterBlocks/MobileSemesterBlock.tsx b/src/components/PlannerComponents/SemesterBlocks/MobileSemesterBlock.tsx new file mode 100644 index 0000000..c690afa --- /dev/null +++ b/src/components/PlannerComponents/SemesterBlocks/MobileSemesterBlock.tsx @@ -0,0 +1,81 @@ +import React from "react"; + +import { Droppable } from "@hello-pangea/dnd"; +import PlannerCourseHolder from "../PlannerCourseHolder"; +import DraggableItem from "../../DraggableItem"; + +import { SemesterBlockProps } from "./SemesterBlock"; + +const SemesterBlock: React.FC = ({ + semester, + index, + setPlannerCourses, + setToolboxCourses, +}) => { + return ( +
+
+
+
+ SEM + + {semester.semesterNumber} + +
+
+
+ +
+
+ + +
+ credits: {semester.creditsTotal} +
+
+ + {(provided, snapshot) => { + const isEmpty = semester.courseList.length === 0; + const isHovering = snapshot.isDraggingOver; + + return ( +
+ {isEmpty && !isHovering && ( + + )} + {isEmpty && isHovering && ( + + )} + {!isEmpty && + semester.courseList.map((course, courseIndex) => ( + + ))} + {provided.placeholder} +
+ ); + }} +
+ +
+
+
+ ); +}; + +export default SemesterBlock; diff --git a/src/components/PlannerComponents/SemesterBlocks/SemesterBlock.tsx b/src/components/PlannerComponents/SemesterBlocks/SemesterBlock.tsx new file mode 100644 index 0000000..05f9098 --- /dev/null +++ b/src/components/PlannerComponents/SemesterBlocks/SemesterBlock.tsx @@ -0,0 +1,49 @@ +import React, { Dispatch, SetStateAction } from "react"; +import useIsDesktop from "../../../hooks/useIsDesktop"; + +import { SemesterType } from "../../../types/interfaces/Semester.interface"; +import { CourseEntry } from "../../../types/interfaces/Course.interface"; +import MobileSemesterBlock from "./MobileSemesterBlock"; +import DesktopSemesterBlock from "./DesktopSemesterBlock"; + +export interface SemesterBlockProps { + semester: SemesterType; + index: number; + setPlannerCourses: Dispatch>; + setToolboxCourses: Dispatch>; +} + +const SemesterBlock: React.FC = ({ + semester, + index, + setPlannerCourses, + setToolboxCourses, +}) => { + const isDesktop = useIsDesktop(); + + if (isDesktop) { + if (isDesktop) { + return ( + + ); + } else { + return ( + + ); + } + } +}; + +export default SemesterBlock; diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index 63bfad2..6a1f190 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -1,9 +1,7 @@ import React from "react"; import { Dispatch, SetStateAction } from "react"; -import useIsDesktop from "../hooks/useIsDesktop"; -import SemesterBlock from "../components/PlannerComponents/SemesterBlock"; -import DesktopSemesterBlock from "../components/PlannerComponents/DesktopSemesterBlock"; +import SemesterBlock from "../components/PlannerComponents/SemesterBlocks/SemesterBlock"; import { SemesterType } from "../types/interfaces/Semester.interface"; import { CourseEntry } from "../types/interfaces/Course.interface"; import AddSemester from "../components/PlannerComponents/AddSemester"; @@ -21,35 +19,21 @@ const Planner: React.FC = ({ setPlannerCourses, setToolboxCourses, }) => { - const isDesktop = useIsDesktop(); - return (
{plannerCourses.map((semester, index) => { - if (isDesktop) { - return ( - - ); - } else { - return ( - - ); - } + return ( + + ); })}
From 3c8a8c146ecb665e2a8a6ac6730e83c67d9a0b0a Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 30 Sep 2025 17:21:38 -0400 Subject: [PATCH 05/81] fix indexing errors --- src/App.tsx | 22 +++++----- .../SemesterBlocks/DesktopSemesterBlock.tsx | 2 +- .../SemesterBlocks/SemesterBlock.tsx | 40 +++++++++---------- src/pages/Planner.tsx | 2 +- 4 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index becf035..9130ce6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -40,7 +40,7 @@ function App() { const reorder = ( list: T[], startIndex: number, - endIndex: number, + endIndex: number ): T[] => { const result = [...list]; const [removed] = result.splice(startIndex, 1); @@ -71,7 +71,7 @@ function App() { if (sInd === "toolbox" && dInd === "toolbox") { setToolboxCourses((prev) => - reorder(prev, source.index, destination.index), + reorder(prev, source.index, destination.index) ); return; } @@ -89,17 +89,17 @@ function App() { const semesterIndex = parseInt(dInd.split("-")[1]); setPlannerCourses((prev) => prev.map((semester, idx) => - idx === semesterIndex - 1 + idx === semesterIndex ? { ...semester, courseList: reorder( semester.courseList, source.index, - destination.index, + destination.index ), } - : semester, - ), + : semester + ) ); return; } @@ -110,8 +110,8 @@ function App() { setPlannerCourses((prev) => { const updated = [...prev]; - const sourceIdx = sourceSemesterIndex - 1; - const destIdx = destSemesterIndex - 1; + const sourceIdx = sourceSemesterIndex; + const destIdx = destSemesterIndex; const sourceSemester = updated[sourceIdx]; const destSemester = updated[destIdx]; @@ -180,15 +180,15 @@ function App() { }; } return semester; - }), + }) ); setToolboxCourses((prev) => prev .map((c) => - c.name === courseToClone.name ? { ...c, count: c.count - 1 } : c, + c.name === courseToClone.name ? { ...c, count: c.count - 1 } : c ) - .filter((c) => c.count > 0), + .filter((c) => c.count > 0) ); } diff --git a/src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx b/src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx index 9e9a271..c1036ec 100644 --- a/src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx +++ b/src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx @@ -35,7 +35,7 @@ const DesktopSemesterBlock: React.FC = ({
{isEmpty && !isHovering && ( diff --git a/src/components/PlannerComponents/SemesterBlocks/SemesterBlock.tsx b/src/components/PlannerComponents/SemesterBlocks/SemesterBlock.tsx index 05f9098..9c3536b 100644 --- a/src/components/PlannerComponents/SemesterBlocks/SemesterBlock.tsx +++ b/src/components/PlannerComponents/SemesterBlocks/SemesterBlock.tsx @@ -22,27 +22,25 @@ const SemesterBlock: React.FC = ({ const isDesktop = useIsDesktop(); if (isDesktop) { - if (isDesktop) { - return ( - - ); - } else { - return ( - - ); - } + return ( + + ); + } else { + return ( + + ); } }; diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index 6a1f190..57ad459 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -28,7 +28,7 @@ const Planner: React.FC = ({ return ( Date: Tue, 7 Oct 2025 15:05:06 -0400 Subject: [PATCH 06/81] refactor planner drag and drop --- src/App.tsx | 248 ++--------------------------- src/hooks/usePlannerDragAndDrop.ts | 186 ++++++++++++++++++++++ 2 files changed, 196 insertions(+), 238 deletions(-) create mode 100644 src/hooks/usePlannerDragAndDrop.ts diff --git a/src/App.tsx b/src/App.tsx index 9130ce6..3131477 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,12 +8,12 @@ import { CourseEntry, CourseType, } from "./types/interfaces/Course.interface.ts"; -import { DragDropContext, DropResult } from "@hello-pangea/dnd"; +import { DragDropContext } from "@hello-pangea/dnd"; import { SemesterType } from "./types/interfaces/Semester.interface.ts"; import { Filters } from "./types/Filters"; +import { usePlannerDragAndDrop } from "./hooks/usePlannerDragAndDrop"; -function App() { - // Original state from App.tsx +export default function App() { const [toolboxCourses, setToolboxCourses] = useState([]); const [plannerCourses, setPlannerCourses] = useState([ { @@ -24,10 +24,7 @@ function App() { }, ]); const [isDragging, setIsDragging] = useState(false); - const [searchResults, setSearchResults] = useState([]); - - // Moved from SearchBar.tsx const [searchPrompt, setSearchPrompt] = useState(""); const [showFilter, setShowFilter] = useState(false); const [filters, setFilters] = useState({ @@ -36,236 +33,13 @@ function App() { Semesters: [], }); - // Reorders a simple array - const reorder = ( - list: T[], - startIndex: number, - endIndex: number - ): T[] => { - const result = [...list]; - const [removed] = result.splice(startIndex, 1); - result.splice(endIndex, 0, removed); - return result; - }; - - const cloneCourse = (course: CourseEntry): CourseEntry => { - return { - ...course, - name: course.name + "-" + Math.random().toString(36).substring(2, 7), - data: { ...course.data }, - count: 1, - }; - }; - - const onDragStart = () => { - setIsDragging(true); - }; - - const onDragEnd = (result: DropResult) => { - setIsDragging(false); - const { source, destination, draggableId } = result; - if (!destination) return; - - const sInd = source.droppableId; - const dInd = destination.droppableId; - - if (sInd === "toolbox" && dInd === "toolbox") { - setToolboxCourses((prev) => - reorder(prev, source.index, destination.index) - ); - return; - } - - if (dInd === "garbage" && sInd === "toolbox") { - setToolboxCourses((prev) => { - const updated = [...prev]; - updated.splice(source.index, 1); - return updated; - }); - return; - } - - if (sInd === dInd && dInd.startsWith("planner-")) { - const semesterIndex = parseInt(dInd.split("-")[1]); - setPlannerCourses((prev) => - prev.map((semester, idx) => - idx === semesterIndex - ? { - ...semester, - courseList: reorder( - semester.courseList, - source.index, - destination.index - ), - } - : semester - ) - ); - return; - } - - if (sInd.startsWith("planner-") && dInd.startsWith("planner-")) { - const sourceSemesterIndex = parseInt(sInd.split("-")[1]); - const destSemesterIndex = parseInt(dInd.split("-")[1]); - - setPlannerCourses((prev) => { - const updated = [...prev]; - const sourceIdx = sourceSemesterIndex; - const destIdx = destSemesterIndex; - - const sourceSemester = updated[sourceIdx]; - const destSemester = updated[destIdx]; - - if ( - !sourceSemester || - !destSemester || - source.index < 0 || - source.index >= sourceSemester.courseList.length - ) { - console.warn("Invalid source or destination index"); - return prev; - } - - const courseListCopy = [...sourceSemester.courseList]; - const [movedCourse] = courseListCopy.splice(source.index, 1); - - if (!movedCourse) { - console.warn("Failed to extract course from source"); - return prev; - } - - updated[sourceIdx] = { - ...sourceSemester, - courseList: courseListCopy, - creditsTotal: - sourceSemester.creditsTotal - movedCourse.data.credit_max, - }; - - const destListCopy = [...destSemester.courseList]; - destListCopy.splice(destination.index, 0, movedCourse); - - updated[destIdx] = { - ...destSemester, - courseList: destListCopy, - creditsTotal: destSemester.creditsTotal + movedCourse.data.credit_max, - }; - - return updated; - }); - - return; - } - - const fromToolbox = sInd === "toolbox"; - const toPlanner = dInd.startsWith("planner-"); - - if (fromToolbox && toPlanner) { - const courseToClone = toolboxCourses.find((c) => c.name === draggableId); - const semesterIndex = parseInt(dInd.split("-")[1]); - - if (courseToClone) { - const newCourse = cloneCourse(courseToClone); - - setPlannerCourses((prev) => - prev.map((semester, idx) => { - if (idx + 1 === semesterIndex) { - const updatedList = [...semester.courseList]; - updatedList.splice(destination.index, 0, newCourse); - - return { - ...semester, - courseList: updatedList, - creditsTotal: - semester.creditsTotal + courseToClone.data.credit_max, - }; - } - return semester; - }) - ); - - setToolboxCourses((prev) => - prev - .map((c) => - c.name === courseToClone.name ? { ...c, count: c.count - 1 } : c - ) - .filter((c) => c.count > 0) - ); - } - - return; - } - - // if (sInd.startsWith("planner-") && dInd === "toolbox") { - // const sourceSemesterIndex = parseInt(sInd.split("-")[1]); - - // setPlannerCourses((prev) => { - // const updated = [...prev]; - // const sourceSemester = updated[sourceSemesterIndex - 1]; - // const courseListCopy = [...sourceSemester.courseList]; - // const [removedCourse] = courseListCopy.splice(source.index, 1); - - // if (!removedCourse) return prev; - - // updated[sourceSemesterIndex - 1] = { - // ...sourceSemester, - // courseList: courseListCopy, - // creditsTotal: - // sourceSemester.creditsTotal - removedCourse.data.credit_max, - // }; - - // setToolboxCourses((prevToolbox) => { - // const existing = prevToolbox.find( - // (c) => - // c.data.dept === removedCourse.data.dept && - // c.data.code_num === removedCourse.data.code_num - // ); - - // if (existing) { - // return prevToolbox.map((c) => - // c.name === existing.name ? { ...c, count: c.count + 1 } : c - // ); - // } else { - // return [ - // ...prevToolbox, - // { - // name: removedCourse.data.dept + removedCourse.data.code_num, - // data: removedCourse.data, - // count: 1, - // }, - // ]; - // } - // }); - - // return updated; - // }); - - // return; - // } - - if (sInd.startsWith("planner-") && dInd === "garbage") { - const sourceSemesterIndex = parseInt(sInd.split("-")[1]); - - setPlannerCourses((prev) => { - const updated = [...prev]; - const sourceSemester = updated[sourceSemesterIndex - 1]; - const courseListCopy = [...sourceSemester.courseList]; - const [removedCourse] = courseListCopy.splice(source.index, 1); - - if (!removedCourse) return prev; - - updated[sourceSemesterIndex - 1] = { - ...sourceSemester, - courseList: courseListCopy, - creditsTotal: - sourceSemester.creditsTotal - removedCourse.data.credit_max, - }; - - return updated; - }); - - return; - } - }; + // The complex logic is now neatly contained in the hook + const { onDragStart, onDragEnd } = usePlannerDragAndDrop({ + toolboxCourses, + setToolboxCourses, + setPlannerCourses, + setIsDragging, + }); return (
@@ -335,5 +109,3 @@ function App() {
); } - -export default App; diff --git a/src/hooks/usePlannerDragAndDrop.ts b/src/hooks/usePlannerDragAndDrop.ts new file mode 100644 index 0000000..4b7239e --- /dev/null +++ b/src/hooks/usePlannerDragAndDrop.ts @@ -0,0 +1,186 @@ +import { Dispatch, SetStateAction } from "react"; +import { DropResult } from "@hello-pangea/dnd"; +import { CourseEntry } from "../types/interfaces/Course.interface"; +import { SemesterType } from "../types/interfaces/Semester.interface"; + +// --- Helper Functions --- + +const reorder = (list: T[], startIndex: number, endIndex: number): T[] => { + const result = [...list]; + const [removed] = result.splice(startIndex, 1); + result.splice(endIndex, 0, removed); + return result; +}; + +const cloneCourse = (course: CourseEntry): CourseEntry => { + return { + ...course, + name: `${course.name}-${Math.random().toString(36).substring(2, 7)}`, + data: { ...course.data }, + count: 1, + }; +}; + +// --- Hook Definition --- + +interface UsePlannerDragAndDropProps { + toolboxCourses: CourseEntry[]; + setToolboxCourses: Dispatch>; + setPlannerCourses: Dispatch>; + setIsDragging: Dispatch>; +} + +export const usePlannerDragAndDrop = ({ + toolboxCourses, + setToolboxCourses, + setPlannerCourses, + setIsDragging, +}: UsePlannerDragAndDropProps) => { + const onDragStart = () => { + setIsDragging(true); + }; + + const onDragEnd = (result: DropResult) => { + setIsDragging(false); + const { source, destination, draggableId } = result; + if (!destination) return; + + const sInd = source.droppableId; + const dInd = destination.droppableId; + + // Reorder in toolbox + if (sInd === "toolbox" && dInd === "toolbox") { + setToolboxCourses((prev) => + reorder(prev, source.index, destination.index) + ); + return; + } + + // Delete from toolbox + if (dInd === "garbage" && sInd === "toolbox") { + setToolboxCourses((prev) => + prev.filter((_, index) => index !== source.index) + ); + return; + } + + // Reorder within the same semester + if (sInd === dInd && dInd.startsWith("planner-")) { + const semesterIndex = parseInt(dInd.split("-")[1]) - 1; + setPlannerCourses((prev) => + prev.map((semester, idx) => + idx === semesterIndex + ? { + ...semester, + courseList: reorder( + semester.courseList, + source.index, + destination.index + ), + } + : semester + ) + ); + return; + } + + // Move between different semesters + if (sInd.startsWith("planner-") && dInd.startsWith("planner-")) { + const sourceSemesterIndex = parseInt(sInd.split("-")[1]) - 1; + const destSemesterIndex = parseInt(dInd.split("-")[1]) - 1; + + setPlannerCourses((prev) => { + const updated = [...prev]; + if ( + sourceSemesterIndex >= updated.length || + destSemesterIndex >= updated.length + ) + return prev; + + const sourceSemester = updated[sourceSemesterIndex]; + const destSemester = updated[destSemesterIndex]; + + const sourceListCopy = [...sourceSemester.courseList]; + const [movedCourse] = sourceListCopy.splice(source.index, 1); + + updated[sourceSemesterIndex] = { + ...sourceSemester, + courseList: sourceListCopy, + creditsTotal: + sourceSemester.creditsTotal - movedCourse.data.credit_max, + }; + + const destListCopy = [...destSemester.courseList]; + destListCopy.splice(destination.index, 0, movedCourse); + + updated[destSemesterIndex] = { + ...destSemester, + courseList: destListCopy, + creditsTotal: destSemester.creditsTotal + movedCourse.data.credit_max, + }; + + return updated; + }); + return; + } + + // Move from toolbox to planner + if (sInd === "toolbox" && dInd.startsWith("planner-")) { + const courseToClone = toolboxCourses.find((c) => c.name === draggableId); + const semesterIndex = parseInt(dInd.split("-")[1]); + + if (courseToClone) { + const newCourse = cloneCourse(courseToClone); + setPlannerCourses((prev) => + prev.map((semester, idx) => { + if (idx + 1 === semesterIndex) { + const updatedList = [...semester.courseList]; + updatedList.splice(destination.index, 0, newCourse); + return { + ...semester, + courseList: updatedList, + creditsTotal: + semester.creditsTotal + courseToClone.data.credit_max, + }; + } + return semester; + }) + ); + setToolboxCourses((prev) => + prev + .map((c) => + c.name === courseToClone.name ? { ...c, count: c.count - 1 } : c + ) + .filter((c) => c.count > 0) + ); + } + return; + } + + // Delete from planner + if (sInd.startsWith("planner-") && dInd === "garbage") { + const sourceSemesterIndex = parseInt(sInd.split("-")[1]) - 1; + setPlannerCourses((prev) => { + const updated = [...prev]; + if (sourceSemesterIndex >= updated.length) return prev; + + const sourceSemester = updated[sourceSemesterIndex]; + const courseListCopy = [...sourceSemester.courseList]; + const [removedCourse] = courseListCopy.splice(source.index, 1); + + if (!removedCourse) return prev; + + updated[sourceSemesterIndex] = { + ...sourceSemester, + courseList: courseListCopy, + creditsTotal: + sourceSemester.creditsTotal - removedCourse.data.credit_max, + }; + return updated; + }); + return; + } + }; + + return { onDragStart, onDragEnd }; +}; From 60f38fe51cf248cccbc1529951681e94b5bfd404 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:29:48 -0400 Subject: [PATCH 07/81] have logo and label sticky on top --- src/pages/Catalog.tsx | 4 +++- src/pages/HomePage.tsx | 11 ++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index 224183b..8d06171 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -37,7 +37,8 @@ const Catalog: React.FC = ({ }) => { return ( <> -
+
+

Courses

= ({ setFilters={setFilters} />
+
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index f0f0cf0..b2be4c8 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -27,14 +27,11 @@ const HomePage = (props: HomePageProps) => { return (
- Carpi Logo +
+ Carpi Logo +
-

Courses

{ />
{isDesktop && ( -
+

Planner

Date: Tue, 7 Oct 2025 15:36:54 -0400 Subject: [PATCH 08/81] custom scrollbar color --- src/index.css | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/index.css b/src/index.css index d274355..f04ad21 100644 --- a/src/index.css +++ b/src/index.css @@ -8,9 +8,27 @@ } body { - @apply bg-carpipink; + scrollbar-color: var(--color-darkblue) var(--color-carpipink); + scrollbar-width: thin; } +/* scrollbar for webkit browsers */ +::-webkit-scrollbar { + width: 12px; + height: 12px; +} + +::-webkit-scrollbar-track { + background: var(--color-carpipink); +} + +::-webkit-scrollbar-thumb { + background-color: var(--color-darkblue); + border-radius: 9999px; + border: 3px solid var(--color-carpipink); +} + +/* prevent blurriness of images */ img { image-rendering: crisp-edges; image-rendering: -webkit-optimize-contrast; From 780e9ba2e120dbffd5a42672eaed975c81b73b70 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:39:54 -0400 Subject: [PATCH 09/81] move isDragging to homepage --- src/pages/Catalog.tsx | 8 +++----- src/pages/HomePage.tsx | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index 8d06171..56ec936 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -36,7 +36,7 @@ const Catalog: React.FC = ({ setFilters, }) => { return ( - <> +

Courses

= ({ />
-
+
{searchResults?.map((course: CourseType, index: number) => ( = ({ - +
); }; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index b2be4c8..99c5f47 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -26,7 +26,7 @@ const HomePage = (props: HomePageProps) => { const isDesktop = useIsDesktop(); return ( -
+
Carpi Logo
From 48d58dd4734e03d05d85cefd57974a74e5a42112 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:31:23 -0400 Subject: [PATCH 10/81] remove toolbox icon --- src/components/Toolbox/Toolbox.tsx | 92 ++++++++++++------------ src/components/Toolbox/ToolboxButton.tsx | 26 ++++--- 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/components/Toolbox/Toolbox.tsx b/src/components/Toolbox/Toolbox.tsx index 9ac68d5..3642787 100644 --- a/src/components/Toolbox/Toolbox.tsx +++ b/src/components/Toolbox/Toolbox.tsx @@ -28,60 +28,62 @@ const Toolbox: React.FC = ({ courses, isDragging }) => { return sum; }; return ( - <> -
+
+ {!isDesktop && ( - -
-
-
- -
- - - {(provided, snapshot) => { - return ( -
- {courses.map((course, index) => ( - - ))} - {provided.placeholder} -
- ); - }} -
+ )} + +
+
+
+
- {!isDesktop && } + + {(provided, snapshot) => { + return ( +
+ {courses.map((course, index) => ( + + ))} + {provided.placeholder} +
+ ); + }} +
+ + {!isDesktop && }
- +
); }; diff --git a/src/components/Toolbox/ToolboxButton.tsx b/src/components/Toolbox/ToolboxButton.tsx index 3b7dc09..00427ca 100644 --- a/src/components/Toolbox/ToolboxButton.tsx +++ b/src/components/Toolbox/ToolboxButton.tsx @@ -12,22 +12,20 @@ const ToolboxButton: React.FC = ({ count, }) => { return ( - <> - - +

{count}

+
+ + ); }; From 7d24c7e263a748cbea582942b89bad716e7c4f3e Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:36:18 -0400 Subject: [PATCH 11/81] add counting to the desktop toolbox version --- src/components/Toolbox/Toolbox.tsx | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/Toolbox/Toolbox.tsx b/src/components/Toolbox/Toolbox.tsx index 3642787..717ee2f 100644 --- a/src/components/Toolbox/Toolbox.tsx +++ b/src/components/Toolbox/Toolbox.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import useIsDesktop from "../../hooks/useIsDesktop"; import ToolboxButton from "./ToolboxButton"; @@ -16,24 +16,24 @@ interface ToolboxProps { const Toolbox: React.FC = ({ courses, isDragging }) => { const [isOpen, setIsOpen] = useState(false); + const [count, updateCount] = useState(0); const isDesktop = useIsDesktop(); const toggleToolbox = () => { setIsOpen((open) => !open); }; - const toolboxSum = () => { - let sum = 0; - courses.forEach((course) => (sum += course.count)); - return sum; - }; + useEffect(() => { + updateCount(courses.length); + }, [courses]); + return (
{!isDesktop && ( )} @@ -50,6 +50,13 @@ const Toolbox: React.FC = ({ courses, isDragging }) => { +
+

{count}

+
From 235e65a45dc56fdf539f304eb0bc7a17159051c9 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 7 Oct 2025 17:01:54 -0400 Subject: [PATCH 12/81] clean up planner course styling --- .../PlannerComponents/PlannerCourse.tsx | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/components/PlannerComponents/PlannerCourse.tsx b/src/components/PlannerComponents/PlannerCourse.tsx index f770e2b..4c0e395 100644 --- a/src/components/PlannerComponents/PlannerCourse.tsx +++ b/src/components/PlannerComponents/PlannerCourse.tsx @@ -63,8 +63,8 @@ const PlannerCourse: React.FC = ({ ...semester.courseList.slice(index + 1), ], } - : semester, - ), + : semester + ) ); }; @@ -116,7 +116,7 @@ const PlannerCourse: React.FC = ({ semester.creditsTotal - prev[semesterIndex - 1].courseList[index].data.credit_max, } - : semester, + : semester ); return updatedPlanner.map((semester, i) => @@ -126,7 +126,7 @@ const PlannerCourse: React.FC = ({ courseList: [...semester.courseList, courseCopy], creditsTotal: semester.creditsTotal + courseCopy.data.credit_max, } - : semester, + : semester ); }); }; @@ -150,12 +150,12 @@ const PlannerCourse: React.FC = ({ creditsTotal: semester.creditsTotal - courseToMove.data.credit_max, } - : semester, + : semester ); setToolboxCourses((toolboxPrev) => { const existingIndex = toolboxPrev.findIndex( - (entry) => entry.name === cleanedName, + (entry) => entry.name === cleanedName ); if (existingIndex !== -1) { @@ -182,8 +182,8 @@ const PlannerCourse: React.FC = ({ semester.creditsTotal - semester.courseList[index].data.credit_max, } - : semester, - ), + : semester + ) ); }; @@ -203,25 +203,23 @@ const PlannerCourse: React.FC = ({ }; return ( -
+
-
-
- -
- - {course.dept} - {course.code_num} - -

{toTitleCase(course.title)}

-
+
+ +
+ + {course.dept} + {course.code_num} + +

{toTitleCase(course.title)}

-
+

{course.credit_max}

From 78de211926408116f759e8dc4ff889a9eb8262d1 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 7 Oct 2025 17:02:22 -0400 Subject: [PATCH 13/81] prettier check --- .../PlannerComponents/PlannerCourse.tsx | 16 ++++++++-------- src/hooks/usePlannerDragAndDrop.ts | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/components/PlannerComponents/PlannerCourse.tsx b/src/components/PlannerComponents/PlannerCourse.tsx index 4c0e395..74890ef 100644 --- a/src/components/PlannerComponents/PlannerCourse.tsx +++ b/src/components/PlannerComponents/PlannerCourse.tsx @@ -63,8 +63,8 @@ const PlannerCourse: React.FC = ({ ...semester.courseList.slice(index + 1), ], } - : semester - ) + : semester, + ), ); }; @@ -116,7 +116,7 @@ const PlannerCourse: React.FC = ({ semester.creditsTotal - prev[semesterIndex - 1].courseList[index].data.credit_max, } - : semester + : semester, ); return updatedPlanner.map((semester, i) => @@ -126,7 +126,7 @@ const PlannerCourse: React.FC = ({ courseList: [...semester.courseList, courseCopy], creditsTotal: semester.creditsTotal + courseCopy.data.credit_max, } - : semester + : semester, ); }); }; @@ -150,12 +150,12 @@ const PlannerCourse: React.FC = ({ creditsTotal: semester.creditsTotal - courseToMove.data.credit_max, } - : semester + : semester, ); setToolboxCourses((toolboxPrev) => { const existingIndex = toolboxPrev.findIndex( - (entry) => entry.name === cleanedName + (entry) => entry.name === cleanedName, ); if (existingIndex !== -1) { @@ -182,8 +182,8 @@ const PlannerCourse: React.FC = ({ semester.creditsTotal - semester.courseList[index].data.credit_max, } - : semester - ) + : semester, + ), ); }; diff --git a/src/hooks/usePlannerDragAndDrop.ts b/src/hooks/usePlannerDragAndDrop.ts index 4b7239e..fa36db8 100644 --- a/src/hooks/usePlannerDragAndDrop.ts +++ b/src/hooks/usePlannerDragAndDrop.ts @@ -51,7 +51,7 @@ export const usePlannerDragAndDrop = ({ // Reorder in toolbox if (sInd === "toolbox" && dInd === "toolbox") { setToolboxCourses((prev) => - reorder(prev, source.index, destination.index) + reorder(prev, source.index, destination.index), ); return; } @@ -59,7 +59,7 @@ export const usePlannerDragAndDrop = ({ // Delete from toolbox if (dInd === "garbage" && sInd === "toolbox") { setToolboxCourses((prev) => - prev.filter((_, index) => index !== source.index) + prev.filter((_, index) => index !== source.index), ); return; } @@ -75,11 +75,11 @@ export const usePlannerDragAndDrop = ({ courseList: reorder( semester.courseList, source.index, - destination.index + destination.index, ), } - : semester - ) + : semester, + ), ); return; } @@ -144,14 +144,14 @@ export const usePlannerDragAndDrop = ({ }; } return semester; - }) + }), ); setToolboxCourses((prev) => prev .map((c) => - c.name === courseToClone.name ? { ...c, count: c.count - 1 } : c + c.name === courseToClone.name ? { ...c, count: c.count - 1 } : c, ) - .filter((c) => c.count > 0) + .filter((c) => c.count > 0), ); } return; From a40bc3c24fbea3741fbdb5beb34ae60d2f11e7ac Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 7 Oct 2025 17:13:33 -0400 Subject: [PATCH 14/81] unset draggable item position --- src/components/DraggableItem.tsx | 77 ++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/src/components/DraggableItem.tsx b/src/components/DraggableItem.tsx index 67d1ef9..c202b18 100644 --- a/src/components/DraggableItem.tsx +++ b/src/components/DraggableItem.tsx @@ -29,39 +29,50 @@ const DraggableItem: React.FC = ({ }) => { return ( - {(provided, snapshot) => ( -
- {location === "planner" && - setPlannerCourses && - setToolboxCourses && - semesterIndex !== null ? ( - - ) : ( - - )} -
- )} + {(provided, snapshot) => { + // create a mutable copy of the style object provided by the library + const style = { + ...provided.draggableProps.style, + cursor: "grab", + }; + + // unset top and let properties when dragging + if (snapshot.isDragging) { + (style as any).top = undefined; + (style as any).left = undefined; + } + + return ( +
+ {location === "planner" && + setPlannerCourses && + setToolboxCourses && + semesterIndex !== null ? ( + + ) : ( + + )} +
+ ); + }}
); }; From 49eb0e955018ee61f774c25421ccaf9140777e1d Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 10 Oct 2025 16:27:29 -0400 Subject: [PATCH 15/81] make sure planner courses are the right z-index --- src/App.tsx | 114 +++++++++--------- src/components/DraggableItem.tsx | 1 - .../PlannerComponents/PlannerCourse.tsx | 19 +-- src/pages/HomePage.tsx | 2 +- 4 files changed, 67 insertions(+), 69 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 3131477..be23b63 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -48,64 +48,62 @@ export default function App() { isDragging ? "brightness-50" : "" }`} >
-
- - - - - } - /> - - } - /> - - } - /> - - - - -
+ + + + + } + /> + + } + /> + + } + /> + + + +
); } diff --git a/src/components/DraggableItem.tsx b/src/components/DraggableItem.tsx index c202b18..947801c 100644 --- a/src/components/DraggableItem.tsx +++ b/src/components/DraggableItem.tsx @@ -48,7 +48,6 @@ const DraggableItem: React.FC = ({ {...provided.draggableProps} {...provided.dragHandleProps} style={style} - className="" > {location === "planner" && setPlannerCourses && diff --git a/src/components/PlannerComponents/PlannerCourse.tsx b/src/components/PlannerComponents/PlannerCourse.tsx index 74890ef..496ac4d 100644 --- a/src/components/PlannerComponents/PlannerCourse.tsx +++ b/src/components/PlannerComponents/PlannerCourse.tsx @@ -22,6 +22,7 @@ interface PlannerCourseProps { const PlannerCourse: React.FC = ({ course, index, + isDragging, setPlannerCourses, setToolboxCourses, semesterIndex, @@ -63,8 +64,8 @@ const PlannerCourse: React.FC = ({ ...semester.courseList.slice(index + 1), ], } - : semester, - ), + : semester + ) ); }; @@ -116,7 +117,7 @@ const PlannerCourse: React.FC = ({ semester.creditsTotal - prev[semesterIndex - 1].courseList[index].data.credit_max, } - : semester, + : semester ); return updatedPlanner.map((semester, i) => @@ -126,7 +127,7 @@ const PlannerCourse: React.FC = ({ courseList: [...semester.courseList, courseCopy], creditsTotal: semester.creditsTotal + courseCopy.data.credit_max, } - : semester, + : semester ); }); }; @@ -150,12 +151,12 @@ const PlannerCourse: React.FC = ({ creditsTotal: semester.creditsTotal - courseToMove.data.credit_max, } - : semester, + : semester ); setToolboxCourses((toolboxPrev) => { const existingIndex = toolboxPrev.findIndex( - (entry) => entry.name === cleanedName, + (entry) => entry.name === cleanedName ); if (existingIndex !== -1) { @@ -182,8 +183,8 @@ const PlannerCourse: React.FC = ({ semester.creditsTotal - semester.courseList[index].data.credit_max, } - : semester, - ), + : semester + ) ); }; @@ -203,7 +204,7 @@ const PlannerCourse: React.FC = ({ }; return ( -
+
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 99c5f47..0bc46e1 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -47,7 +47,7 @@ const HomePage = (props: HomePageProps) => { />
{isDesktop && ( -
+

Planner

Date: Fri, 10 Oct 2025 17:10:18 -0400 Subject: [PATCH 16/81] clean up errors and switch to single column --- .../PlannerComponents/PlannerCourse.tsx | 48 +++++++++---------- .../SemesterBlocks/DesktopSemesterBlock.tsx | 2 +- src/pages/Planner.tsx | 13 ++--- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/components/PlannerComponents/PlannerCourse.tsx b/src/components/PlannerComponents/PlannerCourse.tsx index 97cfb82..3d5f998 100644 --- a/src/components/PlannerComponents/PlannerCourse.tsx +++ b/src/components/PlannerComponents/PlannerCourse.tsx @@ -206,33 +206,33 @@ const PlannerCourse: React.FC = ({ /* Right-Click Context Menu */ return ( -
-
-
- -
- - {course.dept} - {course.code_num} - -

{toTitleCase(course.title)}

+ + +
+
+ +
+ + {course.dept} + {course.code_num} + +

{toTitleCase(course.title)}

+
-
-
-
-

{course.credit_max}

+ +
+
+

{course.credit_max}

+
+
-
-
- +
= ({ setToolboxCourses, }) => { return ( -
+
Semester {semester.semesterNumber} diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index f7014eb..d07b5b8 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -1,5 +1,5 @@ import React, { Dispatch, SetStateAction } from "react"; -import SemesterBlock from "../components/PlannerComponents/SemesterBlock"; +import SemesterBlock from "../components/PlannerComponents/SemesterBlocks/SemesterBlock"; import { SemesterType } from "../types/interfaces/Semester.interface"; import { CourseEntry } from "../types/interfaces/Course.interface"; import AddSemester from "../components/PlannerComponents/AddSemester"; @@ -27,16 +27,13 @@ const Planner: React.FC = ({ }; return ( -
+
{plannerCourses.map((semester, index) => ( -
+
From 85be4ad4189aa49202a5db18970561054471bf51 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:16:34 -0400 Subject: [PATCH 17/81] adjust toolbox for dekstop --- src/components/Toolbox/Toolbox.tsx | 122 +++++++++++++++++------------ 1 file changed, 71 insertions(+), 51 deletions(-) diff --git a/src/components/Toolbox/Toolbox.tsx b/src/components/Toolbox/Toolbox.tsx index 717ee2f..4ca44e4 100644 --- a/src/components/Toolbox/Toolbox.tsx +++ b/src/components/Toolbox/Toolbox.tsx @@ -15,76 +15,96 @@ interface ToolboxProps { } const Toolbox: React.FC = ({ courses, isDragging }) => { - const [isOpen, setIsOpen] = useState(false); - const [count, updateCount] = useState(0); const isDesktop = useIsDesktop(); + const [isOpen, setIsOpen] = useState(isDesktop); + const [count, setCount] = useState(0); + + useEffect(() => { + setCount(courses.length); + }, [courses]); + + useEffect(() => { + setIsOpen(isDesktop); + }, [isDesktop]); const toggleToolbox = () => { setIsOpen((open) => !open); }; - useEffect(() => { - updateCount(courses.length); - }, [courses]); - return ( -
- {!isDesktop && ( +
+ {!isDesktop && !isOpen && ( )} + -
+ +
-
-
+ +
+ + {(provided, snapshot) => (
-

{count}

+ {courses.map((course, index) => ( + + ))} + {provided.placeholder}
- -
- - - {(provided, snapshot) => { - return ( -
- {courses.map((course, index) => ( - - ))} - {provided.placeholder} -
- ); - }} + )}
From fc3013332dba67ddc943a7df25aa5960b25c86e0 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:26:32 -0400 Subject: [PATCH 18/81] add tailwind-scrollbar --- package-lock.json | 44 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/index.css | 1 + 3 files changed, 46 insertions(+) diff --git a/package-lock.json b/package-lock.json index 3721c7a..a0d1a39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.16", "globals": "^15.14.0", + "tailwind-scrollbar": "^4.0.2", "typescript": "~5.6.2", "typescript-eslint": "^8.18.2", "vite": "^6.0.5" @@ -2149,6 +2150,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/prismjs": { + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", + "dev": true + }, "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -2610,6 +2617,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4063,6 +4079,19 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prism-react-renderer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "dev": true, + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -4460,6 +4489,21 @@ "node": ">=8" } }, + "node_modules/tailwind-scrollbar": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/tailwind-scrollbar/-/tailwind-scrollbar-4.0.2.tgz", + "integrity": "sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA==", + "dev": true, + "dependencies": { + "prism-react-renderer": "^2.4.1" + }, + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "tailwindcss": "4.x" + } + }, "node_modules/tailwindcss": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.6.tgz", diff --git a/package.json b/package.json index bcfc33d..e1709a9 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.16", "globals": "^15.14.0", + "tailwind-scrollbar": "^4.0.2", "typescript": "~5.6.2", "typescript-eslint": "^8.18.2", "vite": "^6.0.5" diff --git a/src/index.css b/src/index.css index f04ad21..4361ec1 100644 --- a/src/index.css +++ b/src/index.css @@ -1,4 +1,5 @@ @import "tailwindcss"; +@plugin 'tailwind-scrollbar'; @theme { --color-carpipink: oklch(88.46% 0.0441 18.02); From 516794111004a861b1c39a128ea3107db3489230 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:31:43 -0400 Subject: [PATCH 19/81] adjust scrollbar appearances --- src/pages/Catalog.tsx | 24 +++++++++++++++--------- src/pages/HomePage.tsx | 10 ++++++---- src/pages/Planner.tsx | 39 ++++++++++++++++++++------------------- 3 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index 56ec936..aa15cd9 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -50,15 +50,21 @@ const Catalog: React.FC = ({ />
-
- {searchResults?.map((course: CourseType, index: number) => ( - - ))} +
+
+ {searchResults?.map((course: CourseType, index: number) => ( + + ))} +
- ))} -
+
+ {departments.map((dept) => ( + + ))}
); }; diff --git a/src/components/SearchBar/SeachBar.tsx b/src/components/SearchBar/SeachBar.tsx index 5c8103c..b128bb3 100644 --- a/src/components/SearchBar/SeachBar.tsx +++ b/src/components/SearchBar/SeachBar.tsx @@ -62,7 +62,7 @@ const SearchBar: React.FC = ({ updateSearchResults }) => { "&attrFilters=" + attrFilters + "&semFilters=" + - semFilters, + semFilters ); const data = response.data; @@ -98,7 +98,7 @@ const SearchBar: React.FC = ({ updateSearchResults }) => { if (prev[category].includes(value)) { const newFilters: Filters = { ...prev }; newFilters[category] = newFilters[category].filter( - (tag) => tag !== value, + (tag) => tag !== value ); return newFilters; } @@ -114,7 +114,7 @@ const SearchBar: React.FC = ({ updateSearchResults }) => { const newFilters: Filters = { ...prev }; for (const category of Object.keys(prev) as (keyof Filters)[]) { newFilters[category] = newFilters[category].filter( - (tag) => tag !== value, + (tag) => tag !== value ); } @@ -131,7 +131,7 @@ const SearchBar: React.FC = ({ updateSearchResults }) => { }; return ( -
+
@@ -161,7 +161,7 @@ const SearchBar: React.FC = ({ updateSearchResults }) => {
-
+
{filters.Subject.map((tag, index) => ( ))} @@ -189,7 +189,12 @@ const SearchBar: React.FC = ({ updateSearchResults }) => { {showFilter && ( )} - {showDeptFilter && } + + {showDeptFilter && ( +
+ +
+ )}
); }; diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index aa15cd9..9ad8493 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -37,7 +37,7 @@ const Catalog: React.FC = ({ }) => { return (
-
+

Courses

= ({ />
-
+
= ({
- +
); }; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 1c86b77..52606cc 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -27,12 +27,12 @@ const HomePage = (props: HomePageProps) => { return (
Carpi Logo
-
+
Date: Fri, 17 Oct 2025 16:30:55 -0400 Subject: [PATCH 23/81] set catalog scroll height to 18rem --- src/components/SearchBar/SeachBar.tsx | 2 +- src/pages/Catalog.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/SearchBar/SeachBar.tsx b/src/components/SearchBar/SeachBar.tsx index b128bb3..05428e7 100644 --- a/src/components/SearchBar/SeachBar.tsx +++ b/src/components/SearchBar/SeachBar.tsx @@ -191,7 +191,7 @@ const SearchBar: React.FC = ({ updateSearchResults }) => { )} {showDeptFilter && ( -
+
)} diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index 9ad8493..81ef7fe 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -50,7 +50,7 @@ const Catalog: React.FC = ({ />
-
+
Date: Fri, 17 Oct 2025 16:34:38 -0400 Subject: [PATCH 24/81] remove isdragging overlays --- src/components/GarbageBin.tsx | 4 ++-- src/components/Toolbox/ToolboxCourse.tsx | 2 +- src/pages/Catalog.tsx | 4 +--- src/pages/HomePage.tsx | 4 +--- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/components/GarbageBin.tsx b/src/components/GarbageBin.tsx index a4229d4..f1aacd8 100644 --- a/src/components/GarbageBin.tsx +++ b/src/components/GarbageBin.tsx @@ -14,11 +14,11 @@ const GarbageBin: React.FC = ({ isDragging }) => {
{provided.placeholder}
diff --git a/src/components/Toolbox/ToolboxCourse.tsx b/src/components/Toolbox/ToolboxCourse.tsx index c21e324..11efba1 100644 --- a/src/components/Toolbox/ToolboxCourse.tsx +++ b/src/components/Toolbox/ToolboxCourse.tsx @@ -15,7 +15,7 @@ const ToolboxCourse: React.FC = ({ return (
= ({
{searchResults?.map((course: CourseType, index: number) => ( { const isDesktop = useIsDesktop(); return ( -
+
Carpi Logo
From f8272ad2f2c9713e8aad4680d9bed7c3a9b9dc43 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:38:08 -0400 Subject: [PATCH 25/81] clean up isdragging --- .../PlannerComponents/PlannerCourse.tsx | 1 - .../PlannerComponents/RightClickContext.tsx | 21 ++++++++----------- src/components/Toolbox/ToolboxCourse.tsx | 6 +----- src/pages/Catalog.tsx | 1 - src/pages/Planner.tsx | 1 - 5 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/components/PlannerComponents/PlannerCourse.tsx b/src/components/PlannerComponents/PlannerCourse.tsx index 3d5f998..7d97a42 100644 --- a/src/components/PlannerComponents/PlannerCourse.tsx +++ b/src/components/PlannerComponents/PlannerCourse.tsx @@ -23,7 +23,6 @@ interface PlannerCourseProps { const PlannerCourse: React.FC = ({ course, index, - isDragging, setPlannerCourses, setToolboxCourses, semesterIndex, diff --git a/src/components/PlannerComponents/RightClickContext.tsx b/src/components/PlannerComponents/RightClickContext.tsx index ea71015..0b6a23b 100644 --- a/src/components/PlannerComponents/RightClickContext.tsx +++ b/src/components/PlannerComponents/RightClickContext.tsx @@ -41,8 +41,8 @@ const PlannerCourse: React.FC = ({ ...semester.courseList.slice(index + 1), ], } - : semester, - ), + : semester + ) ); }; @@ -76,7 +76,7 @@ const PlannerCourse: React.FC = ({ courseList: s.courseList.filter((_, idx) => idx !== index), creditsTotal: s.creditsTotal - courseCopy.data.credit_max, } - : s, + : s ); return updated.map((s, i) => i === semesterIndex @@ -85,7 +85,7 @@ const PlannerCourse: React.FC = ({ courseList: [...s.courseList, courseCopy], creditsTotal: s.creditsTotal + courseCopy.data.credit_max, } - : s, + : s ); }); }; @@ -103,12 +103,12 @@ const PlannerCourse: React.FC = ({ courseList: s.courseList.filter((_, idx) => idx !== index), creditsTotal: s.creditsTotal - courseToMove.data.credit_max, } - : s, + : s ); setToolboxCourses((toolboxPrev) => { const existingIndex = toolboxPrev.findIndex( - (entry) => entry.name === cleanedName, + (entry) => entry.name === cleanedName ); if (existingIndex !== -1) { const updatedToolbox = [...toolboxPrev]; @@ -132,8 +132,8 @@ const PlannerCourse: React.FC = ({ creditsTotal: s.creditsTotal - s.courseList[index].data.credit_max, } - : s, - ), + : s + ) ); }; @@ -175,10 +175,7 @@ const PlannerCourse: React.FC = ({ {/* Context Menu Content */} - + = ({ - name, - count, - isDragging, -}) => { +const ToolboxCourse: React.FC = ({ name, count }) => { return (
= ({ toolboxCourses, setToolboxCourses, - isDragging, searchResults, setSearchResults, searchPrompt, diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index 473bdad..99aa94d 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -13,7 +13,6 @@ interface PlannerProps { } const Planner: React.FC = ({ - isDragging, plannerCourses, setPlannerCourses, setToolboxCourses, From b7713be52eb8bfb0068027c04a0027f0c5ca6793 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:49:09 -0400 Subject: [PATCH 26/81] clean up base page formatting --- src/components/SearchBar/SeachBar.tsx | 2 +- src/pages/Catalog.tsx | 6 +++--- src/pages/HomePage.tsx | 5 +++-- src/pages/Planner.tsx | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/SearchBar/SeachBar.tsx b/src/components/SearchBar/SeachBar.tsx index 05428e7..0f3bf7c 100644 --- a/src/components/SearchBar/SeachBar.tsx +++ b/src/components/SearchBar/SeachBar.tsx @@ -191,7 +191,7 @@ const SearchBar: React.FC = ({ updateSearchResults }) => { )} {showDeptFilter && ( -
+
)} diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index 51c86b2..fe4be81 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -35,7 +35,7 @@ const Catalog: React.FC = ({ setFilters, }) => { return ( -
+ <>

Courses

= ({ />
-
+
@@ -65,7 +65,7 @@ const Catalog: React.FC = ({
+ ); }; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index baef277..428a99b 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -27,10 +27,11 @@ const HomePage = (props: HomePageProps) => { return (
-
+
Carpi Logo
-
+ +
= ({ }; return ( -
-
+
+
Carpi Logo
From 287528e4b7221c058e832088be1a22129b3730ea Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 17 Oct 2025 17:01:14 -0400 Subject: [PATCH 27/81] Update Toolbox.tsx --- src/components/Toolbox/Toolbox.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/Toolbox/Toolbox.tsx b/src/components/Toolbox/Toolbox.tsx index 4ca44e4..e7a17d9 100644 --- a/src/components/Toolbox/Toolbox.tsx +++ b/src/components/Toolbox/Toolbox.tsx @@ -57,7 +57,7 @@ const Toolbox: React.FC = ({ courses, isDragging }) => { }`} >
= ({ courses, isDragging }) => {
- {(provided, snapshot) => ( + {(provided) => (
{courses.map((course, index) => ( Date: Fri, 17 Oct 2025 17:04:42 -0400 Subject: [PATCH 28/81] navbar sticky on bottom on mobile --- src/components/Toolbox/Toolbox.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Toolbox/Toolbox.tsx b/src/components/Toolbox/Toolbox.tsx index e7a17d9..8a618e2 100644 --- a/src/components/Toolbox/Toolbox.tsx +++ b/src/components/Toolbox/Toolbox.tsx @@ -33,7 +33,7 @@ const Toolbox: React.FC = ({ courses, isDragging }) => { return (
@@ -105,9 +105,9 @@ const Toolbox: React.FC = ({ courses, isDragging }) => { )}
- - {!isDesktop && }
+ + {!isDesktop && }
); }; From 29eaec0a8dc3a815eabd2219fce0c5b27c27865f Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 17 Oct 2025 17:06:33 -0400 Subject: [PATCH 29/81] add padding to the planner section --- src/pages/Planner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index c27a3dd..040afbd 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -26,7 +26,7 @@ const Planner: React.FC = ({ }; return ( -
+
Carpi Logo
From cbbb7a93729f8424749da8fee0ad9e8c03f59605 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 17 Oct 2025 17:22:05 -0400 Subject: [PATCH 30/81] adjust styling for course types --- src/components/DraggableItem.tsx | 32 +++++++++++++----------- src/components/Toolbox/Toolbox.tsx | 6 ++--- src/components/Toolbox/ToolboxCourse.tsx | 9 ++----- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/components/DraggableItem.tsx b/src/components/DraggableItem.tsx index 947801c..45cf1a9 100644 --- a/src/components/DraggableItem.tsx +++ b/src/components/DraggableItem.tsx @@ -1,17 +1,17 @@ +import { Dispatch, SetStateAction } from "react"; import { Draggable } from "@hello-pangea/dnd"; +import { SemesterType } from "../types/interfaces/Semester.interface"; +import { CourseType, CourseEntry } from "../types/interfaces/Course.interface"; + import PlannerCourse from "./PlannerComponents/PlannerCourse"; import ToolboxCourse from "./Toolbox/ToolboxCourse"; -import { CourseType } from "../types/interfaces/Course.interface"; -import { Dispatch, SetStateAction } from "react"; -import { SemesterType } from "../types/interfaces/Semester.interface"; -import { CourseEntry } from "../types/interfaces/Course.interface"; interface DraggableItemProps { name: string; count: number; index: number; course: CourseType; - location: "toolbox" | "planner"; + location: string; setPlannerCourses: Dispatch> | null; setToolboxCourses: Dispatch> | null; semesterIndex: number | null; @@ -30,10 +30,14 @@ const DraggableItem: React.FC = ({ return ( {(provided, snapshot) => { + const isOverPlanner = + snapshot.isDragging && snapshot.draggingOver !== "toolbox"; + // create a mutable copy of the style object provided by the library const style = { ...provided.draggableProps.style, cursor: "grab", + width: isOverPlanner ? "fixed" : "auto", }; // unset top and let properties when dragging @@ -47,26 +51,24 @@ const DraggableItem: React.FC = ({ ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} + className="transition-all duration-200 ease-in-out" style={style} > - {location === "planner" && - setPlannerCourses && - setToolboxCourses && - semesterIndex !== null ? ( + {isOverPlanner || location === "planner" ? ( ) : ( )}
diff --git a/src/components/Toolbox/Toolbox.tsx b/src/components/Toolbox/Toolbox.tsx index 8a618e2..18d3224 100644 --- a/src/components/Toolbox/Toolbox.tsx +++ b/src/components/Toolbox/Toolbox.tsx @@ -45,8 +45,6 @@ const Toolbox: React.FC = ({ courses, isDragging }) => { /> )} - -
= ({ courses, isDragging }) => { : "translate-y-full" }`} > + +
= ({ courses, isDragging }) => {
{courses.map((course, index) => ( diff --git a/src/components/Toolbox/ToolboxCourse.tsx b/src/components/Toolbox/ToolboxCourse.tsx index a57ae3a..5ebd155 100644 --- a/src/components/Toolbox/ToolboxCourse.tsx +++ b/src/components/Toolbox/ToolboxCourse.tsx @@ -9,14 +9,9 @@ interface ToolboxCourseProps { const ToolboxCourse: React.FC = ({ name, count }) => { return ( -
+

{count}

From 4535ddfe73be93f87317088be6719f330b325bd0 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 17 Oct 2025 17:25:39 -0400 Subject: [PATCH 31/81] clean up plannerCourseHolder --- .../PlannerComponents/PlannerCourseHolder.tsx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/components/PlannerComponents/PlannerCourseHolder.tsx b/src/components/PlannerComponents/PlannerCourseHolder.tsx index 419e2a3..76b2f75 100644 --- a/src/components/PlannerComponents/PlannerCourseHolder.tsx +++ b/src/components/PlannerComponents/PlannerCourseHolder.tsx @@ -8,21 +8,15 @@ const PlannerCourseHolder: React.FC = ({ isHover, }) => { return ( - <> -
+
+ {isHover ? ( +

Drop it here!

+ ) : (

Try dragging a course from your Toolbox

- - {isHover && ( -
-

Drop it here!

-
- )} -
- + )} +
); }; From d272ec6c6e13200bac1bcc08cc44bbfdc91c98e4 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 17 Oct 2025 17:31:34 -0400 Subject: [PATCH 32/81] move add semester block to the title bar --- src/components/PlannerComponents/AddSemester.tsx | 14 ++++++-------- src/pages/Planner.tsx | 11 +++++++---- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/components/PlannerComponents/AddSemester.tsx b/src/components/PlannerComponents/AddSemester.tsx index 52f180e..53c56c0 100644 --- a/src/components/PlannerComponents/AddSemester.tsx +++ b/src/components/PlannerComponents/AddSemester.tsx @@ -19,14 +19,12 @@ const AddSemester: React.FC = ({ setPlannerCourses }) => { }; return ( - <> - - + ); }; diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index 040afbd..dc6d38c 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -31,8 +31,12 @@ const Planner: React.FC = ({ Carpi Logo
-

Planner

-
+
+

Planner

+ +
+ +
{plannerCourses.map((semester, index) => (
= ({
))} - -
+
); }; From c8aafc8353916925d0cab6323e3cdd3928d45b17 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 17 Oct 2025 17:33:07 -0400 Subject: [PATCH 33/81] Update Toolbox.tsx --- src/components/Toolbox/Toolbox.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Toolbox/Toolbox.tsx b/src/components/Toolbox/Toolbox.tsx index 18d3224..6de7e47 100644 --- a/src/components/Toolbox/Toolbox.tsx +++ b/src/components/Toolbox/Toolbox.tsx @@ -84,8 +84,7 @@ const Toolbox: React.FC = ({ courses, isDragging }) => {
{courses.map((course, index) => ( Date: Mon, 20 Oct 2025 16:41:48 -0400 Subject: [PATCH 34/81] handle eslint --- src/components/DraggableItem.tsx | 4 ++-- src/components/PlannerComponents/DeleteSemester.tsx | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/DraggableItem.tsx b/src/components/DraggableItem.tsx index 45cf1a9..87ca5f6 100644 --- a/src/components/DraggableItem.tsx +++ b/src/components/DraggableItem.tsx @@ -42,8 +42,8 @@ const DraggableItem: React.FC = ({ // unset top and let properties when dragging if (snapshot.isDragging) { - (style as any).top = undefined; - (style as any).left = undefined; + (style as React.CSSProperties).top = undefined; + (style as React.CSSProperties).left = undefined; } return ( diff --git a/src/components/PlannerComponents/DeleteSemester.tsx b/src/components/PlannerComponents/DeleteSemester.tsx index 9002fae..ceb4ba7 100644 --- a/src/components/PlannerComponents/DeleteSemester.tsx +++ b/src/components/PlannerComponents/DeleteSemester.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { SemesterType } from "../../types/interfaces/Semester.interface"; interface DeleteSemesterProps { semesterNumber: number; From 0c8f38d4bd9ecf0ad92d684b06c08adf465175b3 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:00:28 -0400 Subject: [PATCH 35/81] adjust dragging indications for desktop --- src/components/DraggableItem.tsx | 2 +- .../PlannerComponents/PlannerCourseHolder.tsx | 10 +++--- .../SemesterBlocks/DesktopSemesterBlock.tsx | 33 ++++++++++--------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/components/DraggableItem.tsx b/src/components/DraggableItem.tsx index 87ca5f6..740006e 100644 --- a/src/components/DraggableItem.tsx +++ b/src/components/DraggableItem.tsx @@ -41,7 +41,7 @@ const DraggableItem: React.FC = ({ }; // unset top and let properties when dragging - if (snapshot.isDragging) { + if (snapshot.isDragging && location === "toolbox") { (style as React.CSSProperties).top = undefined; (style as React.CSSProperties).left = undefined; } diff --git a/src/components/PlannerComponents/PlannerCourseHolder.tsx b/src/components/PlannerComponents/PlannerCourseHolder.tsx index 76b2f75..b234d3d 100644 --- a/src/components/PlannerComponents/PlannerCourseHolder.tsx +++ b/src/components/PlannerComponents/PlannerCourseHolder.tsx @@ -8,13 +8,13 @@ const PlannerCourseHolder: React.FC = ({ isHover, }) => { return ( -
+
{isHover ? ( -

Drop it here!

+

Drop the course!

) : ( -

- Try dragging a course from your Toolbox -

+

Drag a course here to add it to this semester

)}
); diff --git a/src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx b/src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx index dd6a88c..c59cf5f 100644 --- a/src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx +++ b/src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx @@ -41,21 +41,24 @@ const DesktopSemesterBlock: React.FC = ({ )} {isEmpty && isHovering && } - {!isEmpty && - semester.courseList.map((course, courseIndex) => ( - - ))} - {provided.placeholder} + {!isEmpty && ( + <> + {semester.courseList.map((course, courseIndex) => ( + + ))} + {provided.placeholder} + + )}
); }} From b2e6a2afdb5453bcc065119a55cfe5c947c3461c Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:17:20 -0400 Subject: [PATCH 36/81] optimize search and filter sections --- src/components/SearchBar/FilterPanel.tsx | 86 ---------------- .../SearchBar/FilterPanel/FilterPanel.tsx | 77 +++++++++++++++ .../{ => FilterPanel}/FilterSection.tsx | 4 +- src/components/SearchBar/SeachBar.tsx | 98 +++++++++---------- 4 files changed, 126 insertions(+), 139 deletions(-) delete mode 100644 src/components/SearchBar/FilterPanel.tsx create mode 100644 src/components/SearchBar/FilterPanel/FilterPanel.tsx rename src/components/SearchBar/{ => FilterPanel}/FilterSection.tsx (88%) diff --git a/src/components/SearchBar/FilterPanel.tsx b/src/components/SearchBar/FilterPanel.tsx deleted file mode 100644 index 7ba1a7f..0000000 --- a/src/components/SearchBar/FilterPanel.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import api from "../../axios"; -import { Filters } from "../../types/Filters"; -import FilterSection from "./FilterSection"; -import { useEffect, useState } from "react"; - -interface FilterPanelProps { - filters: { [key: string]: string[] }; - updateFilters: (category: keyof Filters, value: string) => void; -} - -const FilterPanel: React.FC = ({ - filters, - updateFilters, -}) => { - const [subjects, setSubjects] = useState([]); - const [attributes, setAttributes] = useState([]); - const [semesters, setSemesters] = useState([]); - - useEffect(() => { - console.log("getting filters"); - - const fetchSubjects = async () => { - const response = await api.get("/course/filter/values/departments"); - const subjects = response.data.map((subject: string, index: number) => { - return { - id: index, - code: subject, - }; - }); - setSubjects(subjects); - }; - - const fetchAttributes = async () => { - const response = await api.get("/course/filter/values/attributes"); - const attributes = response.data.map( - (attribute: string, index: number) => { - return { - id: index, - code: attribute, - }; - }, - ); - setAttributes(attributes); - }; - - const fetchSemesters = async () => { - const response = await api.get("/course/filter/values/semesters"); - const semesters = response.data.map((semester: string, index: number) => { - return { - id: index, - code: semester, - }; - }); - setSemesters(semesters); - }; - - fetchSubjects(); - fetchAttributes(); - fetchSemesters(); - }, []); - - return ( - <> - - - - - ); -}; - -export default FilterPanel; diff --git a/src/components/SearchBar/FilterPanel/FilterPanel.tsx b/src/components/SearchBar/FilterPanel/FilterPanel.tsx new file mode 100644 index 0000000..2e5d6d3 --- /dev/null +++ b/src/components/SearchBar/FilterPanel/FilterPanel.tsx @@ -0,0 +1,77 @@ +import api from "../../../axios"; +import { Filters } from "../../../types/Filters"; +import FilterSection from "./FilterSection"; +import { useEffect, useState } from "react"; + +interface FilterPanelProps { + filters: { [key: string]: string[] }; + updateFilters: (category: keyof Filters, value: string) => void; +} + +const formatApiData = (data: string[]) => { + return data.map((item: string, index: number) => ({ + id: index, + code: item, + })); +}; + +const FilterPanel: React.FC = ({ + filters, + updateFilters, +}) => { + const [subjects, setSubjects] = useState<{ id: number; code: string }[]>([]); + const [attributes, setAttributes] = useState<{ id: number; code: string }[]>( + [] + ); + const [semesters, setSemesters] = useState<{ id: number; code: string }[]>( + [] + ); + + useEffect(() => { + console.log("getting filters"); + + const fetchAllFilters = async () => { + try { + const [subjectsResponse, attributesResponse, semestersResponse] = + await Promise.all([ + api.get("/course/filter/values/departments"), + api.get("/course/filter/values/attributes"), + api.get("/course/filter/values/semesters"), + ]); + + setSubjects(formatApiData(subjectsResponse.data)); + setAttributes(formatApiData(attributesResponse.data)); + setSemesters(formatApiData(semestersResponse.data)); + } catch (error) { + console.error("Failed to fetch filters:", error); + } + }; + + fetchAllFilters(); + }, []); + + return ( + <> + + + + + ); +}; + +export default FilterPanel; diff --git a/src/components/SearchBar/FilterSection.tsx b/src/components/SearchBar/FilterPanel/FilterSection.tsx similarity index 88% rename from src/components/SearchBar/FilterSection.tsx rename to src/components/SearchBar/FilterPanel/FilterSection.tsx index 16c1e13..8b3ee8d 100644 --- a/src/components/SearchBar/FilterSection.tsx +++ b/src/components/SearchBar/FilterPanel/FilterSection.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Filters } from "../../types/Filters"; +import { Filters } from "../../../types/Filters"; interface TagProp { id: number; @@ -21,7 +21,7 @@ const FilterSection: React.FC = ({ }) => { return (
-

{sectionName}

+

{sectionName}

{tags.map((tag) => ( -
+ +
- {showFilter && ( - - )} + {showFilter && ( + + )} - {showDeptFilter && ( -
- -
- )} + {showDeptFilter && ( +
+ +
+ )} +
); }; From 5e3acae6f6423cf024968bcbbc481d3cba8c3fa2 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:18:26 -0400 Subject: [PATCH 37/81] chosen tag accessibility formating --- src/components/SearchBar/ChosenTag.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/SearchBar/ChosenTag.tsx b/src/components/SearchBar/ChosenTag.tsx index f5c9c4e..e01d12b 100644 --- a/src/components/SearchBar/ChosenTag.tsx +++ b/src/components/SearchBar/ChosenTag.tsx @@ -8,14 +8,19 @@ interface ChosenTagProp { const ChosenTag: React.FC = ({ name, onRemove }) => { return ( - <> -
+ {name} +
- + + +
); }; From a7012ba7077825d597cf3e091db3720b07f83861 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:52:13 -0400 Subject: [PATCH 38/81] fix spacing in filter and search bar --- .../SearchBar/FilterPanel/FilterPanel.tsx | 4 +- .../SearchBar/FilterPanel/FilterSection.tsx | 4 +- src/components/SearchBar/SeachBar.tsx | 88 ++++++++++--------- 3 files changed, 49 insertions(+), 47 deletions(-) diff --git a/src/components/SearchBar/FilterPanel/FilterPanel.tsx b/src/components/SearchBar/FilterPanel/FilterPanel.tsx index 2e5d6d3..fe9a9de 100644 --- a/src/components/SearchBar/FilterPanel/FilterPanel.tsx +++ b/src/components/SearchBar/FilterPanel/FilterPanel.tsx @@ -51,7 +51,7 @@ const FilterPanel: React.FC = ({ }, []); return ( - <> +
= ({ selected={filters.Semesters} updateFilters={updateFilters} /> - +
); }; diff --git a/src/components/SearchBar/FilterPanel/FilterSection.tsx b/src/components/SearchBar/FilterPanel/FilterSection.tsx index 8b3ee8d..97dc325 100644 --- a/src/components/SearchBar/FilterPanel/FilterSection.tsx +++ b/src/components/SearchBar/FilterPanel/FilterSection.tsx @@ -21,8 +21,8 @@ const FilterSection: React.FC = ({ }) => { return (
-

{sectionName}

-
+

{sectionName}

+
{tags.map((tag) => ( +
+
+ {allActiveFilters.map((tag) => ( + + ))}
- {showFilter && ( - - )} - - {showDeptFilter && ( -
- -
- )} +
+ + {showFilter && ( + + )} + + {showDeptFilter && ( +
+ +
+ )}
); }; From 5790dc001164317c2567e93290201d54fea37e70 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:52:27 -0400 Subject: [PATCH 39/81] add filter panel popup --- .../FilterPanel/FilterPanelPopup.tsx | 21 +++++++++++++++++++ src/components/SearchBar/SeachBar.tsx | 14 ++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 src/components/SearchBar/FilterPanel/FilterPanelPopup.tsx diff --git a/src/components/SearchBar/FilterPanel/FilterPanelPopup.tsx b/src/components/SearchBar/FilterPanel/FilterPanelPopup.tsx new file mode 100644 index 0000000..b8fc199 --- /dev/null +++ b/src/components/SearchBar/FilterPanel/FilterPanelPopup.tsx @@ -0,0 +1,21 @@ +import { Filters } from "../../../types/Filters"; +import FilterPanel from "./FilterPanel"; + +interface FilterPanelPopupProps { + filters: { [key: string]: string[] }; + updateFilters: (category: keyof Filters, value: string) => void; + setShowFilter: (show: boolean) => void; +} + +const FilterPanelPopup: React.FC = ({ + filters, + updateFilters, +}) => { + return ( +
+ +
+ ); +}; + +export default FilterPanelPopup; diff --git a/src/components/SearchBar/SeachBar.tsx b/src/components/SearchBar/SeachBar.tsx index 393aca4..f999312 100644 --- a/src/components/SearchBar/SeachBar.tsx +++ b/src/components/SearchBar/SeachBar.tsx @@ -7,6 +7,7 @@ import { Filters } from "../../types/Filters"; import DepartmentFilters from "../Department-Filters"; import { CourseType } from "../../types/interfaces/Course.interface.ts"; import useIsDesktop from "../../hooks/useIsDesktop.ts"; +import FilterPanelPopup from "./FilterPanel/FilterPanelPopup.tsx"; interface SearchBarProps { updateSearchResults: (results: CourseType[]) => void; @@ -184,9 +185,16 @@ const SearchBar: React.FC = ({
- {showFilter && ( - - )} + {showFilter && + (isDesktop ? ( + + ) : ( + + ))} {showDeptFilter && (
From baab001c36ac3578d1d0d0e444e4c23def6b6cce Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:55:54 -0400 Subject: [PATCH 40/81] add filter icon for desktop --- src/components/SearchBar/SeachBar.tsx | 34 +++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/components/SearchBar/SeachBar.tsx b/src/components/SearchBar/SeachBar.tsx index f999312..3630ba0 100644 --- a/src/components/SearchBar/SeachBar.tsx +++ b/src/components/SearchBar/SeachBar.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useRef, useMemo } from "react"; -import { IoClose, IoSearchOutline } from "react-icons/io5"; +import { IoClose, IoSearchOutline, IoFilter } from "react-icons/io5"; import api from "../../axios"; import FilterPanel from "./FilterPanel/FilterPanel.tsx"; import ChosenTag from "./ChosenTag"; @@ -141,8 +141,8 @@ const SearchBar: React.FC = ({ return (
-
- +
+ = ({ }} /> - {showFilter && ( + {showFilter ? ( { setShowFilter(false); // Uses prop setSearchPrompt(""); // Uses prop }} + className="w-5 h-5" + /> + ) : ( + { + setShowFilter(true); // Uses prop + }} + className="w-5 h-5" /> )}
@@ -175,14 +183,16 @@ const SearchBar: React.FC = ({ ))}
- + {!isDesktop && ( + + )}
{showFilter && From e76db67aabd528fe6123ead8420da926546a4ea6 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:58:13 -0400 Subject: [PATCH 41/81] remove show options text --- src/components/SearchBar/SeachBar.tsx | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/components/SearchBar/SeachBar.tsx b/src/components/SearchBar/SeachBar.tsx index 3630ba0..5b10e0a 100644 --- a/src/components/SearchBar/SeachBar.tsx +++ b/src/components/SearchBar/SeachBar.tsx @@ -176,23 +176,10 @@ const SearchBar: React.FC = ({ )}
-
-
- {allActiveFilters.map((tag) => ( - - ))} -
- - {!isDesktop && ( - - )} +
+ {allActiveFilters.map((tag) => ( + + ))}
{showFilter && From ab8054ce04da6a567ef08196eb148bdc646b22a7 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:10:33 -0400 Subject: [PATCH 42/81] adjust panel spacing --- src/components/Course/Course.tsx | 88 +++++++++++++-------------- src/components/SearchBar/SeachBar.tsx | 2 +- src/pages/Catalog.tsx | 34 +++++------ 3 files changed, 60 insertions(+), 64 deletions(-) diff --git a/src/components/Course/Course.tsx b/src/components/Course/Course.tsx index b699bdb..55669aa 100644 --- a/src/components/Course/Course.tsx +++ b/src/components/Course/Course.tsx @@ -38,12 +38,12 @@ const Course: React.FC = ({ setToolboxCourses((prevCourses) => { const existingIndex = prevCourses.findIndex( - (c) => c.name === courseDisplay, + (c) => c.name === courseDisplay ); if (existingIndex !== -1) { return prevCourses.map((c, i) => - i === existingIndex ? { ...c, count: c.count + 1 } : c, + i === existingIndex ? { ...c, count: c.count + 1 } : c ); } else { return [ @@ -57,56 +57,54 @@ const Course: React.FC = ({ toolboxCourses[toolboxCourses.findIndex((c) => c.name === courseDisplay)]; const courseCount = toolboxCourse ? toolboxCourse.count : undefined; return ( - <> +
-
-

{courseCount}

-
-
-
-
- - {course.dept} - {course.code_num} - -

{toTitleCase(course.title)}

-
-
- - {course.attr_list?.split(",").map((attr, index) => { - return ; - })} - {course.sem_list?.split(",").map((semester, index) => { - return ; - })} -
+

{courseCount}

+
+ +
+
+
+ + {course.dept} + {course.code_num} + +

{toTitleCase(course.title)}

-
- +
+ + {course.attr_list?.split(",").map((attr, index) => { + return ; + })} + {course.sem_list?.split(",").map((semester, index) => { + return ; + })}
-
- - {course.desc_text.trim() === "" - ? "Empty Description" - : course.desc_text} - +
+
- +
+ + {course.desc_text.trim() === "" + ? "Empty Description" + : course.desc_text} + +
+
); }; diff --git a/src/components/SearchBar/SeachBar.tsx b/src/components/SearchBar/SeachBar.tsx index 5b10e0a..5f5c91d 100644 --- a/src/components/SearchBar/SeachBar.tsx +++ b/src/components/SearchBar/SeachBar.tsx @@ -140,7 +140,7 @@ const SearchBar: React.FC = ({ }, [filters]); return ( -
+
= ({ setFilters, }) => { return ( - <> +
-

Courses

+

Courses

= ({ />
-
-
- {searchResults?.map((course: CourseType, index: number) => ( - - ))} + {searchResults.length > 0 && ( +
+
+ {searchResults?.map((course: CourseType, index: number) => ( + + ))} +
-
- -
); }; From a13771879839fccbb46749a9405654494034d579 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:12:48 -0400 Subject: [PATCH 43/81] standardize spacing on the page --- src/pages/Catalog.tsx | 2 +- src/pages/HomePage.tsx | 4 ++-- src/pages/Planner.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index 5c88617..8c528a4 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -35,7 +35,7 @@ const Catalog: React.FC = ({ setFilters, }) => { return ( -
+

Courses

{ const isDesktop = useIsDesktop(); return ( -
+
Carpi Logo
-
+
= ({ }; return ( -
+
Carpi Logo
From 6f39e802067ac0d05d904f2e55e724de71be5468 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:13:07 -0400 Subject: [PATCH 44/81] remove debug colors --- src/pages/Catalog.tsx | 2 +- src/pages/Planner.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index 8c528a4..5bc2b4d 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -35,7 +35,7 @@ const Catalog: React.FC = ({ setFilters, }) => { return ( -
+

Courses

= ({ }; return ( -
+
Carpi Logo
From 60cc96bc4b58b85b612f0c2b7a7a1ab9bbff3a3d Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:18:47 -0400 Subject: [PATCH 45/81] more space standardizations --- src/pages/HomePage.tsx | 4 ++-- src/pages/Planner.tsx | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 24d6b09..ffa1e8d 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -26,12 +26,12 @@ const HomePage = (props: HomePageProps) => { const isDesktop = useIsDesktop(); return ( -
+
Carpi Logo
-
+
= ({ }; return ( -
-
+
+
Carpi Logo
-
-

Planner

- -
+
+
+

Planner

+ +
-
{plannerCourses.map((semester, index) => (
Date: Tue, 21 Oct 2025 17:20:11 -0400 Subject: [PATCH 46/81] add padding to course place holder --- src/components/PlannerComponents/PlannerCourseHolder.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PlannerComponents/PlannerCourseHolder.tsx b/src/components/PlannerComponents/PlannerCourseHolder.tsx index b234d3d..969a8d5 100644 --- a/src/components/PlannerComponents/PlannerCourseHolder.tsx +++ b/src/components/PlannerComponents/PlannerCourseHolder.tsx @@ -9,7 +9,7 @@ const PlannerCourseHolder: React.FC = ({ }) => { return (
{isHover ? (

Drop the course!

From 4e3ecd19017014a34c5b1cd95d3476fc52302ea3 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:23:11 -0400 Subject: [PATCH 47/81] fix department button colors --- src/components/Department-Filters.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/Department-Filters.tsx b/src/components/Department-Filters.tsx index f39d5ee..83d36c1 100644 --- a/src/components/Department-Filters.tsx +++ b/src/components/Department-Filters.tsx @@ -66,16 +66,15 @@ const DepartmentFilters: React.FC = ({
{isDesktop && ( diff --git a/src/types/Filters.ts b/src/types/Filters.ts index bbb4d1a..c3ea99e 100644 --- a/src/types/Filters.ts +++ b/src/types/Filters.ts @@ -3,3 +3,8 @@ export type Filters = { Attributes: string[]; Semesters: string[]; }; + +export type FilterData = { + id: number; + code: string; +}; From dd87039ffea5e697461e1dcbea8273a185f96a90 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:05:03 -0400 Subject: [PATCH 49/81] disable scrolling when using dnd --- src/hooks/usePlannerDragAndDrop.ts | 18 ++++++++++-------- src/index.css | 8 ++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/hooks/usePlannerDragAndDrop.ts b/src/hooks/usePlannerDragAndDrop.ts index fa36db8..f3c9df2 100644 --- a/src/hooks/usePlannerDragAndDrop.ts +++ b/src/hooks/usePlannerDragAndDrop.ts @@ -37,10 +37,12 @@ export const usePlannerDragAndDrop = ({ setIsDragging, }: UsePlannerDragAndDropProps) => { const onDragStart = () => { + document.body.classList.add("no-scroll-during-drag"); setIsDragging(true); }; const onDragEnd = (result: DropResult) => { + document.body.classList.remove("no-scroll-during-drag"); setIsDragging(false); const { source, destination, draggableId } = result; if (!destination) return; @@ -51,7 +53,7 @@ export const usePlannerDragAndDrop = ({ // Reorder in toolbox if (sInd === "toolbox" && dInd === "toolbox") { setToolboxCourses((prev) => - reorder(prev, source.index, destination.index), + reorder(prev, source.index, destination.index) ); return; } @@ -59,7 +61,7 @@ export const usePlannerDragAndDrop = ({ // Delete from toolbox if (dInd === "garbage" && sInd === "toolbox") { setToolboxCourses((prev) => - prev.filter((_, index) => index !== source.index), + prev.filter((_, index) => index !== source.index) ); return; } @@ -75,11 +77,11 @@ export const usePlannerDragAndDrop = ({ courseList: reorder( semester.courseList, source.index, - destination.index, + destination.index ), } - : semester, - ), + : semester + ) ); return; } @@ -144,14 +146,14 @@ export const usePlannerDragAndDrop = ({ }; } return semester; - }), + }) ); setToolboxCourses((prev) => prev .map((c) => - c.name === courseToClone.name ? { ...c, count: c.count - 1 } : c, + c.name === courseToClone.name ? { ...c, count: c.count - 1 } : c ) - .filter((c) => c.count > 0), + .filter((c) => c.count > 0) ); } return; diff --git a/src/index.css b/src/index.css index 61ddf22..8ac0e4f 100644 --- a/src/index.css +++ b/src/index.css @@ -14,6 +14,14 @@ body { scrollbar-width: thin; } +body.no-scroll-during-drag { + /* This prevents the entire page from scrolling */ + overflow: hidden !important; + + /* This can also help prevent other touch behaviors on some browsers */ + touch-action: none !important; +} + /* scrollbar for webkit browsers */ ::-webkit-scrollbar { width: 12px; From 59a8aaf9e5a65b4522110dd9d83553a327fae41a Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:05:21 -0400 Subject: [PATCH 50/81] fix course type transition on mobile --- src/components/DraggableItem.tsx | 17 ++---- .../PlannerComponents/PlannerCourse.tsx | 4 +- src/components/Toolbox/Toolbox.tsx | 8 +-- src/components/Toolbox/ToolboxCourse.tsx | 56 +++++++++++++++++-- 4 files changed, 63 insertions(+), 22 deletions(-) diff --git a/src/components/DraggableItem.tsx b/src/components/DraggableItem.tsx index 740006e..405569a 100644 --- a/src/components/DraggableItem.tsx +++ b/src/components/DraggableItem.tsx @@ -30,31 +30,23 @@ const DraggableItem: React.FC = ({ return ( {(provided, snapshot) => { - const isOverPlanner = - snapshot.isDragging && snapshot.draggingOver !== "toolbox"; + // const isOverPlanner = + // snapshot.isDragging && snapshot.draggingOver !== "toolbox"; - // create a mutable copy of the style object provided by the library const style = { ...provided.draggableProps.style, cursor: "grab", - width: isOverPlanner ? "fixed" : "auto", + // background: snapshot.isDragging ? "#891726" : undefined, }; - // unset top and let properties when dragging - if (snapshot.isDragging && location === "toolbox") { - (style as React.CSSProperties).top = undefined; - (style as React.CSSProperties).left = undefined; - } - return (
- {isOverPlanner || location === "planner" ? ( + {location !== "toolbox" ? ( = ({ /> ) : ( >; setToolboxCourses: Dispatch>; @@ -207,7 +207,7 @@ const PlannerCourse: React.FC = ({ return ( -
+
diff --git a/src/components/Toolbox/Toolbox.tsx b/src/components/Toolbox/Toolbox.tsx index 6de7e47..d4a8eed 100644 --- a/src/components/Toolbox/Toolbox.tsx +++ b/src/components/Toolbox/Toolbox.tsx @@ -46,12 +46,12 @@ const Toolbox: React.FC = ({ courses, isDragging }) => { )}
diff --git a/src/components/Toolbox/ToolboxCourse.tsx b/src/components/Toolbox/ToolboxCourse.tsx index 5ebd155..c93241b 100644 --- a/src/components/Toolbox/ToolboxCourse.tsx +++ b/src/components/Toolbox/ToolboxCourse.tsx @@ -1,21 +1,69 @@ import React from "react"; +import { CourseType } from "../../types/interfaces/Course.interface"; +import { MdDragIndicator } from "react-icons/md"; interface ToolboxCourseProps { name: string; count: number; index: number; isDragging: boolean; + course: CourseType; } -const ToolboxCourse: React.FC = ({ name, count }) => { +const ToolboxCourse: React.FC = ({ + course, + count, + isDragging, +}) => { + const toTitleCase = (str: string) => { + return str + .toLowerCase() + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); + }; + + if (isDragging) { + return ( +
+
+ +
+ + {course.dept} + {course.code_num} + +

{toTitleCase(course.title)}

+
+
+ +
+
+

{course.credit_max}

+
+
+
+ ); + } + return ( -
+

{count}

- {name} +
+ + {course.dept} + {course.code_num} + +

{toTitleCase(course.title)}

+
); }; From a176938c3de1bc10341f90d140b0e34eacbe7dfb Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:16:41 -0400 Subject: [PATCH 51/81] adjust toolbox course styling --- src/components/Toolbox/ToolboxCourse.tsx | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/components/Toolbox/ToolboxCourse.tsx b/src/components/Toolbox/ToolboxCourse.tsx index c93241b..ffe5581 100644 --- a/src/components/Toolbox/ToolboxCourse.tsx +++ b/src/components/Toolbox/ToolboxCourse.tsx @@ -11,9 +11,10 @@ interface ToolboxCourseProps { } const ToolboxCourse: React.FC = ({ - course, + name, count, isDragging, + course, }) => { const toTitleCase = (str: string) => { return str @@ -49,7 +50,9 @@ const ToolboxCourse: React.FC = ({ } return ( -
+
= ({ >

{count}

-
- - {course.dept} - {course.code_num} - -

{toTitleCase(course.title)}

-
+ {name}
); }; From dea178484679a915a9c5a28a857d9cd0509cd8b5 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:16:56 -0400 Subject: [PATCH 52/81] add flex gap to toolbox courses --- src/components/Toolbox/Toolbox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Toolbox/Toolbox.tsx b/src/components/Toolbox/Toolbox.tsx index d4a8eed..dc3f0ab 100644 --- a/src/components/Toolbox/Toolbox.tsx +++ b/src/components/Toolbox/Toolbox.tsx @@ -84,7 +84,7 @@ const Toolbox: React.FC = ({ courses, isDragging }) => {
{courses.map((course, index) => ( Date: Thu, 23 Oct 2025 22:17:08 -0400 Subject: [PATCH 53/81] remove z-500 on planner course --- src/components/PlannerComponents/PlannerCourse.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PlannerComponents/PlannerCourse.tsx b/src/components/PlannerComponents/PlannerCourse.tsx index 38031ee..63b270f 100644 --- a/src/components/PlannerComponents/PlannerCourse.tsx +++ b/src/components/PlannerComponents/PlannerCourse.tsx @@ -207,7 +207,7 @@ const PlannerCourse: React.FC = ({ return ( -
+
From 6c62e4bb8f3177d6400216063ca561476f2da78d Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:24:24 -0400 Subject: [PATCH 54/81] add move from planner to toolbox --- src/App.tsx | 1 + src/hooks/usePlannerDragAndDrop.ts | 53 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/App.tsx b/src/App.tsx index f1e3bb1..433a154 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -44,6 +44,7 @@ export default function App() { // The complex logic is now neatly contained in the hook const { onDragStart, onDragEnd } = usePlannerDragAndDrop({ toolboxCourses, + plannerCourses, setToolboxCourses, setPlannerCourses, setIsDragging, diff --git a/src/hooks/usePlannerDragAndDrop.ts b/src/hooks/usePlannerDragAndDrop.ts index f3c9df2..ae2583f 100644 --- a/src/hooks/usePlannerDragAndDrop.ts +++ b/src/hooks/usePlannerDragAndDrop.ts @@ -25,6 +25,7 @@ const cloneCourse = (course: CourseEntry): CourseEntry => { interface UsePlannerDragAndDropProps { toolboxCourses: CourseEntry[]; + plannerCourses: SemesterType[]; setToolboxCourses: Dispatch>; setPlannerCourses: Dispatch>; setIsDragging: Dispatch>; @@ -32,6 +33,7 @@ interface UsePlannerDragAndDropProps { export const usePlannerDragAndDrop = ({ toolboxCourses, + plannerCourses, setToolboxCourses, setPlannerCourses, setIsDragging, @@ -182,6 +184,57 @@ export const usePlannerDragAndDrop = ({ }); return; } + + // Move from planner to toolbox + if (sInd.startsWith("planner-") && dInd === "toolbox") { + const sourceSemesterIndex = parseInt(sInd.split("-")[1]) - 1; + + if (sourceSemesterIndex >= plannerCourses.length) return; + const sourceSemester = plannerCourses[sourceSemesterIndex]; + if (source.index >= sourceSemester.courseList.length) return; + + const courseToMove = sourceSemester.courseList[source.index]; + if (!courseToMove) return; + + setPlannerCourses((prev) => + prev.map((semester, idx) => { + if (idx !== sourceSemesterIndex) return semester; + return { + ...semester, + courseList: semester.courseList.filter( + (_, index) => index !== source.index + ), + creditsTotal: semester.creditsTotal - courseToMove.data.credit_max, + }; + }) + ); + + const cleanedName = courseToMove.name.split("-")[0]; + + setToolboxCourses((prev) => { + const existingIndex = prev.findIndex((c) => c.name === cleanedName); + + const courseForToolbox: CourseEntry = { + ...courseToMove, + name: cleanedName, + }; + + if (existingIndex !== -1) { + const updated = [...prev]; + updated[existingIndex] = { + ...updated[existingIndex], + count: updated[existingIndex].count + courseForToolbox.count, + }; + return updated; + } else { + // It's a new course for the toolbox, add it + const newToolboxList = [...prev]; + newToolboxList.splice(destination.index, 0, courseForToolbox); + return newToolboxList; + } + }); + return; + } }; return { onDragStart, onDragEnd }; From 31ffd4afb54f633a5997371211152bf1436be234 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:53:37 -0400 Subject: [PATCH 55/81] split up planner course page actions --- src/components/DraggableItem.tsx | 1 + .../PlannerComponents/PlannerCourse.tsx | 202 ++++-------------- .../PlannerComponents/PlannerOptionsPopup.tsx | 44 ++++ src/hooks/usePlannerCourseOptions.tsx | 179 ++++++++++++++++ 4 files changed, 268 insertions(+), 158 deletions(-) create mode 100644 src/components/PlannerComponents/PlannerOptionsPopup.tsx create mode 100644 src/hooks/usePlannerCourseOptions.tsx diff --git a/src/components/DraggableItem.tsx b/src/components/DraggableItem.tsx index 405569a..0a31d11 100644 --- a/src/components/DraggableItem.tsx +++ b/src/components/DraggableItem.tsx @@ -49,6 +49,7 @@ const DraggableItem: React.FC = ({ {location !== "toolbox" ? ( >; setToolboxCourses: Dispatch>; semesterIndex: number; + count: number; } const PlannerCourse: React.FC = ({ course, + count, index, setPlannerCourses, setToolboxCourses, @@ -35,179 +39,51 @@ const PlannerCourse: React.FC = ({ setOpenPopup((prev) => !prev); }; - const handleClickOutside = (event: MouseEvent) => { - if ( - componentRef.current && - event.target instanceof Node && - !componentRef.current.contains(event.target) - ) { - setOpenPopup(false); - } + const handleSelect = (action: () => void) => { + action(); + setOpenPopup(false); }; - const handleDuplicate = () => { - setPlannerCourses((prev) => - prev.map((semester, i) => - i === semesterIndex - 1 - ? { - ...semester, - creditsTotal: - semester.creditsTotal + - semester.courseList[index].data.credit_max, - courseList: [ - ...semester.courseList.slice(0, index + 1), - { - name: getNextName(semester.courseList[index].name), - count: semester.courseList[index].count, - data: semester.courseList[index].data, - }, - ...semester.courseList.slice(index + 1), - ], - } - : semester - ) - ); - }; - - const getNextName = (originalName: string): string => { - const [base, tag] = originalName.split("-"); - - if (!tag) { - return `${base}-A`; - } - - const nextTag = incrementTag(tag); - return `${base}-${nextTag}`; - }; - - const incrementTag = (tag: string): string => { - const chars = tag.toUpperCase().split(""); - let carry = true; + const { + handleDuplicate, + handleMoveNext, + handleMoveToolbox, + handleDelete, + toTitleCase, + } = usePlannerCourse({ + setPlannerCourses: setPlannerCourses, + setToolboxCourses: setToolboxCourses, + semesterIndex: semesterIndex, + courseIndex: index, + course: course, + name: course.title, + count: count, + }); - for (let i = chars.length - 1; i >= 0 && carry; i--) { - if (chars[i] === "Z") { - chars[i] = "A"; - } else { - chars[i] = String.fromCharCode(chars[i].charCodeAt(0) + 1); - carry = false; + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + componentRef.current && + !componentRef.current.contains(event.target as Node) + ) { + setOpenPopup(false); } - } - - if (carry) { - chars.unshift("A"); - } - - return chars.join(""); - }; - - const handleMoveNext = () => { - setPlannerCourses((prev) => { - const courseCopy = { - name: prev[semesterIndex - 1].courseList[index].name, - count: prev[semesterIndex - 1].courseList[index].count, - data: prev[semesterIndex - 1].courseList[index].data, - }; - - const updatedPlanner = prev.map((semester, i) => - i === semesterIndex - 1 - ? { - ...semester, - courseList: semester.courseList.filter((_, idx) => idx !== index), - creditsTotal: - semester.creditsTotal - - prev[semesterIndex - 1].courseList[index].data.credit_max, - } - : semester - ); - - return updatedPlanner.map((semester, i) => - i === semesterIndex - ? { - ...semester, - courseList: [...semester.courseList, courseCopy], - creditsTotal: semester.creditsTotal + courseCopy.data.credit_max, - } - : semester - ); - }); - }; - - const handleMoveToolbox = () => { - setPlannerCourses((prev) => { - const courseToMove = prev[semesterIndex - 1].courseList[index]; - - const cleanedName = courseToMove.name.split("-")[0]; - - const cleanedCourse = { - ...courseToMove, - name: cleanedName, - }; - - const updatedPlanner = prev.map((semester, i) => - i === semesterIndex - 1 - ? { - ...semester, - courseList: semester.courseList.filter((_, idx) => idx !== index), - creditsTotal: - semester.creditsTotal - courseToMove.data.credit_max, - } - : semester - ); - - setToolboxCourses((toolboxPrev) => { - const existingIndex = toolboxPrev.findIndex( - (entry) => entry.name === cleanedName - ); - - if (existingIndex !== -1) { - const updated = [...toolboxPrev]; - updated[existingIndex].count += cleanedCourse.count; - return updated; - } else { - return [...toolboxPrev, cleanedCourse]; - } - }); - - return updatedPlanner; - }); - }; - - const handleDelete = () => { - setPlannerCourses((prev) => - prev.map((semester, i) => - i === semesterIndex - 1 - ? { - ...semester, - courseList: semester.courseList.filter((_, idx) => idx !== index), - creditsTotal: - semester.creditsTotal - - semester.courseList[index].data.credit_max, - } - : semester - ) - ); - }; + }; - useEffect(() => { document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, []); - const toTitleCase = (str: string) => { - return str - .toLowerCase() - .split(" ") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" "); - }; - /* Right-Click Context Menu */ return ( -
+
@@ -230,6 +106,16 @@ const PlannerCourse: React.FC = ({ className="cursor-pointer text-2xl" />
+ + {openPopup && ( + + )}
diff --git a/src/components/PlannerComponents/PlannerOptionsPopup.tsx b/src/components/PlannerComponents/PlannerOptionsPopup.tsx new file mode 100644 index 0000000..f4130cb --- /dev/null +++ b/src/components/PlannerComponents/PlannerOptionsPopup.tsx @@ -0,0 +1,44 @@ +interface PlannerOptionsPopupProps { + handleSelect: (action: () => void) => void; + handleDuplicate: () => void; + handleMoveNext: () => void; + handleMoveToolbox: () => void; + handleDelete: () => void; +} + +const PlannerOptionsPopup: React.FC = ({ + handleSelect, + ...actions +}) => { + return ( +
+ + +
+ + +
+ ); +}; + +export default PlannerOptionsPopup; diff --git a/src/hooks/usePlannerCourseOptions.tsx b/src/hooks/usePlannerCourseOptions.tsx new file mode 100644 index 0000000..65c28aa --- /dev/null +++ b/src/hooks/usePlannerCourseOptions.tsx @@ -0,0 +1,179 @@ +import { Dispatch, SetStateAction } from "react"; +import { SemesterType } from "../types/interfaces/Semester.interface"; +import { CourseEntry, CourseType } from "../types/interfaces/Course.interface"; + +interface UsePlannerCourseProps { + setPlannerCourses: Dispatch>; + setToolboxCourses: Dispatch>; + semesterIndex: number; // 1-based index + courseIndex: number; // 0-based index + course: CourseType; + name: string; + count: number; +} + +export const usePlannerCourse = ({ + setPlannerCourses, + setToolboxCourses, + semesterIndex, + courseIndex, + course, + name, + count, +}: UsePlannerCourseProps) => { + // --- Helper Functions --- + + const getNextName = (originalName: string): string => { + const [base, tag] = originalName.split("-"); + if (!tag) { + return `${base}-A`; + } + const nextTag = incrementTag(tag); + return `${base}-${nextTag}`; + }; + + const incrementTag = (tag: string): string => { + const chars = tag.toUpperCase().split(""); + let carry = true; + for (let i = chars.length - 1; i >= 0 && carry; i--) { + if (chars[i] === "Z") { + chars[i] = "A"; + } else { + chars[i] = String.fromCharCode(chars[i].charCodeAt(0) + 1); + carry = false; + } + } + if (carry) { + chars.unshift("A"); + } + return chars.join(""); + }; + + const toTitleCase = (str: string): string => { + if (!str) return ""; + return str + .toLowerCase() + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); + }; + + // --- Handler Functions --- + + const handleDuplicate = () => { + setPlannerCourses((prev) => + prev.map((semester, i) => + i === semesterIndex - 1 // 1-based index + ? { + ...semester, + creditsTotal: semester.creditsTotal + course.credit_max, + courseList: [ + ...semester.courseList.slice(0, courseIndex + 1), + { + name: getNextName(name), // Use prop + count: count, // Use prop + data: course, // Use prop + }, + ...semester.courseList.slice(courseIndex + 1), + ], + } + : semester + ) + ); + }; + + const handleMoveNext = () => { + setPlannerCourses((prev) => { + const courseCopy = { name, count, data: course }; + + const updatedPlanner = prev.map((semester, i) => + i === semesterIndex - 1 + ? { + ...semester, + courseList: semester.courseList.filter( + (_, idx) => idx !== courseIndex + ), + creditsTotal: semester.creditsTotal - course.credit_max, + } + : semester + ); + + // Add to next semester + // Note: This will not work if it's the last semester + return updatedPlanner.map((semester, i) => + i === semesterIndex // 1-based index becomes 0-based index of next sem + ? { + ...semester, + courseList: [...semester.courseList, courseCopy], + creditsTotal: semester.creditsTotal + courseCopy.data.credit_max, + } + : semester + ); + }); + }; + + const handleMoveToolbox = () => { + setPlannerCourses((prev) => { + const courseToMove = { name, count, data: course }; + const cleanedName = courseToMove.name.split("-")[0]; + const cleanedCourse = { + ...courseToMove, + name: cleanedName, + }; + + const updatedPlanner = prev.map((semester, i) => + i === semesterIndex - 1 + ? { + ...semester, + courseList: semester.courseList.filter( + (_, idx) => idx !== courseIndex + ), + creditsTotal: + semester.creditsTotal - courseToMove.data.credit_max, + } + : semester + ); + + setToolboxCourses((toolboxPrev) => { + const existingIndex = toolboxPrev.findIndex( + (entry) => entry.name === cleanedName + ); + + if (existingIndex !== -1) { + const updated = [...toolboxPrev]; + updated[existingIndex].count += cleanedCourse.count; + return updated; + } else { + return [...toolboxPrev, cleanedCourse]; + } + }); + + return updatedPlanner; + }); + }; + + const handleDelete = () => { + setPlannerCourses((prev) => + prev.map((semester, i) => + i === semesterIndex - 1 + ? { + ...semester, + courseList: semester.courseList.filter( + (_, idx) => idx !== courseIndex + ), + creditsTotal: semester.creditsTotal - course.credit_max, + } + : semester + ) + ); + }; + + // Return all the functions the component needs + return { + handleDuplicate, + handleMoveNext, + handleMoveToolbox, + handleDelete, + toTitleCase, + }; +}; From 1a414ba9d5f043f47071a7cb10dfd0bbba901207 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:55:30 -0400 Subject: [PATCH 56/81] rename dnd hook --- src/App.tsx | 4 ++-- src/hooks/{usePlannerDragAndDrop.ts => useDragAndDrop.ts} | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/hooks/{usePlannerDragAndDrop.ts => useDragAndDrop.ts} (98%) diff --git a/src/App.tsx b/src/App.tsx index 433a154..a31a4c9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,7 +11,7 @@ import { import { DragDropContext } from "@hello-pangea/dnd"; import { SemesterType } from "./types/interfaces/Semester.interface.ts"; import { Filters, FilterData } from "./types/Filters"; -import { usePlannerDragAndDrop } from "./hooks/usePlannerDragAndDrop"; +import { useDragAndDrop } from "./hooks/useDragAndDrop.ts"; import api from "./axios.ts"; const formatApiData = (data: string[]) => { @@ -42,7 +42,7 @@ export default function App() { }); // The complex logic is now neatly contained in the hook - const { onDragStart, onDragEnd } = usePlannerDragAndDrop({ + const { onDragStart, onDragEnd } = useDragAndDrop({ toolboxCourses, plannerCourses, setToolboxCourses, diff --git a/src/hooks/usePlannerDragAndDrop.ts b/src/hooks/useDragAndDrop.ts similarity index 98% rename from src/hooks/usePlannerDragAndDrop.ts rename to src/hooks/useDragAndDrop.ts index ae2583f..a41524d 100644 --- a/src/hooks/usePlannerDragAndDrop.ts +++ b/src/hooks/useDragAndDrop.ts @@ -23,7 +23,7 @@ const cloneCourse = (course: CourseEntry): CourseEntry => { // --- Hook Definition --- -interface UsePlannerDragAndDropProps { +interface UseDragAndDropProps { toolboxCourses: CourseEntry[]; plannerCourses: SemesterType[]; setToolboxCourses: Dispatch>; @@ -31,13 +31,13 @@ interface UsePlannerDragAndDropProps { setIsDragging: Dispatch>; } -export const usePlannerDragAndDrop = ({ +export const useDragAndDrop = ({ toolboxCourses, plannerCourses, setToolboxCourses, setPlannerCourses, setIsDragging, -}: UsePlannerDragAndDropProps) => { +}: UseDragAndDropProps) => { const onDragStart = () => { document.body.classList.add("no-scroll-during-drag"); setIsDragging(true); From 321fa1ed8a30a772da8b8c5be13587606c906869 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:35:32 -0400 Subject: [PATCH 57/81] remove prop drilling --- src/App.tsx | 31 ++------------------------- src/components/SearchBar/SeachBar.tsx | 27 ++++++++++++----------- src/pages/Catalog.tsx | 28 ++++-------------------- src/pages/HomePage.tsx | 20 ++--------------- 4 files changed, 22 insertions(+), 84 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a31a4c9..3d7fdf7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,13 +4,10 @@ import Catalog from "./pages/Catalog"; import Planner from "./pages/Planner"; import Toolbox from "./components/Toolbox/Toolbox"; import HomePage from "./pages/HomePage"; -import { - CourseEntry, - CourseType, -} from "./types/interfaces/Course.interface.ts"; +import { CourseEntry } from "./types/interfaces/Course.interface.ts"; import { DragDropContext } from "@hello-pangea/dnd"; import { SemesterType } from "./types/interfaces/Semester.interface.ts"; -import { Filters, FilterData } from "./types/Filters"; +import { FilterData } from "./types/Filters"; import { useDragAndDrop } from "./hooks/useDragAndDrop.ts"; import api from "./axios.ts"; @@ -32,14 +29,6 @@ export default function App() { }, ]); const [isDragging, setIsDragging] = useState(false); - const [searchResults, setSearchResults] = useState([]); - const [searchPrompt, setSearchPrompt] = useState(""); - const [showFilter, setShowFilter] = useState(false); - const [filters, setFilters] = useState({ - Subject: [], - Attributes: [], - Semesters: [], - }); // The complex logic is now neatly contained in the hook const { onDragStart, onDragEnd } = useDragAndDrop({ @@ -89,14 +78,6 @@ export default function App() { setToolboxCourses={setToolboxCourses} plannerCourses={plannerCourses} setPlannerCourses={setPlannerCourses} - searchResults={searchResults} - setSearchResults={setSearchResults} - searchPrompt={searchPrompt} - setSearchPrompt={setSearchPrompt} - showFilter={showFilter} - setShowFilter={setShowFilter} - filters={filters} - setFilters={setFilters} subjects={subjects} attributes={attributes} semesters={semesters} @@ -110,14 +91,6 @@ export default function App() { isDragging={isDragging} toolboxCourses={toolboxCourses} setToolboxCourses={setToolboxCourses} - searchResults={searchResults} - setSearchResults={setSearchResults} - searchPrompt={searchPrompt} - setSearchPrompt={setSearchPrompt} - showFilter={showFilter} - setShowFilter={setShowFilter} - filters={filters} - setFilters={setFilters} subjects={subjects} attributes={attributes} semesters={semesters} diff --git a/src/components/SearchBar/SeachBar.tsx b/src/components/SearchBar/SeachBar.tsx index cd649ba..f81d317 100644 --- a/src/components/SearchBar/SeachBar.tsx +++ b/src/components/SearchBar/SeachBar.tsx @@ -12,12 +12,6 @@ import FilterPanelPopup from "./FilterPanel/FilterPanelPopup.tsx"; interface SearchBarProps { updateSearchResults: (results: CourseType[]) => void; // Props for lifted state - searchPrompt: string; - setSearchPrompt: React.Dispatch>; - showFilter: boolean; - setShowFilter: React.Dispatch>; - filters: Filters; - setFilters: React.Dispatch>; subjects: FilterData[]; attributes: FilterData[]; semesters: FilterData[]; @@ -25,12 +19,6 @@ interface SearchBarProps { const SearchBar: React.FC = ({ updateSearchResults, - searchPrompt, - setSearchPrompt, - showFilter, - setShowFilter, - filters, - setFilters, subjects, attributes, semesters, @@ -38,6 +26,13 @@ const SearchBar: React.FC = ({ const isDesktop = useIsDesktop(); const [showDeptFilter, setShowDeptFilter] = useState(true); + const [showFilter, setShowFilter] = useState(false); + const [searchPrompt, setSearchPrompt] = useState(""); + const [filters, setFilters] = useState({ + Subject: [], + Attributes: [], + Semesters: [], + }); const debounceTimeout = useRef | null>(null); const lastNoResultQuery = useRef(null); @@ -97,6 +92,12 @@ const SearchBar: React.FC = ({ const handleSearch = (e: React.KeyboardEvent) => { (e.currentTarget as HTMLInputElement).blur(); setShowFilter(false); + setShowDeptFilter(false); + }; + + const handleInputChange = (e: React.ChangeEvent) => { + setSearchPrompt(e.target.value); + if (e.target.value.length === 0) setShowDeptFilter(false); }; const updateFilters = (category: keyof Filters, value: string) => { @@ -158,7 +159,7 @@ const SearchBar: React.FC = ({ value={searchPrompt} // Uses prop enterKeyHint="search" onClick={() => setShowFilter(true)} // Uses prop - onChange={(e) => setSearchPrompt(e.target.value)} // Uses prop + onChange={handleInputChange} // Uses prop onKeyDown={(e) => { if (e.key === "Enter" || e.key === "Done") handleSearch(e); }} diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index 68e0d99..32f924a 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -5,21 +5,13 @@ import { CourseType, CourseEntry, } from "../types/interfaces/Course.interface.ts"; -import { FilterData, Filters } from "../types/Filters"; +import { FilterData } from "../types/Filters"; interface CatalogProps { toolboxCourses: CourseEntry[]; setToolboxCourses: React.Dispatch>; isDragging: boolean; // New props added from the moved state - searchResults: CourseType[]; - setSearchResults: React.Dispatch>; - searchPrompt: string; - setSearchPrompt: React.Dispatch>; - showFilter: boolean; - setShowFilter: React.Dispatch>; - filters: Filters; - setFilters: React.Dispatch>; subjects: FilterData[]; attributes: FilterData[]; semesters: FilterData[]; @@ -28,30 +20,18 @@ interface CatalogProps { const Catalog: React.FC = ({ toolboxCourses, setToolboxCourses, - searchResults, - setSearchResults, - searchPrompt, - setSearchPrompt, - showFilter, - setShowFilter, - filters, - setFilters, subjects, attributes, semesters, }) => { + const [searchResults, setSearchResults] = React.useState([]); + return (

Courses

= ({ {searchResults.length > 0 && (
-
+
{searchResults?.map((course: CourseType, index: number) => ( >; plannerCourses: SemesterType[]; setPlannerCourses: React.Dispatch>; - searchResults: CourseType[]; - setSearchResults: React.Dispatch>; - searchPrompt: string; - setSearchPrompt: React.Dispatch>; - showFilter: boolean; - setShowFilter: React.Dispatch>; - filters: Filters; - setFilters: React.Dispatch>; subjects: FilterData[]; attributes: FilterData[]; @@ -41,14 +33,6 @@ const HomePage = (props: HomePageProps) => { isDragging={props.isDragging} toolboxCourses={props.toolboxCourses} setToolboxCourses={props.setToolboxCourses} - searchResults={props.searchResults} - setSearchResults={props.setSearchResults} - searchPrompt={props.searchPrompt} - setSearchPrompt={props.setSearchPrompt} - showFilter={props.showFilter} - setShowFilter={props.setShowFilter} - filters={props.filters} - setFilters={props.setFilters} subjects={props.subjects} attributes={props.attributes} semesters={props.semesters} From d97c90bd7e096e017ea83d6cfe401a66898505cb Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:39:33 -0400 Subject: [PATCH 58/81] add close on click outside for filter popup --- src/components/SearchBar/SeachBar.tsx | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/components/SearchBar/SeachBar.tsx b/src/components/SearchBar/SeachBar.tsx index f81d317..d1ad0c1 100644 --- a/src/components/SearchBar/SeachBar.tsx +++ b/src/components/SearchBar/SeachBar.tsx @@ -24,6 +24,7 @@ const SearchBar: React.FC = ({ semesters, }) => { const isDesktop = useIsDesktop(); + const componentRef = useRef(null); const [showDeptFilter, setShowDeptFilter] = useState(true); const [showFilter, setShowFilter] = useState(false); @@ -146,9 +147,28 @@ const SearchBar: React.FC = ({ return Object.values(filters).flat(); }, [filters]); + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + componentRef.current && + !componentRef.current.contains(event.target as Node) + ) { + setShowFilter(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + return (
-
+
Date: Fri, 24 Oct 2025 17:06:19 -0400 Subject: [PATCH 59/81] add CourseWorkspaceContext --- src/context/CourseWorkspaceContext.tsx | 20 +++++++++++ src/context/CourseWorkspaceProvider.tsx | 45 +++++++++++++++++++++++++ src/hooks/useCourse.ts | 10 ++++++ 3 files changed, 75 insertions(+) create mode 100644 src/context/CourseWorkspaceContext.tsx create mode 100644 src/context/CourseWorkspaceProvider.tsx create mode 100644 src/hooks/useCourse.ts diff --git a/src/context/CourseWorkspaceContext.tsx b/src/context/CourseWorkspaceContext.tsx new file mode 100644 index 0000000..716ce0e --- /dev/null +++ b/src/context/CourseWorkspaceContext.tsx @@ -0,0 +1,20 @@ +import React, { createContext } from "react"; +import { CourseEntry } from "../types/interfaces/Course.interface.ts"; +import { SemesterType } from "../types/interfaces/Semester.interface.ts"; +import { OnDragStartResponder, OnDragEndResponder } from "@hello-pangea/dnd"; + +export interface CourseWorkspaceContextType { + plannerCourses: SemesterType[]; + setPlannerCourses: React.Dispatch>; + toolboxCourses: CourseEntry[]; + setToolboxCourses: React.Dispatch>; + isDragging: boolean; + onDragStart: OnDragStartResponder; + onDragEnd: OnDragEndResponder; +} + +const CourseWorkspaceContext = createContext( + null +); + +export default CourseWorkspaceContext; diff --git a/src/context/CourseWorkspaceProvider.tsx b/src/context/CourseWorkspaceProvider.tsx new file mode 100644 index 0000000..4e15334 --- /dev/null +++ b/src/context/CourseWorkspaceProvider.tsx @@ -0,0 +1,45 @@ +import React, { useState, ReactNode } from "react"; +import { CourseEntry } from "../types/interfaces/Course.interface.ts"; +import { SemesterType } from "../types/interfaces/Semester.interface.ts"; +import { useDragAndDrop } from "../hooks/useDragAndDrop.ts"; +import CourseWorkspaceContext from "./CourseWorkspaceContext.tsx"; + +export const CourseWorkspaceProvider: React.FC<{ children: ReactNode }> = ({ + children, +}) => { + // all states + const [toolboxCourses, setToolboxCourses] = useState([]); + const [plannerCourses, setPlannerCourses] = useState([ + { + semesterNumber: 1, + semesterSeason: "fall", + creditsTotal: 0, + courseList: [], + }, + ]); + const [isDragging, setIsDragging] = useState(false); + + const { onDragStart, onDragEnd } = useDragAndDrop({ + toolboxCourses, + setToolboxCourses, + plannerCourses, + setPlannerCourses, + setIsDragging, + }); + + const value = { + plannerCourses, + setPlannerCourses, + toolboxCourses, + setToolboxCourses, + isDragging, + onDragStart, + onDragEnd, + }; + + return ( + + {children} + + ); +}; diff --git a/src/hooks/useCourse.ts b/src/hooks/useCourse.ts new file mode 100644 index 0000000..e6c9024 --- /dev/null +++ b/src/hooks/useCourse.ts @@ -0,0 +1,10 @@ +import { useContext } from "react"; +import CourseWorkspaceContext from "../context/CourseWorkspaceContext"; + +export const usePlanner = () => { + const context = useContext(CourseWorkspaceContext); + if (!context) { + throw new Error("usePlanner must be used within a CourseProvider"); + } + return context; +}; From a75933592f0835eb3fcef3f7b274ae584cf7fcd3 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:08:50 -0400 Subject: [PATCH 60/81] fix naming conventions for course workspace --- src/hooks/{useCourse.ts => useCourseWorkspace.ts} | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) rename src/hooks/{useCourse.ts => useCourseWorkspace.ts} (59%) diff --git a/src/hooks/useCourse.ts b/src/hooks/useCourseWorkspace.ts similarity index 59% rename from src/hooks/useCourse.ts rename to src/hooks/useCourseWorkspace.ts index e6c9024..09bae89 100644 --- a/src/hooks/useCourse.ts +++ b/src/hooks/useCourseWorkspace.ts @@ -1,10 +1,12 @@ import { useContext } from "react"; import CourseWorkspaceContext from "../context/CourseWorkspaceContext"; -export const usePlanner = () => { +export const useCourseWorkspace = () => { const context = useContext(CourseWorkspaceContext); if (!context) { - throw new Error("usePlanner must be used within a CourseProvider"); + throw new Error( + "useCourseWorkspace must be used within a CourseWorkspaceProvider" + ); } return context; }; From 33aa0508861f96a7efc3e801fe709f33a878b2c0 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:19:20 -0400 Subject: [PATCH 61/81] integrate CourseWorkspace context --- src/App.tsx | 113 +++++++++++------------------ src/components/Course/Course.tsx | 16 ++-- src/components/Toolbox/Toolbox.tsx | 17 ++--- src/pages/Catalog.tsx | 18 +---- src/pages/HomePage.tsx | 18 +---- src/pages/Planner.tsx | 19 ++--- 6 files changed, 64 insertions(+), 137 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 3d7fdf7..5d48e6e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,12 +4,11 @@ import Catalog from "./pages/Catalog"; import Planner from "./pages/Planner"; import Toolbox from "./components/Toolbox/Toolbox"; import HomePage from "./pages/HomePage"; -import { CourseEntry } from "./types/interfaces/Course.interface.ts"; import { DragDropContext } from "@hello-pangea/dnd"; -import { SemesterType } from "./types/interfaces/Semester.interface.ts"; import { FilterData } from "./types/Filters"; -import { useDragAndDrop } from "./hooks/useDragAndDrop.ts"; import api from "./axios.ts"; +import { CourseWorkspaceProvider } from "./context/CourseWorkspaceProvider.tsx"; +import { useCourseWorkspace } from "./hooks/useCourseWorkspace.ts"; const formatApiData = (data: string[]) => { return data.map((item: string, index: number) => ({ @@ -18,27 +17,19 @@ const formatApiData = (data: string[]) => { })); }; -export default function App() { - const [toolboxCourses, setToolboxCourses] = useState([]); - const [plannerCourses, setPlannerCourses] = useState([ - { - semesterNumber: 1, - semesterSeason: "fall", - creditsTotal: 0, - courseList: [], - }, - ]); - const [isDragging, setIsDragging] = useState(false); - - // The complex logic is now neatly contained in the hook - const { onDragStart, onDragEnd } = useDragAndDrop({ - toolboxCourses, - plannerCourses, - setToolboxCourses, - setPlannerCourses, - setIsDragging, - }); +const AppDragDropContext: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + // 2. Get the dnd functions from the hook + const { onDragStart, onDragEnd } = useCourseWorkspace(); + return ( + + {children} + + ); +}; +export default function App() { // pre-fetch filter data const [subjects, setSubjects] = useState([]); const [attributes, setAttributes] = useState([]); @@ -66,51 +57,35 @@ export default function App() { }, []); return ( - - - - - } - /> - - } - /> - - } - /> - - - - + + + + + + } + /> + + } + /> + } /> + + + + + ); } diff --git a/src/components/Course/Course.tsx b/src/components/Course/Course.tsx index 55669aa..512bb9b 100644 --- a/src/components/Course/Course.tsx +++ b/src/components/Course/Course.tsx @@ -2,22 +2,16 @@ import React, { useState } from "react"; import Tag from "./Tag"; import AddButton from "./AddButton"; import { motion } from "framer-motion"; -import { - CourseType, - CourseEntry, -} from "../../types/interfaces/Course.interface"; +import { CourseType } from "../../types/interfaces/Course.interface"; +import { useCourseWorkspace } from "../../hooks/useCourseWorkspace"; interface CourseProps { course: CourseType; - toolboxCourses: CourseEntry[]; - setToolboxCourses: React.Dispatch>; } -const Course: React.FC = ({ - course, - toolboxCourses, - setToolboxCourses, -}) => { +const Course: React.FC = ({ course }) => { + const { toolboxCourses, setToolboxCourses } = useCourseWorkspace(); + const [isOpen, setIsOpen] = useState(false); const toggleOpen = (e: React.MouseEvent) => { const target = e.target as HTMLElement; diff --git a/src/components/Toolbox/Toolbox.tsx b/src/components/Toolbox/Toolbox.tsx index dc3f0ab..d9d34af 100644 --- a/src/components/Toolbox/Toolbox.tsx +++ b/src/components/Toolbox/Toolbox.tsx @@ -5,23 +5,20 @@ import ToolboxButton from "./ToolboxButton"; import NavButton from "./NavButton"; import { Droppable } from "@hello-pangea/dnd"; import { IoIosArrowDown } from "react-icons/io"; -import { CourseEntry } from "../../types/interfaces/Course.interface"; import GarbageBin from "../GarbageBin"; import DraggableItem from "../DraggableItem"; +import { useCourseWorkspace } from "../../hooks/useCourseWorkspace"; -interface ToolboxProps { - courses: CourseEntry[]; - isDragging: boolean; -} - -const Toolbox: React.FC = ({ courses, isDragging }) => { +const Toolbox: React.FC = () => { const isDesktop = useIsDesktop(); const [isOpen, setIsOpen] = useState(isDesktop); const [count, setCount] = useState(0); + const { toolboxCourses, isDragging } = useCourseWorkspace(); + useEffect(() => { - setCount(courses.length); - }, [courses]); + setCount(toolboxCourses.length); + }, [toolboxCourses]); useEffect(() => { setIsOpen(isDesktop); @@ -86,7 +83,7 @@ const Toolbox: React.FC = ({ courses, isDragging }) => { {...provided.droppableProps} className={`courses gap-4 h-[75px] md:min-h-[50px] scrollbar-none flex justify-items-center w-full overflow-x-auto px-4 pb-2 transition-colors`} > - {courses.map((course, index) => ( + {toolboxCourses.map((course, index) => ( >; - isDragging: boolean; - // New props added from the moved state subjects: FilterData[]; attributes: FilterData[]; semesters: FilterData[]; } const Catalog: React.FC = ({ - toolboxCourses, - setToolboxCourses, subjects, attributes, semesters, @@ -42,12 +33,7 @@ const Catalog: React.FC = ({
{searchResults?.map((course: CourseType, index: number) => ( - + ))}
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 5c615c1..6222c51 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -2,17 +2,9 @@ import useIsDesktop from "../hooks/useIsDesktop"; import Catalog from "./Catalog"; import Planner from "./Planner"; -import { CourseEntry } from "../types/interfaces/Course.interface"; -import { SemesterType } from "../types/interfaces/Semester.interface"; import { FilterData } from "../types/Filters"; interface HomePageProps { - isDragging: boolean; - toolboxCourses: CourseEntry[]; - setToolboxCourses: React.Dispatch>; - plannerCourses: SemesterType[]; - setPlannerCourses: React.Dispatch>; - subjects: FilterData[]; attributes: FilterData[]; semesters: FilterData[]; @@ -30,9 +22,6 @@ const HomePage = (props: HomePageProps) => {
{
{isDesktop && (
- +
)}
diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index 29e065a..841d42f 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -1,22 +1,13 @@ -import React, { Dispatch, SetStateAction } from "react"; +import React from "react"; import SemesterBlock from "../components/PlannerComponents/SemesterBlocks/SemesterBlock"; -import { SemesterType } from "../types/interfaces/Semester.interface"; -import { CourseEntry } from "../types/interfaces/Course.interface"; import AddSemester from "../components/PlannerComponents/AddSemester"; import DeleteSemester from "../components/PlannerComponents/DeleteSemester"; +import { useCourseWorkspace } from "../hooks/useCourseWorkspace"; -interface PlannerProps { - isDragging: boolean; - plannerCourses: SemesterType[]; - setPlannerCourses: Dispatch>; - setToolboxCourses: Dispatch>; -} +const Planner: React.FC = () => { + const { plannerCourses, setPlannerCourses, setToolboxCourses } = + useCourseWorkspace(); -const Planner: React.FC = ({ - plannerCourses, - setPlannerCourses, - setToolboxCourses, -}) => { const handleDeleteSemester = (semesterNumber: number) => { setPlannerCourses((prev) => prev From c8fb5a5de5374fb5454e70d33a9f94fc13f4264a Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:24:36 -0400 Subject: [PATCH 62/81] move all course types to Course folder --- src/components/Course/{Course.tsx => CatalogCourse.tsx} | 4 ++-- src/components/Course/{ => CatalogCourse}/AddButton.tsx | 0 src/components/Course/{ => CatalogCourse}/Tag.tsx | 0 .../{PlannerComponents => Course}/PlannerCourse.tsx | 0 src/components/{Toolbox => Course}/ToolboxCourse.tsx | 0 src/components/DraggableItem.tsx | 4 ++-- src/pages/Catalog.tsx | 2 +- 7 files changed, 5 insertions(+), 5 deletions(-) rename src/components/Course/{Course.tsx => CatalogCourse.tsx} (97%) rename src/components/Course/{ => CatalogCourse}/AddButton.tsx (100%) rename src/components/Course/{ => CatalogCourse}/Tag.tsx (100%) rename src/components/{PlannerComponents => Course}/PlannerCourse.tsx (100%) rename src/components/{Toolbox => Course}/ToolboxCourse.tsx (100%) diff --git a/src/components/Course/Course.tsx b/src/components/Course/CatalogCourse.tsx similarity index 97% rename from src/components/Course/Course.tsx rename to src/components/Course/CatalogCourse.tsx index 512bb9b..27f13e9 100644 --- a/src/components/Course/Course.tsx +++ b/src/components/Course/CatalogCourse.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; -import Tag from "./Tag"; -import AddButton from "./AddButton"; +import Tag from "./CatalogCourse/Tag"; +import AddButton from "./CatalogCourse/AddButton"; import { motion } from "framer-motion"; import { CourseType } from "../../types/interfaces/Course.interface"; import { useCourseWorkspace } from "../../hooks/useCourseWorkspace"; diff --git a/src/components/Course/AddButton.tsx b/src/components/Course/CatalogCourse/AddButton.tsx similarity index 100% rename from src/components/Course/AddButton.tsx rename to src/components/Course/CatalogCourse/AddButton.tsx diff --git a/src/components/Course/Tag.tsx b/src/components/Course/CatalogCourse/Tag.tsx similarity index 100% rename from src/components/Course/Tag.tsx rename to src/components/Course/CatalogCourse/Tag.tsx diff --git a/src/components/PlannerComponents/PlannerCourse.tsx b/src/components/Course/PlannerCourse.tsx similarity index 100% rename from src/components/PlannerComponents/PlannerCourse.tsx rename to src/components/Course/PlannerCourse.tsx diff --git a/src/components/Toolbox/ToolboxCourse.tsx b/src/components/Course/ToolboxCourse.tsx similarity index 100% rename from src/components/Toolbox/ToolboxCourse.tsx rename to src/components/Course/ToolboxCourse.tsx diff --git a/src/components/DraggableItem.tsx b/src/components/DraggableItem.tsx index 0a31d11..58b0fd7 100644 --- a/src/components/DraggableItem.tsx +++ b/src/components/DraggableItem.tsx @@ -3,8 +3,8 @@ import { Draggable } from "@hello-pangea/dnd"; import { SemesterType } from "../types/interfaces/Semester.interface"; import { CourseType, CourseEntry } from "../types/interfaces/Course.interface"; -import PlannerCourse from "./PlannerComponents/PlannerCourse"; -import ToolboxCourse from "./Toolbox/ToolboxCourse"; +import PlannerCourse from "./Course/PlannerCourse"; +import ToolboxCourse from "./Course/ToolboxCourse"; interface DraggableItemProps { name: string; diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index c012cb3..5711272 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -1,5 +1,5 @@ import React from "react"; -import Course from "../components/Course/Course"; +import Course from "../components/Course/CatalogCourse.tsx"; import SearchBar from "../components/SearchBar/SeachBar"; import { CourseType } from "../types/interfaces/Course.interface.ts"; import { FilterData } from "../types/Filters"; From 8dbb9b986aa1920cb0b40a0fc111e1abd284efe7 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Sat, 25 Oct 2025 19:35:49 -0400 Subject: [PATCH 63/81] split the screen into thirds --- src/components/Course/PlannerCourse.tsx | 2 +- src/pages/Catalog.tsx | 2 +- src/pages/HomePage.tsx | 4 +-- src/pages/Planner.tsx | 36 +++++++++++++------------ 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/components/Course/PlannerCourse.tsx b/src/components/Course/PlannerCourse.tsx index 7b3281b..a2e9d7d 100644 --- a/src/components/Course/PlannerCourse.tsx +++ b/src/components/Course/PlannerCourse.tsx @@ -11,7 +11,7 @@ import { SemesterType } from "../../types/interfaces/Semester.interface"; import { CourseEntry } from "../../types/interfaces/Course.interface"; import * as RightClickContextMenu from "@radix-ui/react-context-menu"; import { usePlannerCourse } from "../../hooks/usePlannerCourseOptions"; -import PlannerOptionsPopup from "./PlannerOptionsPopup"; +import PlannerOptionsPopup from "../PlannerComponents/PlannerOptionsPopup"; interface PlannerCourseProps { course: CourseType; diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index 5711272..7376887 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -31,7 +31,7 @@ const Catalog: React.FC = ({ {searchResults.length > 0 && (
-
+
{searchResults?.map((course: CourseType, index: number) => ( ))} diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 6222c51..29b56d5 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -20,7 +20,7 @@ const HomePage = (props: HomePageProps) => {
-
+
{ />
{isDesktop && ( -
+
)} diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index 841d42f..b4d6d9e 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -23,27 +23,29 @@ const Planner: React.FC = () => {
-
+

Planner

- {plannerCourses.map((semester, index) => ( -
- - -
-
- ))} +
+ {plannerCourses.map((semester, index) => ( +
+ + +
+
+ ))} +
); From 3cd2a3c54f4ad34b695eba33e009d30ec52a36a1 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Sat, 25 Oct 2025 19:42:48 -0400 Subject: [PATCH 64/81] maintain consistent semester block height --- src/pages/Planner.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index b4d6d9e..08b8a11 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -30,7 +30,10 @@ const Planner: React.FC = () => {
{plannerCourses.map((semester, index) => ( -
+
{ semesterNumber={semester.semesterNumber} onDelete={handleDeleteSemester} /> -
))}
From 4e1cd0f34f26547a1202a514e06bb1eb421c8aaa Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Sat, 25 Oct 2025 19:45:34 -0400 Subject: [PATCH 65/81] adjust placeholder course --- src/components/PlannerComponents/PlannerCourseHolder.tsx | 2 +- .../PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PlannerComponents/PlannerCourseHolder.tsx b/src/components/PlannerComponents/PlannerCourseHolder.tsx index 969a8d5..89b41ec 100644 --- a/src/components/PlannerComponents/PlannerCourseHolder.tsx +++ b/src/components/PlannerComponents/PlannerCourseHolder.tsx @@ -9,7 +9,7 @@ const PlannerCourseHolder: React.FC = ({ }) => { return (
{isHover ? (

Drop the course!

diff --git a/src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx b/src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx index c59cf5f..b17896a 100644 --- a/src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx +++ b/src/components/PlannerComponents/SemesterBlocks/DesktopSemesterBlock.tsx @@ -35,7 +35,7 @@ const DesktopSemesterBlock: React.FC = ({
{isEmpty && !isHovering && ( From 0543f1d833baaeb1d5306d95c19bc21dc412c8a4 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Sat, 25 Oct 2025 19:53:12 -0400 Subject: [PATCH 66/81] fix searchbar and filter panel popups --- src/components/SearchBar/SeachBar.tsx | 44 ++++++++++++++------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/components/SearchBar/SeachBar.tsx b/src/components/SearchBar/SeachBar.tsx index d1ad0c1..2fe2db8 100644 --- a/src/components/SearchBar/SeachBar.tsx +++ b/src/components/SearchBar/SeachBar.tsx @@ -98,7 +98,8 @@ const SearchBar: React.FC = ({ const handleInputChange = (e: React.ChangeEvent) => { setSearchPrompt(e.target.value); - if (e.target.value.length === 0) setShowDeptFilter(false); + setShowFilter(false); + if (e.target.value.length === 0) setShowDeptFilter(true); }; const updateFilters = (category: keyof Filters, value: string) => { @@ -176,10 +177,10 @@ const SearchBar: React.FC = ({ aria-required="true" placeholder="Find Courses Here" className="flex-grow text-base placeholder-darkblue-40 focus:placeholder-transparent focus:outline-none focus:ring-0" - value={searchPrompt} // Uses prop + value={searchPrompt} enterKeyHint="search" - onClick={() => setShowFilter(true)} // Uses prop - onChange={handleInputChange} // Uses prop + onClick={() => setShowFilter(false)} + onChange={handleInputChange} onKeyDown={(e) => { if (e.key === "Enter" || e.key === "Done") handleSearch(e); }} @@ -201,16 +202,8 @@ const SearchBar: React.FC = ({ className="w-5 h-5" /> )} -
-
- {allActiveFilters.map((tag) => ( - - ))} -
- - {showFilter && - (isDesktop ? ( + {showFilter && isDesktop && ( = ({ attributes={attributes} semesters={semesters} /> - ) : ( - + )} +
+ +
+ {allActiveFilters.map((tag) => ( + ))} +
+ + {showFilter && !isDesktop && ( + + )} {showDeptFilter && (
From cd71dd61f00690d866d2ea5e57839751080b6c74 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:37:30 -0400 Subject: [PATCH 67/81] refactor catalog panel --- src/components/Department-Filters.tsx | 11 +- .../SearchBar/FilterPanel/FilterPanel.tsx | 2 +- .../FilterPanel/FilterPanelPopup.tsx | 2 +- .../SearchBar/FilterPanel/FilterSection.tsx | 11 +- src/components/SearchBar/SeachBar.tsx | 146 ++---------------- src/pages/Catalog copy.tsx | 115 ++++++++++++++ src/pages/Catalog.tsx | 107 ++++++++++++- 7 files changed, 248 insertions(+), 146 deletions(-) create mode 100644 src/pages/Catalog copy.tsx diff --git a/src/components/Department-Filters.tsx b/src/components/Department-Filters.tsx index 83d36c1..7dc2067 100644 --- a/src/components/Department-Filters.tsx +++ b/src/components/Department-Filters.tsx @@ -54,7 +54,7 @@ const departments: Department[] = [ ]; interface DepartmentFiltersProps { - updateFilters: (category: keyof Filters, value: string) => void; + updateFilters: React.Dispatch>; } const DepartmentFilters: React.FC = ({ @@ -76,7 +76,14 @@ const DepartmentFilters: React.FC = ({ hover:bg-darkblue hover:text-white h-fit `} - onClick={() => updateFilters("Subject", dept.code)} + onClick={() => + updateFilters((prev) => ({ + ...prev, + Subject: prev.Subject.includes(dept.code) + ? prev.Subject.filter((code) => code !== dept.code) + : [...prev.Subject, dept.code], + })) + } >
{dept.code} diff --git a/src/components/SearchBar/FilterPanel/FilterPanel.tsx b/src/components/SearchBar/FilterPanel/FilterPanel.tsx index 18b520f..6ea8cd0 100644 --- a/src/components/SearchBar/FilterPanel/FilterPanel.tsx +++ b/src/components/SearchBar/FilterPanel/FilterPanel.tsx @@ -3,7 +3,7 @@ import FilterSection from "./FilterSection"; interface FilterPanelProps { filters: { [key: string]: string[] }; - updateFilters: (category: keyof Filters, value: string) => void; + updateFilters: React.Dispatch>; subjects: FilterData[]; attributes: FilterData[]; diff --git a/src/components/SearchBar/FilterPanel/FilterPanelPopup.tsx b/src/components/SearchBar/FilterPanel/FilterPanelPopup.tsx index 6cbcef2..ba80976 100644 --- a/src/components/SearchBar/FilterPanel/FilterPanelPopup.tsx +++ b/src/components/SearchBar/FilterPanel/FilterPanelPopup.tsx @@ -3,7 +3,7 @@ import FilterPanel from "./FilterPanel"; interface FilterPanelPopupProps { filters: { [key: string]: string[] }; - updateFilters: (category: keyof Filters, value: string) => void; + updateFilters: React.Dispatch>; subjects: FilterData[]; attributes: FilterData[]; diff --git a/src/components/SearchBar/FilterPanel/FilterSection.tsx b/src/components/SearchBar/FilterPanel/FilterSection.tsx index 97dc325..a3b862c 100644 --- a/src/components/SearchBar/FilterPanel/FilterSection.tsx +++ b/src/components/SearchBar/FilterPanel/FilterSection.tsx @@ -10,7 +10,7 @@ interface FilterSectionProps { sectionName: keyof Filters; tags: TagProp[]; selected: string[]; - updateFilters: (category: keyof Filters, value: string) => void; + updateFilters: React.Dispatch>; } const FilterSection: React.FC = ({ @@ -28,7 +28,14 @@ const FilterSection: React.FC = ({ key={tag.id} className={`rounded-2xl px-3 py-1 text-sm mr-1 mb-1 flex-none ${selected.includes(tag.code) ? "bg-darkblue text-carpipink font-thin" : "border border-darkblue text-darkblue"}`} - onClick={() => updateFilters(sectionName, tag.code)} + onClick={() => + updateFilters((prev) => ({ + ...prev, + [sectionName]: selected.includes(tag.code) + ? prev[sectionName].filter((code) => code !== tag.code) + : [...prev[sectionName], tag.code], + })) + } > {tag.code} diff --git a/src/components/SearchBar/SeachBar.tsx b/src/components/SearchBar/SeachBar.tsx index 2fe2db8..f0971ea 100644 --- a/src/components/SearchBar/SeachBar.tsx +++ b/src/components/SearchBar/SeachBar.tsx @@ -1,16 +1,15 @@ -import React, { useState, useEffect, useRef, useMemo } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { IoClose, IoSearchOutline, IoFilter } from "react-icons/io5"; -import api from "../../axios"; import FilterPanel from "./FilterPanel/FilterPanel.tsx"; -import ChosenTag from "./ChosenTag"; import { Filters, FilterData } from "../../types/Filters"; -import DepartmentFilters from "../Department-Filters"; -import { CourseType } from "../../types/interfaces/Course.interface.ts"; import useIsDesktop from "../../hooks/useIsDesktop.ts"; import FilterPanelPopup from "./FilterPanel/FilterPanelPopup.tsx"; interface SearchBarProps { - updateSearchResults: (results: CourseType[]) => void; + setSearchPrompt: React.Dispatch>; + setFilters: React.Dispatch>; + selectedFilters: { [key: string]: string[] }; + searchPrompt: string; // Props for lifted state subjects: FilterData[]; attributes: FilterData[]; @@ -18,7 +17,10 @@ interface SearchBarProps { } const SearchBar: React.FC = ({ - updateSearchResults, + setSearchPrompt, + setFilters, + selectedFilters, + searchPrompt, subjects, attributes, semesters, @@ -26,128 +28,18 @@ const SearchBar: React.FC = ({ const isDesktop = useIsDesktop(); const componentRef = useRef(null); - const [showDeptFilter, setShowDeptFilter] = useState(true); const [showFilter, setShowFilter] = useState(false); - const [searchPrompt, setSearchPrompt] = useState(""); - const [filters, setFilters] = useState({ - Subject: [], - Attributes: [], - Semesters: [], - }); - - const debounceTimeout = useRef | null>(null); - const lastNoResultQuery = useRef(null); - - useEffect(() => { - if (debounceTimeout.current) { - clearTimeout(debounceTimeout.current); - } - - debounceTimeout.current = setTimeout(async () => { - const deptFilters = filters.Subject.join(","); - const attrFilters = filters.Attributes.join(","); - const semFilters = filters.Semesters.join(","); - - if (searchPrompt.length < 3) { - lastNoResultQuery.current = null; - } - - if ( - lastNoResultQuery.current && - searchPrompt.startsWith(lastNoResultQuery.current) - ) { - console.log("Skipping redundant no-result query"); - return; - } - - try { - const response = await api.get( - "course/search?searchPrompt=" + - searchPrompt + - "&deptFilters=" + - deptFilters + - "&attrFilters=" + - attrFilters + - "&semFilters=" + - semFilters - ); - - const data = response.data; - updateSearchResults(data); - - if (data.length === 0 && searchPrompt.length >= 3) { - lastNoResultQuery.current = searchPrompt; - } else { - lastNoResultQuery.current = null; - } - } catch (error) { - console.error("Search error:", error); - } - }, 300); - - return () => { - if (debounceTimeout.current) clearTimeout(debounceTimeout.current); - }; - }, [filters, searchPrompt, updateSearchResults]); const handleSearch = (e: React.KeyboardEvent) => { (e.currentTarget as HTMLInputElement).blur(); setShowFilter(false); - setShowDeptFilter(false); }; const handleInputChange = (e: React.ChangeEvent) => { setSearchPrompt(e.target.value); setShowFilter(false); - if (e.target.value.length === 0) setShowDeptFilter(true); }; - const updateFilters = (category: keyof Filters, value: string) => { - if (showDeptFilter) { - setShowDeptFilter(false); - window.scrollTo(0, 0); - } - - setFilters((prev) => { - if (prev[category].includes(value)) { - const newFilters: Filters = { ...prev }; - newFilters[category] = newFilters[category].filter( - (tag) => tag !== value - ); - return newFilters; - } - return { - ...prev, - [category]: [...prev[category], value], - }; - }); - }; - - const removeFilter = (value: string) => { - setFilters((prev) => { - const newFilters: Filters = { ...prev }; - for (const category of Object.keys(prev) as (keyof Filters)[]) { - newFilters[category] = newFilters[category].filter( - (tag) => tag !== value - ); - } - - if ( - newFilters.Subject.length === 0 && - newFilters.Attributes.length === 0 && - newFilters.Semesters.length === 0 - ) { - setShowDeptFilter(true); - setShowFilter(false); - } - return newFilters; - }); - }; - - const allActiveFilters = useMemo(() => { - return Object.values(filters).flat(); - }, [filters]); - useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( @@ -205,8 +97,8 @@ const SearchBar: React.FC = ({ {showFilter && isDesktop && ( = ({ )}
-
- {allActiveFilters.map((tag) => ( - - ))} -
- {showFilter && !isDesktop && ( )} - - {showDeptFilter && ( -
- -
- )}
); }; diff --git a/src/pages/Catalog copy.tsx b/src/pages/Catalog copy.tsx new file mode 100644 index 0000000..d1bb1a7 --- /dev/null +++ b/src/pages/Catalog copy.tsx @@ -0,0 +1,115 @@ +import React, { useState, useEffect, useRef } from "react"; +import api from "../axios.ts"; +import Course from "../components/Course/CatalogCourse.tsx"; +import SearchBar from "../components/SearchBar/SeachBar"; +import { Filters, FilterData } from "../types/Filters"; +import { CourseType } from "../types/interfaces/Course.interface.ts"; +import DepartmentFilters from "../components/Department-Filters.tsx"; + +interface CatalogProps { + subjects: FilterData[]; + attributes: FilterData[]; + semesters: FilterData[]; +} + +const Catalog: React.FC = ({ + subjects, + attributes, + semesters, +}) => { + const [searchResults, setSearchResults] = React.useState([]); + const [searchPrompt, setSearchPrompt] = useState(""); + const [filters, setFilters] = useState({ + Subject: [], + Attributes: [], + Semesters: [], + }); + + const debounceTimeout = useRef | null>(null); + const lastNoResultQuery = useRef(null); + + useEffect(() => { + if (debounceTimeout.current) { + clearTimeout(debounceTimeout.current); + } + + debounceTimeout.current = setTimeout(async () => { + const deptFilters = filters.Subject.join(","); + const attrFilters = filters.Attributes.join(","); + const semFilters = filters.Semesters.join(","); + + if (searchPrompt.length < 3) { + lastNoResultQuery.current = null; + } + + if ( + lastNoResultQuery.current && + searchPrompt.startsWith(lastNoResultQuery.current) + ) { + console.log("Skipping redundant no-result query"); + return; + } + + try { + const response = await api.get( + "course/search?searchPrompt=" + + searchPrompt + + "&deptFilters=" + + deptFilters + + "&attrFilters=" + + attrFilters + + "&semFilters=" + + semFilters + ); + + const data = response.data; + setSearchResults(data); + + if (data.length === 0 && searchPrompt.length >= 3) { + lastNoResultQuery.current = searchPrompt; + } else { + lastNoResultQuery.current = null; + } + } catch (error) { + console.error("Search error:", error); + } + }, 300); + + return () => { + if (debounceTimeout.current) clearTimeout(debounceTimeout.current); + }; + }, [filters, searchPrompt, setSearchResults]); + + return ( +
+
+

Courses

+ +
+ + {searchResults.length > 0 ? ( +
+
+ {searchResults?.map((course: CourseType, index: number) => ( + + ))} +
+
+ ) : ( +
+ +
+ )} +
+ ); +}; + +export default Catalog; diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index 7376887..6d2796b 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -1,8 +1,11 @@ -import React from "react"; +import React, { useState, useRef, useMemo, useEffect } from "react"; +import api from "../axios.ts"; import Course from "../components/Course/CatalogCourse.tsx"; import SearchBar from "../components/SearchBar/SeachBar"; +import { Filters, FilterData } from "../types/Filters"; import { CourseType } from "../types/interfaces/Course.interface.ts"; -import { FilterData } from "../types/Filters"; +import DepartmentFilters from "../components/Department-Filters.tsx"; +import ChosenTag from "../components/SearchBar/ChosenTag"; interface CatalogProps { subjects: FilterData[]; @@ -16,28 +19,118 @@ const Catalog: React.FC = ({ semesters, }) => { const [searchResults, setSearchResults] = React.useState([]); + const [searchPrompt, setSearchPrompt] = useState(""); + const [filters, setFilters] = useState({ + Subject: [], + Attributes: [], + Semesters: [], + }); + + const debounceTimeout = useRef | null>(null); + const lastNoResultQuery = useRef(null); + + useEffect(() => { + if (debounceTimeout.current) { + clearTimeout(debounceTimeout.current); + } + + debounceTimeout.current = setTimeout(async () => { + const deptFilters = filters.Subject.join(","); + const attrFilters = filters.Attributes.join(","); + const semFilters = filters.Semesters.join(","); + + if (searchPrompt.length < 3) { + lastNoResultQuery.current = null; + } + + if ( + lastNoResultQuery.current && + searchPrompt.startsWith(lastNoResultQuery.current) + ) { + console.log("Skipping redundant no-result query"); + return; + } + + try { + const response = await api.get( + "course/search?searchPrompt=" + + searchPrompt + + "&deptFilters=" + + deptFilters + + "&attrFilters=" + + attrFilters + + "&semFilters=" + + semFilters + ); + + const data = response.data; + setSearchResults(data); + + if (data.length === 0 && searchPrompt.length >= 3) { + lastNoResultQuery.current = searchPrompt; + } else { + lastNoResultQuery.current = null; + } + } catch (error) { + console.error("Search error:", error); + } + }, 300); + + return () => { + if (debounceTimeout.current) clearTimeout(debounceTimeout.current); + }; + }, [filters, searchPrompt, setSearchResults]); + + const allActiveFilters = useMemo(() => { + return Object.values(filters).flat(); + }, [filters]); + + const removeFilter = (value: string) => { + setFilters((prev) => { + const newFilters: Filters = { ...prev }; + for (const category of Object.keys(prev) as (keyof Filters)[]) { + newFilters[category] = newFilters[category].filter( + (tag) => tag !== value + ); + } + return newFilters; + }); + }; return (

Courses

+ +
+ {allActiveFilters.map((tag) => ( + + ))} +
- {searchResults.length > 0 && ( -
+
+ {searchResults.length > 0 ? (
{searchResults?.map((course: CourseType, index: number) => ( ))}
-
- )} + ) : ( + allActiveFilters.length == 0 && ( + + ) + )} +
); }; From bb969413379a1b1a151a105faf944944cffc0885 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Mon, 27 Oct 2025 19:38:21 -0400 Subject: [PATCH 68/81] rename use planner course options --- .../{usePlannerCourseOptions.tsx => usePlannerCourseOptions.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/hooks/{usePlannerCourseOptions.tsx => usePlannerCourseOptions.ts} (100%) diff --git a/src/hooks/usePlannerCourseOptions.tsx b/src/hooks/usePlannerCourseOptions.ts similarity index 100% rename from src/hooks/usePlannerCourseOptions.tsx rename to src/hooks/usePlannerCourseOptions.ts From 7d0cdf29a63ccc109152c9d18727c95ca5445e8a Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:16:35 -0400 Subject: [PATCH 69/81] create and integrate FilterContext --- src/App.tsx | 73 +++------------- src/components/Department-Filters.tsx | 21 ++--- src/components/SearchBar/ChosenTag.tsx | 14 ++-- .../SearchBar/FilterPanel/FilterPanel.tsx | 40 +++++---- .../FilterPanel/FilterPanelPopup.tsx | 26 +----- .../SearchBar/FilterPanel/FilterSection.tsx | 19 ++--- src/components/SearchBar/SeachBar.tsx | 44 +--------- src/context/FilterContext.tsx | 17 ++++ src/context/FilterProvider.tsx | 72 ++++++++++++++++ src/hooks/useFilters.ts | 10 +++ src/pages/Catalog.tsx | 84 ++++++++----------- src/pages/HomePage.tsx | 15 +--- src/types/Filters.ts | 1 + 13 files changed, 199 insertions(+), 237 deletions(-) create mode 100644 src/context/FilterContext.tsx create mode 100644 src/context/FilterProvider.tsx create mode 100644 src/hooks/useFilters.ts diff --git a/src/App.tsx b/src/App.tsx index 5d48e6e..612e5d1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,22 +1,13 @@ -import { useEffect, useState } from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import Catalog from "./pages/Catalog"; import Planner from "./pages/Planner"; import Toolbox from "./components/Toolbox/Toolbox"; import HomePage from "./pages/HomePage"; import { DragDropContext } from "@hello-pangea/dnd"; -import { FilterData } from "./types/Filters"; -import api from "./axios.ts"; import { CourseWorkspaceProvider } from "./context/CourseWorkspaceProvider.tsx"; +import { FilterProvider } from "./context/FilterProvider.tsx"; import { useCourseWorkspace } from "./hooks/useCourseWorkspace.ts"; -const formatApiData = (data: string[]) => { - return data.map((item: string, index: number) => ({ - id: index, - code: item, - })); -}; - const AppDragDropContext: React.FC<{ children: React.ReactNode }> = ({ children, }) => { @@ -30,61 +21,19 @@ const AppDragDropContext: React.FC<{ children: React.ReactNode }> = ({ }; export default function App() { - // pre-fetch filter data - const [subjects, setSubjects] = useState([]); - const [attributes, setAttributes] = useState([]); - const [semesters, setSemesters] = useState([]); - - useEffect(() => { - const fetchAllFilters = async () => { - try { - const [subjectsResponse, attributesResponse, semestersResponse] = - await Promise.all([ - api.get("/course/filter/values/departments"), - api.get("/course/filter/values/attributes"), - api.get("/course/filter/values/semesters"), - ]); - - setSubjects(formatApiData(subjectsResponse.data)); - setAttributes(formatApiData(attributesResponse.data)); - setSemesters(formatApiData(semestersResponse.data)); - } catch (error) { - console.error("Failed to fetch filters:", error); - } - }; - - fetchAllFilters(); - }, []); - return ( - - - - } - /> - - } - /> - } /> - - - + + + + } /> + } /> + } /> + + + + ); diff --git a/src/components/Department-Filters.tsx b/src/components/Department-Filters.tsx index 7dc2067..e3f9577 100644 --- a/src/components/Department-Filters.tsx +++ b/src/components/Department-Filters.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Filters } from "../types/Filters"; +import { useFilterData } from "../hooks/useFilters"; // Department type interface Department { @@ -53,13 +53,9 @@ const departments: Department[] = [ { code: "WRIT", name: "Writing" }, ]; -interface DepartmentFiltersProps { - updateFilters: React.Dispatch>; -} +const DepartmentFilters: React.FC = () => { + const { toggleFilter } = useFilterData(); -const DepartmentFilters: React.FC = ({ - updateFilters, -}) => { return (
{departments.map((dept) => ( @@ -77,12 +73,11 @@ const DepartmentFilters: React.FC = ({ h-fit `} onClick={() => - updateFilters((prev) => ({ - ...prev, - Subject: prev.Subject.includes(dept.code) - ? prev.Subject.filter((code) => code !== dept.code) - : [...prev.Subject, dept.code], - })) + toggleFilter({ + id: 1, + code: dept.code, + type: "Subject", + }) } >
diff --git a/src/components/SearchBar/ChosenTag.tsx b/src/components/SearchBar/ChosenTag.tsx index e01d12b..3e069e3 100644 --- a/src/components/SearchBar/ChosenTag.tsx +++ b/src/components/SearchBar/ChosenTag.tsx @@ -1,21 +1,23 @@ import React from "react"; import { IoClose } from "react-icons/io5"; +import { useFilterData } from "../../hooks/useFilters"; +import { FilterData } from "../../types/Filters"; interface ChosenTagProp { - name: string; - onRemove: (name: string) => void; + filter: FilterData; } -const ChosenTag: React.FC = ({ name, onRemove }) => { +const ChosenTag: React.FC = ({ filter }) => { + const { toggleFilter } = useFilterData(); return (
- {name} + {filter.code}
); }; diff --git a/src/context/FilterContext.tsx b/src/context/FilterContext.tsx new file mode 100644 index 0000000..b5c3ba3 --- /dev/null +++ b/src/context/FilterContext.tsx @@ -0,0 +1,17 @@ +import { createContext } from "react"; +import { FilterData } from "../types/Filters"; + +export interface FilterContextType { + // available filter options + subjects: FilterData[]; + attributes: FilterData[]; + semesters: FilterData[]; + + // selected filter values + selectedFilters: FilterData[]; + toggleFilter: (filter: FilterData) => void; +} + +const FilterContext = createContext(null); + +export default FilterContext; diff --git a/src/context/FilterProvider.tsx b/src/context/FilterProvider.tsx new file mode 100644 index 0000000..c912d5a --- /dev/null +++ b/src/context/FilterProvider.tsx @@ -0,0 +1,72 @@ +import React, { useState, useEffect, ReactNode } from "react"; +import api from "../axios.ts"; +import { FilterData } from "../types/Filters.ts"; +import FilterContext from "./FilterContext.tsx"; + +const formatApiData = (type: string, data: string[]) => { + return data.map((item: string, index: number) => ({ + id: index, + code: item, + type: type as "Subject" | "Attributes" | "Semesters", + })); +}; + +export const FilterProvider: React.FC<{ children: ReactNode }> = ({ + children, +}) => { + // all states + const [subjects, setSubjects] = useState([]); + const [attributes, setAttributes] = useState([]); + const [semesters, setSemesters] = useState([]); + + useEffect(() => { + const fetchAllFilters = async () => { + try { + const [subjectsResponse, attributesResponse, semestersResponse] = + await Promise.all([ + api.get("/course/filter/values/departments"), + api.get("/course/filter/values/attributes"), + api.get("/course/filter/values/semesters"), + ]); + + setSubjects(formatApiData("Subject", subjectsResponse.data)); + setAttributes(formatApiData("Attributes", attributesResponse.data)); + setSemesters(formatApiData("Semesters", semestersResponse.data)); + } catch (error) { + console.error("Failed to fetch filters:", error); + } + }; + + fetchAllFilters(); + }, []); + + // selected filter state + const [selectedFilters, setSelectedFilters] = useState([]); + + const toggleFilter = (filter: FilterData) => { + setSelectedFilters((prevSelected) => { + const exists = prevSelected.find( + (f) => f.code === filter.code && f.type === filter.type + ); + if (exists) { + return prevSelected.filter( + (f) => !(f.code === filter.code && f.type === filter.type) + ); + } else { + return [...prevSelected, filter]; + } + }); + }; + + const value = { + subjects, + attributes, + semesters, + selectedFilters, + toggleFilter, + }; + + return ( + {children} + ); +}; diff --git a/src/hooks/useFilters.ts b/src/hooks/useFilters.ts new file mode 100644 index 0000000..3ba2cfc --- /dev/null +++ b/src/hooks/useFilters.ts @@ -0,0 +1,10 @@ +import { useContext } from "react"; +import FilterContext from "../context/FilterContext"; + +export const useFilterData = () => { + const context = useContext(FilterContext); + if (!context) { + throw new Error("useFilterData must be used within a FilterProvider"); + } + return context; +}; diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index 6d2796b..9d84d76 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -1,30 +1,19 @@ -import React, { useState, useRef, useMemo, useEffect } from "react"; +import React, { useState, useRef, useEffect } from "react"; import api from "../axios.ts"; import Course from "../components/Course/CatalogCourse.tsx"; import SearchBar from "../components/SearchBar/SeachBar"; -import { Filters, FilterData } from "../types/Filters"; import { CourseType } from "../types/interfaces/Course.interface.ts"; import DepartmentFilters from "../components/Department-Filters.tsx"; import ChosenTag from "../components/SearchBar/ChosenTag"; +import { useFilterData } from "../hooks/useFilters.ts"; + +const Catalog: React.FC = () => { + const { selectedFilters } = useFilterData(); -interface CatalogProps { - subjects: FilterData[]; - attributes: FilterData[]; - semesters: FilterData[]; -} - -const Catalog: React.FC = ({ - subjects, - attributes, - semesters, -}) => { const [searchResults, setSearchResults] = React.useState([]); const [searchPrompt, setSearchPrompt] = useState(""); - const [filters, setFilters] = useState({ - Subject: [], - Attributes: [], - Semesters: [], - }); + const [isLoading, setIsLoading] = useState(false); + const [hasSearched, setHasSearched] = useState(false); const debounceTimeout = useRef | null>(null); const lastNoResultQuery = useRef(null); @@ -34,10 +23,27 @@ const Catalog: React.FC = ({ clearTimeout(debounceTimeout.current); } + if (searchPrompt.length === 0 && selectedFilters.length === 0) { + setHasSearched(false); + setSearchResults([]); + setIsLoading(false); + lastNoResultQuery.current = null; + return; + } + debounceTimeout.current = setTimeout(async () => { - const deptFilters = filters.Subject.join(","); - const attrFilters = filters.Attributes.join(","); - const semFilters = filters.Semesters.join(","); + setIsLoading(true); + setHasSearched(true); + + const deptFilters = selectedFilters + .filter((filter) => filter.type === "Subject") + .map((filter) => filter.code); + const attrFilters = selectedFilters + .filter((filter) => filter.type === "Attributes") + .map((filter) => filter.code); + const semFilters = selectedFilters + .filter((filter) => filter.type === "Semesters") + .map((filter) => filter.code); if (searchPrompt.length < 3) { lastNoResultQuery.current = null; @@ -65,6 +71,7 @@ const Catalog: React.FC = ({ const data = response.data; setSearchResults(data); + setIsLoading(false); if (data.length === 0 && searchPrompt.length >= 3) { lastNoResultQuery.current = searchPrompt; @@ -79,23 +86,7 @@ const Catalog: React.FC = ({ return () => { if (debounceTimeout.current) clearTimeout(debounceTimeout.current); }; - }, [filters, searchPrompt, setSearchResults]); - - const allActiveFilters = useMemo(() => { - return Object.values(filters).flat(); - }, [filters]); - - const removeFilter = (value: string) => { - setFilters((prev) => { - const newFilters: Filters = { ...prev }; - for (const category of Object.keys(prev) as (keyof Filters)[]) { - newFilters[category] = newFilters[category].filter( - (tag) => tag !== value - ); - } - return newFilters; - }); - }; + }, [selectedFilters, searchPrompt, setSearchResults]); return (
@@ -103,17 +94,12 @@ const Catalog: React.FC = ({

Courses

- {allActiveFilters.map((tag) => ( - + {selectedFilters.map((filter) => ( + ))}
@@ -125,10 +111,12 @@ const Catalog: React.FC = ({ ))}
+ ) : isLoading ? ( +
Loading...
+ ) : hasSearched ? ( +
No results found.
) : ( - allActiveFilters.length == 0 && ( - - ) + selectedFilters.length === 0 && )}
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 29b56d5..e9512fd 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -2,15 +2,8 @@ import useIsDesktop from "../hooks/useIsDesktop"; import Catalog from "./Catalog"; import Planner from "./Planner"; -import { FilterData } from "../types/Filters"; -interface HomePageProps { - subjects: FilterData[]; - attributes: FilterData[]; - semesters: FilterData[]; -} - -const HomePage = (props: HomePageProps) => { +const HomePage = () => { const isDesktop = useIsDesktop(); return ( @@ -21,11 +14,7 @@ const HomePage = (props: HomePageProps) => {
- +
{isDesktop && (
diff --git a/src/types/Filters.ts b/src/types/Filters.ts index c3ea99e..8dc3e2b 100644 --- a/src/types/Filters.ts +++ b/src/types/Filters.ts @@ -7,4 +7,5 @@ export type Filters = { export type FilterData = { id: number; code: string; + type: "Subject" | "Attributes" | "Semesters"; }; From d102e60aace95ef7475532c30af95dc918e37c15 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:20:50 -0400 Subject: [PATCH 70/81] adjust catalog spacing --- src/pages/Catalog copy.tsx | 115 ------------------------------------- src/pages/Catalog.tsx | 4 +- 2 files changed, 2 insertions(+), 117 deletions(-) delete mode 100644 src/pages/Catalog copy.tsx diff --git a/src/pages/Catalog copy.tsx b/src/pages/Catalog copy.tsx deleted file mode 100644 index d1bb1a7..0000000 --- a/src/pages/Catalog copy.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React, { useState, useEffect, useRef } from "react"; -import api from "../axios.ts"; -import Course from "../components/Course/CatalogCourse.tsx"; -import SearchBar from "../components/SearchBar/SeachBar"; -import { Filters, FilterData } from "../types/Filters"; -import { CourseType } from "../types/interfaces/Course.interface.ts"; -import DepartmentFilters from "../components/Department-Filters.tsx"; - -interface CatalogProps { - subjects: FilterData[]; - attributes: FilterData[]; - semesters: FilterData[]; -} - -const Catalog: React.FC = ({ - subjects, - attributes, - semesters, -}) => { - const [searchResults, setSearchResults] = React.useState([]); - const [searchPrompt, setSearchPrompt] = useState(""); - const [filters, setFilters] = useState({ - Subject: [], - Attributes: [], - Semesters: [], - }); - - const debounceTimeout = useRef | null>(null); - const lastNoResultQuery = useRef(null); - - useEffect(() => { - if (debounceTimeout.current) { - clearTimeout(debounceTimeout.current); - } - - debounceTimeout.current = setTimeout(async () => { - const deptFilters = filters.Subject.join(","); - const attrFilters = filters.Attributes.join(","); - const semFilters = filters.Semesters.join(","); - - if (searchPrompt.length < 3) { - lastNoResultQuery.current = null; - } - - if ( - lastNoResultQuery.current && - searchPrompt.startsWith(lastNoResultQuery.current) - ) { - console.log("Skipping redundant no-result query"); - return; - } - - try { - const response = await api.get( - "course/search?searchPrompt=" + - searchPrompt + - "&deptFilters=" + - deptFilters + - "&attrFilters=" + - attrFilters + - "&semFilters=" + - semFilters - ); - - const data = response.data; - setSearchResults(data); - - if (data.length === 0 && searchPrompt.length >= 3) { - lastNoResultQuery.current = searchPrompt; - } else { - lastNoResultQuery.current = null; - } - } catch (error) { - console.error("Search error:", error); - } - }, 300); - - return () => { - if (debounceTimeout.current) clearTimeout(debounceTimeout.current); - }; - }, [filters, searchPrompt, setSearchResults]); - - return ( -
-
-

Courses

- -
- - {searchResults.length > 0 ? ( -
-
- {searchResults?.map((course: CourseType, index: number) => ( - - ))} -
-
- ) : ( -
- -
- )} -
- ); -}; - -export default Catalog; diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index 9d84d76..839f44d 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -89,8 +89,8 @@ const Catalog: React.FC = () => { }, [selectedFilters, searchPrompt, setSearchResults]); return ( -
-
+
+

Courses

Date: Mon, 27 Oct 2025 22:44:18 -0400 Subject: [PATCH 71/81] adjust spacing for planner and semester blocks --- .../SemesterBlocks/MobileSemesterBlock.tsx | 37 ++++++++++--------- src/pages/Planner.tsx | 8 ++-- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/components/PlannerComponents/SemesterBlocks/MobileSemesterBlock.tsx b/src/components/PlannerComponents/SemesterBlocks/MobileSemesterBlock.tsx index c690afa..bd59d5d 100644 --- a/src/components/PlannerComponents/SemesterBlocks/MobileSemesterBlock.tsx +++ b/src/components/PlannerComponents/SemesterBlocks/MobileSemesterBlock.tsx @@ -44,7 +44,7 @@ const SemesterBlock: React.FC = ({
{isEmpty && !isHovering && ( @@ -52,27 +52,28 @@ const SemesterBlock: React.FC = ({ {isEmpty && isHovering && ( )} - {!isEmpty && - semester.courseList.map((course, courseIndex) => ( - - ))} - {provided.placeholder} + {!isEmpty && ( + <> + {semester.courseList.map((course, courseIndex) => ( + + ))} + {provided.placeholder} + + )}
); }} - -
); diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index 08b8a11..db63202 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -17,22 +17,22 @@ const Planner: React.FC = () => { }; return ( -
+
Carpi Logo
-
+

Planner

-
+
{plannerCourses.map((semester, index) => (
Date: Mon, 27 Oct 2025 22:50:10 -0400 Subject: [PATCH 72/81] update toolbox total courses correctly --- src/components/Toolbox/Toolbox.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Toolbox/Toolbox.tsx b/src/components/Toolbox/Toolbox.tsx index d9d34af..2660f07 100644 --- a/src/components/Toolbox/Toolbox.tsx +++ b/src/components/Toolbox/Toolbox.tsx @@ -17,7 +17,9 @@ const Toolbox: React.FC = () => { const { toolboxCourses, isDragging } = useCourseWorkspace(); useEffect(() => { - setCount(toolboxCourses.length); + setCount( + toolboxCourses.reduce((total, course) => total + (course.count || 0), 0) + ); }, [toolboxCourses]); useEffect(() => { From f764d0ac84cf9c136927fa4874dd368942f2b481 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 28 Oct 2025 09:30:30 -0400 Subject: [PATCH 73/81] add spacing for the course number badge --- src/components/Toolbox/Toolbox.tsx | 2 +- src/pages/Catalog.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Toolbox/Toolbox.tsx b/src/components/Toolbox/Toolbox.tsx index 2660f07..930dc5d 100644 --- a/src/components/Toolbox/Toolbox.tsx +++ b/src/components/Toolbox/Toolbox.tsx @@ -83,7 +83,7 @@ const Toolbox: React.FC = () => {
{toolboxCourses.map((course, index) => ( {
{searchResults.length > 0 ? ( -
+
{searchResults?.map((course: CourseType, index: number) => ( ))} From 344acff87512c13a34950dc816004d813c287470 Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:55:21 -0400 Subject: [PATCH 74/81] add loading courses ghost --- src/components/Department-Filters.tsx | 2 ++ src/pages/Catalog.tsx | 31 +++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/components/Department-Filters.tsx b/src/components/Department-Filters.tsx index e3f9577..1b81aff 100644 --- a/src/components/Department-Filters.tsx +++ b/src/components/Department-Filters.tsx @@ -86,6 +86,8 @@ const DepartmentFilters: React.FC = () => {
))} + +
); }; diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index 6678174..833128d 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -31,10 +31,10 @@ const Catalog: React.FC = () => { return; } - debounceTimeout.current = setTimeout(async () => { - setIsLoading(true); - setHasSearched(true); + setIsLoading(true); + setHasSearched(true); + debounceTimeout.current = setTimeout(async () => { const deptFilters = selectedFilters .filter((filter) => filter.type === "Subject") .map((filter) => filter.code); @@ -110,9 +110,32 @@ const Catalog: React.FC = () => { {searchResults?.map((course: CourseType, index: number) => ( ))} + +
) : isLoading ? ( -
Loading...
+ Array.from({ length: 6 }).map((_, i) => ( +
+
+
+
+ +
+ {Array.from({ length: 5 }).map((_, j) => ( +
+ ))} +
+
+ +
+
+ )) ) : hasSearched ? (
No results found.
) : ( From 733914043ef70a4e4a1e87f042c3e064f584e28c Mon Sep 17 00:00:00 2001 From: Miranda Zheng <123515762+mirmirmirr@users.noreply.github.com> Date: Tue, 28 Oct 2025 13:08:53 -0400 Subject: [PATCH 75/81] formatting changes --- src/pages/Catalog.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Catalog.tsx b/src/pages/Catalog.tsx index 833128d..7a7f71e 100644 --- a/src/pages/Catalog.tsx +++ b/src/pages/Catalog.tsx @@ -117,14 +117,14 @@ const Catalog: React.FC = () => { Array.from({ length: 6 }).map((_, i) => (
- {Array.from({ length: 5 }).map((_, j) => ( + {Array.from({ length: 4 }).map((_, j) => (
Date: Tue, 28 Oct 2025 13:15:52 -0400 Subject: [PATCH 76/81] add hover effects --- src/components/Course/CatalogCourse.tsx | 2 +- src/components/Course/CatalogCourse/AddButton.tsx | 14 ++++++-------- src/components/SearchBar/ChosenTag.tsx | 2 +- .../SearchBar/FilterPanel/FilterSection.tsx | 4 ++-- src/components/SearchBar/SeachBar.tsx | 4 ++-- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/components/Course/CatalogCourse.tsx b/src/components/Course/CatalogCourse.tsx index 27f13e9..315ccf9 100644 --- a/src/components/Course/CatalogCourse.tsx +++ b/src/components/Course/CatalogCourse.tsx @@ -52,7 +52,7 @@ const Course: React.FC = ({ course }) => { const courseCount = toolboxCourse ? toolboxCourse.count : undefined; return (
= ({ addCourse }) => { return ( - <> - - + ); }; diff --git a/src/components/SearchBar/ChosenTag.tsx b/src/components/SearchBar/ChosenTag.tsx index 3e069e3..d35cc71 100644 --- a/src/components/SearchBar/ChosenTag.tsx +++ b/src/components/SearchBar/ChosenTag.tsx @@ -20,7 +20,7 @@ const ChosenTag: React.FC = ({ filter }) => { aria-label={`Remove filter: ${filter.code}`} className="inline ml-1" > - +
); diff --git a/src/components/SearchBar/FilterPanel/FilterSection.tsx b/src/components/SearchBar/FilterPanel/FilterSection.tsx index cf45101..f0423d8 100644 --- a/src/components/SearchBar/FilterPanel/FilterSection.tsx +++ b/src/components/SearchBar/FilterPanel/FilterSection.tsx @@ -26,8 +26,8 @@ const FilterSection: React.FC = ({ {tags.map((tag) => (