diff --git a/.vite/deps/_metadata.json b/.vite/deps/_metadata.json index f1bb19d..053bb80 100644 --- a/.vite/deps/_metadata.json +++ b/.vite/deps/_metadata.json @@ -1,8 +1,8 @@ { - "hash": "625b799d", - "configHash": "a9b0ffca", - "lockfileHash": "a6ff8f94", - "browserHash": "6b8947ac", + "hash": "14330173", + "configHash": "d7c1e009", + "lockfileHash": "e3b0c442", + "browserHash": "c6eb8ced", "optimized": {}, "chunks": {} } diff --git a/package-lock.json b/package-lock.json index ff7e4ca..02e8bd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "tailwindcss4", "version": "0.0.0", "dependencies": { + "@babel/runtime": "^7.27.0", "@hello-pangea/dnd": "^18.0.1", "@tailwindcss/vite": "^4.0.6", "axios": "^1.7.9", @@ -261,9 +262,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz", - "integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, diff --git a/package.json b/package.json index 1a8712b..a0d5a2c 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "prettier:write": "prettier --write ." }, "dependencies": { + "@babel/runtime": "^7.27.0", "@hello-pangea/dnd": "^18.0.1", "@tailwindcss/vite": "^4.0.6", "axios": "^1.7.9", diff --git a/src/App.tsx b/src/App.tsx index 24bc137..14fccf6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -39,7 +39,7 @@ function App() { const reorder = ( list: T[], startIndex: number, - endIndex: number + endIndex: number, ): T[] => { const result = [...list]; const [removed] = result.splice(startIndex, 1); @@ -70,7 +70,7 @@ function App() { if (sInd === "toolbox" && dInd === "toolbox") { setToolboxCourses((prev) => - reorder(prev, source.index, destination.index) + reorder(prev, source.index, destination.index), ); return; } @@ -94,11 +94,11 @@ function App() { courseList: reorder( semester.courseList, source.index, - destination.index + destination.index, ), } - : semester - ) + : semester, + ), ); return; } @@ -179,15 +179,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), ); } @@ -302,6 +302,7 @@ function App() { isDragging={isDragging} plannerCourses={plannerCourses} setPlannerCourses={setPlannerCourses} + setToolboxCourses={setToolboxCourses} /> } /> diff --git a/src/components/DraggableItem.tsx b/src/components/DraggableItem.tsx index 04399ab..6c2d4cd 100644 --- a/src/components/DraggableItem.tsx +++ b/src/components/DraggableItem.tsx @@ -2,12 +2,19 @@ import { Draggable } from "@hello-pangea/dnd"; 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"; + setPlannerCourses: Dispatch> | null; + setToolboxCourses: Dispatch> | null; + semesterIndex: number | null; } const DraggableItem: React.FC = ({ @@ -16,6 +23,9 @@ const DraggableItem: React.FC = ({ index, course, location, + setPlannerCourses, + setToolboxCourses, + semesterIndex, }) => { return ( @@ -30,11 +40,17 @@ const DraggableItem: React.FC = ({ }} className="relative" > - {location === "planner" ? ( + {location === "planner" && + setPlannerCourses && + setToolboxCourses && + semesterIndex ? ( ) : ( >; + setToolboxCourses: Dispatch>; + semesterIndex: number; } -const PlannerCourse: React.FC = ({ course }) => { +const PlannerCourse: React.FC = ({ + course, + index, + setPlannerCourses, + setToolboxCourses, + semesterIndex, +}) => { const [openPopup, setOpenPopup] = useState(false); const componentRef = useRef(null); @@ -27,6 +44,150 @@ const PlannerCourse: React.FC = ({ course }) => { } }; + 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; + + 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 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 = () => { + console.log(semesterIndex); + 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 () => { @@ -79,17 +240,29 @@ const PlannerCourse: React.FC = ({ course }) => { className="absolute bg-[#F5CECE] rounded-xl border border-slate-500 text-[#283044] text-xs p-2 right-0 top-8 shadow-lg z-1" >
- -
- -
diff --git a/src/components/PlannerComponents/SemesterBlock.tsx b/src/components/PlannerComponents/SemesterBlock.tsx index 2344fb3..0a4ed29 100644 --- a/src/components/PlannerComponents/SemesterBlock.tsx +++ b/src/components/PlannerComponents/SemesterBlock.tsx @@ -1,15 +1,22 @@ -import React from "react"; +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 }) => { +const SemesterBlock: React.FC = ({ + semester, + index, + setPlannerCourses, + setToolboxCourses, +}) => { return ( <>
@@ -53,13 +60,16 @@ const SemesterBlock: React.FC = ({ semester, index }) => { )} {!isEmpty && - semester.courseList.map((course, index) => ( + semester.courseList.map((course, courseIndex) => ( ))} diff --git a/src/components/Toolbox/Toolbox.tsx b/src/components/Toolbox/Toolbox.tsx index 9518057..87088d5 100644 --- a/src/components/Toolbox/Toolbox.tsx +++ b/src/components/Toolbox/Toolbox.tsx @@ -63,6 +63,9 @@ const Toolbox: React.FC = ({ courses, isDragging }) => { index={index} course={course.data} location="toolbox" + setPlannerCourses={null} + setToolboxCourses={null} + semesterIndex={null} /> ))} {provided.placeholder} diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index ff1a4bf..470cb2b 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -2,18 +2,21 @@ import React from "react"; import { Dispatch, SetStateAction } from "react"; import SemesterBlock from "../components/PlannerComponents/SemesterBlock"; import { SemesterType } from "../types/interfaces/Semester.interface"; +import { CourseEntry } from "../types/interfaces/Course.interface"; import AddSemester from "../components/PlannerComponents/AddSemester"; interface PlannerProps { isDragging: boolean; plannerCourses: SemesterType[]; setPlannerCourses: Dispatch>; + setToolboxCourses: Dispatch>; } const Planner: React.FC = ({ isDragging, plannerCourses, setPlannerCourses, + setToolboxCourses, }) => { return ( <> @@ -26,6 +29,8 @@ const Planner: React.FC = ({ semester={semester} index={index + 1} key={semester.semesterNumber} + setPlannerCourses={setPlannerCourses} + setToolboxCourses={setToolboxCourses} /> ); })}