From 9bc75fb59d02d17e9ce0e59d80f3cc2b5bce11a6 Mon Sep 17 00:00:00 2001 From: ramyareddy04 <75100092+ramyareddy04@users.noreply.github.com> Date: Mon, 29 Apr 2024 12:34:19 -0400 Subject: [PATCH 01/38] remove courses + hover x button --- frontend/src/pages/Majors/NavBar.module.css | 21 ++++++ .../src/pages/Majors/components/NavBar.js | 70 ++++++++++++------- 2 files changed, 67 insertions(+), 24 deletions(-) diff --git a/frontend/src/pages/Majors/NavBar.module.css b/frontend/src/pages/Majors/NavBar.module.css index f8d8a3f..c73669b 100644 --- a/frontend/src/pages/Majors/NavBar.module.css +++ b/frontend/src/pages/Majors/NavBar.module.css @@ -11,6 +11,27 @@ border-radius: 15px; } +.program:hover { + filter: brightness(95%); +} + +.program:hover .close { + display: inline-block; +} + +.close { + display:none; + border:0px; + background-color: #E1E9F8; + padding:0px; + padding-right:6px; + margin:0px; +} + +.close:hover { + filter: brightness(90%); +} + .container { flex-direction: column; row-gap: 20px; diff --git a/frontend/src/pages/Majors/components/NavBar.js b/frontend/src/pages/Majors/components/NavBar.js index 40a6683..b43c0bd 100644 --- a/frontend/src/pages/Majors/components/NavBar.js +++ b/frontend/src/pages/Majors/components/NavBar.js @@ -3,6 +3,7 @@ import styles from "./../NavBar.module.css" import MeDropdown from "../../../navbar/account/MeDropdown"; import img_logo from "./../../../commons/images/ma_logo.png" import { NavLink } from "react-router-dom"; +import { local } from "d3"; const programs = [{ ID: 1, @@ -23,20 +24,27 @@ const programs = [{ function NavBar() { let [myPrograms, setPrograms] = useState([]); - // localStorage.removeItem("theProgramList"); + let programCount = 0; - const storedPrograms = localStorage.getItem("theProgramList"); - - if (myPrograms.length > 0) + if (localStorage.getItem("programCount")) { + programCount = JSON.parse(localStorage.getItem("programCount")); + } + + if (myPrograms.length > 0 || programCount == 0) { localStorage.setItem('theProgramList', JSON.stringify(myPrograms)); } + const storedPrograms = localStorage.getItem("theProgramList"); if (storedPrograms) { - myPrograms = JSON.parse(storedPrograms); + if (JSON.parse(storedPrograms).length > 0) { + myPrograms = JSON.parse(storedPrograms); + } + } else { + localStorage.setItem('theProgramList', JSON.stringify(myPrograms)); } - + function List(props) { const filteredData = programs.filter((el) => { if (props.input === "") { @@ -63,9 +71,12 @@ function NavBar() { myDropdown.style.display = "none"; if (myPrograms.length !== 2) { setPrograms([...myPrograms, name.toUpperCase()]); + programCount = myPrograms.length + 1; + localStorage.setItem('programCount', JSON.stringify(programCount)); } } }; + return (
{filteredData.map((item) => ( @@ -86,16 +97,35 @@ function NavBar() { let visibilityHandler = (e) => { document.getElementById("list").style.display = "block"; } + + let programRemover = (e, name) => { + setPrograms(l => { return l.filter(item => item !== name)}); + programCount = myPrograms.length - 1; + localStorage.setItem('programCount', JSON.stringify(programCount)); + }; + + return ( -
- - +
+
+ + +
+
+
PINNED
+ {myPrograms.map(program => ( +
+ + {program} +
+ ))} +
); } @@ -109,15 +139,7 @@ function NavBar() { style={{ width: "150px", height: "auto", marginRight: "10px" }} />
-
- -
-
PINNED
- {myPrograms.map(program => ( -
{program}
- ))} -
-
+
Date: Mon, 29 Apr 2024 12:42:00 -0400 Subject: [PATCH 02/38] css fix --- frontend/src/pages/Majors/NavBar.module.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/src/pages/Majors/NavBar.module.css b/frontend/src/pages/Majors/NavBar.module.css index c73669b..8733628 100644 --- a/frontend/src/pages/Majors/NavBar.module.css +++ b/frontend/src/pages/Majors/NavBar.module.css @@ -28,10 +28,6 @@ margin:0px; } -.close:hover { - filter: brightness(90%); -} - .container { flex-direction: column; row-gap: 20px; From 7c17d9bfae1d4ad29c698c7527af805902ff408c Mon Sep 17 00:00:00 2001 From: winbow13 Date: Sun, 2 Feb 2025 00:38:52 -0500 Subject: [PATCH 03/38] Added back README, fixed minor naming issues --- docs/README.md | 132 +++++++++++++ frontend/package-lock.json | 4 +- frontend/package.json | 2 +- .../src/pages/Majors/components/NavBar.js | 175 ------------------ 4 files changed, 135 insertions(+), 178 deletions(-) create mode 100644 docs/README.md delete mode 100644 frontend/src/pages/Majors/components/NavBar.js diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..879656d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,132 @@ +# MajorAudit + +## Repository Layout +- `/frontend`: The current face of the site, built with React. +- `/backend`: The backend logic for the site, built with Flask. +- `/scrapers`: Chrome extensions for web scraping. +- `/docs`: Documentation. + +## Local Development Environment + +We're working fullstack. + +### Requirements +- Access to MajorAudit GitHub repository. +- npm (Node Package Manager). + +### Setup Instructions + +0. Clone the MajorAudit Repository: + ```bash + git clone + ``` + +### Base Firebase Setup +1. In the root directory, run: + ```bash + npm install -g firebase-tools + ``` + _Note: If it throws permission errors, prepend the command with `sudo`:_ + ```bash + sudo npm install -g firebase-tools + ``` + +### Backend Setup (Python Virtual Environment) +2. Update Python to version 3.12. + - You can use [Homebrew](https://brew.sh/) to install the latest version of Python: + ```bash + brew install python@3.12 + ``` + +3. Navigate to the `/backend` directory. +4. Create a virtual environment: + ```bash + python3.12 -m venv venv + ``` +5. Activate the virtual environment: + ```bash + source venv/bin/activate + ``` +6. Install the required dependencies: + ```bash + pip install -r requirements.txt + ``` +7. Deactivate the virtual environment: + ```bash + deactivate + ``` + +### Secrets Setup +8. Create a `secrets` directory in the `/backend` folder: + ```bash + mkdir secrets + ``` +9. Go to the [Firebase Console](https://console.firebase.google.com/). +10. Select the `majoraudit` project. +11. Click the gear icon next to "Project Overview" and select "Project Settings". +12. Navigate to the "Service Accounts" tab. +13. Generate a new Node.js private key. +14. Move the generated key file to your `secrets` directory. +15. Update the path to the key file in `main.py`: + ```python + cred = credentials.Certificate(r'path_to_secrets_file') + ``` + +### Running the Project +1. Install the required frontend dependencies: + ```bash + cd frontend + npm i + ``` + +2. Ensure you have Java version >= 20 installed. + +3. Log in to Firebase: + ```bash + firebase login + ``` + +4. In the `/frontend` directory, build the frontend: + ```bash + npm run build + ``` + +5. In the root or `/frontend` directory, start the Firebase emulators: + ```bash + firebase emulators:start + ``` + +6. Troubleshoot any errors as needed. + +### Notes +- **Frontend Changes**: Anytime you change the frontend code, stop the emulators, rebuild the frontend, and restart the emulators. The emulators only host the most recent build. +- **Web Scraper Changes**: If you modify the web scraper, remove and reconfigure the extension in Chrome. +- **Backend Changes**: You can modify the backend code on the fly. The emulators will automatically restart when you save changes. + +### Strategies for Development +- **Frontend-Only Development**: + 1. Change the `useState(auth)` value in `App.tsx` to `true`. + 2. Modify the `initLocalStorage()` method in `Graduation.tsx` to use `MockStudent` instead of calling the `getData()` API. + 3. Run the frontend in development mode: + ```bash + npm start + ``` + 4. The frontend will now automatically update as you make changes. + +## Contributing +1. Create a branch for your feature: + ```bash + git checkout -b / + ``` +2. Make your changes. +3. Commit and push your changes to the origin: + ```bash + git commit -m "Your commit message" + git push origin + ``` +4. Create a pull request and add reviewers. In the pull request, reference any relevant issue numbers. +5. Once the pull request is approved, merge it into the master branch. + +## Roadmap +- We use GitHub issues to track bugs and feature requests: [GitHub Issues](https://github.com/YaleComputerSociety/MajorAudit/issues). +- We use GitHub projects to manage everything and do planning: [GitHub Projects](https://github.com/orgs/YaleComputerSociety/projects/2/). \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 921eccc..d189040 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,11 +1,11 @@ { - "name": "new-audit", + "name": "majoraudit", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "new-audit", + "name": "majoraudit", "version": "0.1.0", "dependencies": { "d3": "^7.9.0", diff --git a/frontend/package.json b/frontend/package.json index d1864bc..7c391cf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "new-audit", + "name": "majoraudit", "version": "0.1.0", "private": true, "scripts": { diff --git a/frontend/src/pages/Majors/components/NavBar.js b/frontend/src/pages/Majors/components/NavBar.js deleted file mode 100644 index a18a613..0000000 --- a/frontend/src/pages/Majors/components/NavBar.js +++ /dev/null @@ -1,175 +0,0 @@ -import { React, useState } from "react"; -import styles from "./../NavBar.module.css" -import MeDropdown from "../../../navbar/account/MeDropdown"; -import img_logo from "./../../../commons/images/ma_logo.png" -import { NavLink } from "react-router-dom"; -import { local } from "d3"; - -const programs = [{ - ID: 1, - text: 'Computer Science' -}, -{ - ID: 2, - text: 'History' -}, -{ - ID: 3, - text: 'Economics' -}, -{ - ID: 4, - text: 'Political Science' -}]; - -function NavBar() { - let [myPrograms, setPrograms] = useState([]); - let programCount = 0; - - if (localStorage.getItem("programCount")) - { - programCount = JSON.parse(localStorage.getItem("programCount")); - } - - if (myPrograms.length > 0 || programCount == 0) { - localStorage.setItem('theProgramList', JSON.stringify(myPrograms)); - } - - const storedPrograms = localStorage.getItem("theProgramList"); - if (storedPrograms) { - if (JSON.parse(storedPrograms).length > 0) { - myPrograms = JSON.parse(storedPrograms); - } - } else { - localStorage.setItem('theProgramList', JSON.stringify(myPrograms)); - } - - - function List(props) { - const filteredData = programs.filter((el) => { - if (props.input === "") { - return el; - } - else { - return el.text.toLowerCase().includes(props.input) - } - }) - - // fix so it only retracts drop drown list when not scrolling/clicking - // window.onmousedown = function (e) { - // if (!e.target.matches('.NavBar')) { - // var myDropdown = document.getElementById("list"); - // if (myDropdown.style.display === "block") { - // myDropdown.style.display = "none"; - // } - // } - // } - - let programHandler = (e, name) => { - var myDropdown = document.getElementById("list"); - if (myDropdown.style.display === "block") { - myDropdown.style.display = "none"; - if (myPrograms.length !== 2) { - setPrograms([...myPrograms, name.toUpperCase()]); - programCount = myPrograms.length + 1; - localStorage.setItem('programCount', JSON.stringify(programCount)); - } - } - }; - - return ( -
- {filteredData.map((item) => ( -
programHandler(e, item.text)}>{item.text}
- ))} -
- ) - } - - function SearchBar() { - - const [inputText, setInputText] = useState(""); - let inputHandler = (e) => { - var lowerCase = e.target.value.toLowerCase(); - setInputText(lowerCase); - }; - - let visibilityHandler = (e) => { - document.getElementById("list").style.display = "block"; - } - - let programRemover = (e, name) => { - setPrograms(l => { return l.filter(item => item !== name)}); - programCount = myPrograms.length - 1; - localStorage.setItem('programCount', JSON.stringify(programCount)); - }; - - - return ( -
-
- - -
-
-
PINNED
- {myPrograms.map(program => ( -
- - {program} -
- ))} -
-
- ); - } - - return ( -
-
- -
- -
- - isActive ? styles.activeLink : styles.dormantLink - } - > - Graduation - - - isActive ? styles.activeLink : styles.dormantLink - } - > - Courses - - - isActive ? styles.activeLink : styles.dormantLink - } - > - Majors - - -
-
- ); -} - - -export default NavBar; From ad7e66b76500a5a776478c3c3bb74b2c31a3bf55 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Sun, 2 Feb 2025 14:49:42 -0500 Subject: [PATCH 04/38] spring_svg --- frontend/public/spring.svg | 84 +++++++++++++++++++ .../semester/course/CourseBoxUtils.tsx | 4 +- 2 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 frontend/public/spring.svg diff --git a/frontend/public/spring.svg b/frontend/public/spring.svg new file mode 100644 index 0000000..1a67e09 --- /dev/null +++ b/frontend/public/spring.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/app/courses/semester/course/CourseBoxUtils.tsx b/frontend/src/app/courses/semester/course/CourseBoxUtils.tsx index b85ed43..6211041 100644 --- a/frontend/src/app/courses/semester/course/CourseBoxUtils.tsx +++ b/frontend/src/app/courses/semester/course/CourseBoxUtils.tsx @@ -57,10 +57,10 @@ export function RenderMark(props: { edit: boolean, studentCourse: StudentCourse, export function SeasonIcon(props: { studentCourse: StudentCourse }) { - // const getSeasonImage = () => (String(props.studentCourse.term).endsWith("3") ? fall : fall); + const getSeasonImage = () => (String(props.studentCourse.term).endsWith("3") ? "/fall.svg" : "/spring.svg"); return(
- +
) } From ab83dd13b6166adae21021460944242be363f8de Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Mon, 3 Feb 2025 14:59:16 -0500 Subject: [PATCH 05/38] course coloring, checkmarks --- .../semester/add-course/AddCourseButton.tsx | 2 +- .../app/courses/semester/course/CourseBox.tsx | 25 ++++------------- .../semester/course/CourseBoxUtils.tsx | 27 ++++++++++++++++--- frontend/src/database/data-user.ts | 4 +-- frontend/src/types/type-user.ts | 2 +- 5 files changed, 32 insertions(+), 28 deletions(-) diff --git a/frontend/src/app/courses/semester/add-course/AddCourseButton.tsx b/frontend/src/app/courses/semester/add-course/AddCourseButton.tsx index a391fdb..567ab5c 100644 --- a/frontend/src/app/courses/semester/add-course/AddCourseButton.tsx +++ b/frontend/src/app/courses/semester/add-course/AddCourseButton.tsx @@ -23,7 +23,7 @@ function executeAddCourse( const targetCourse = getCatalogCourse(selectedTerm, targetCode); if(targetCourse){ - const status = selectedTerm === props.term ? "MA_VALID" : "MA_HYPOTHETICAL"; + const status = selectedTerm === props.term ? "DA" : "MA"; const newCourse: StudentCourse = { course: targetCourse, status, term: props.term }; const updatedSemesters = props.user.FYP.studentSemesters.map((semester) => { diff --git a/frontend/src/app/courses/semester/course/CourseBox.tsx b/frontend/src/app/courses/semester/course/CourseBox.tsx index 6cc7bfb..bc65b6c 100644 --- a/frontend/src/app/courses/semester/course/CourseBox.tsx +++ b/frontend/src/app/courses/semester/course/CourseBox.tsx @@ -2,30 +2,15 @@ import Style from "./CourseBox.module.css"; import { User, StudentCourse } from "@/types/type-user"; -import { RenderMark } from "./CourseBoxUtils"; -import { SeasonIcon } from "./CourseBoxUtils"; +import { RenderMark, SeasonIcon, CourseBoxColor } from "./CourseBoxUtils"; import DistributionCircle from "@/components/distribution-circle/DistributionsCircle"; -// import img_fall from "./../../../../commons/images/fall.png"; -// import img_spring from "./../../../../commons/images/spring.png"; - - - // import { useModal } from "../../../hooks/modalContext"; +// const { setModalOpen } = useModal(); function openModal() { setModalOpen(props.SC.course) } // onClick={openModal} -function CourseBox(props: {edit: boolean, studentCourse: StudentCourse, user: User, setUser: Function }) -{ - // const { setModalOpen } = useModal(); - // function openModal() { - // setModalOpen(props.SC.course) - // } - - const getBackgroundColor = () => (props.studentCourse.status === "DA_COMPLETE" ? "#E1E9F8" : "#F5F5F5"); - - - return ( -
- {/* onClick={openModal} */} +function CourseBox(props: {edit: boolean, studentCourse: StudentCourse, user: User, setUser: Function }){ + return( +
diff --git a/frontend/src/app/courses/semester/course/CourseBoxUtils.tsx b/frontend/src/app/courses/semester/course/CourseBoxUtils.tsx index 6211041..1e6ed6e 100644 --- a/frontend/src/app/courses/semester/course/CourseBoxUtils.tsx +++ b/frontend/src/app/courses/semester/course/CourseBoxUtils.tsx @@ -33,20 +33,19 @@ function RemoveCourse(props: { studentCourse: StudentCourse; user: User; setUser export function RenderMark(props: { edit: boolean, studentCourse: StudentCourse, user: User, setUser: Function }) { - if(props.studentCourse.status === "DA_COMPLETE" || props.studentCourse.status === "DA_PROSPECT"){ + if(props.studentCourse.status === "DA"){ return(
); }else - if(props.studentCourse.status === "MA_HYPOTHETICAL" || props.studentCourse.status === "MA_VALID"){ - const mark = props.studentCourse.status === "MA_HYPOTHETICAL" ? "⚠" : "☑"; + if(props.studentCourse.status === "MA"){ return(
{props.edit && }
- {mark} + ⚠
@@ -64,3 +63,23 @@ export function SeasonIcon(props: { studentCourse: StudentCourse })
) } + +export function CourseBoxColor(term: number) { + const currentYearMonth = new Date().toISOString().slice(0, 7); // "YYYY-MM" + + // Convert number to string and validate length + const termStr = String(term); + if (termStr.length !== 6) return "#E1E9F8"; // Default color if term format is invalid + + const year = termStr.slice(0, 4); // Extract year (YYYY) + const season = termStr.slice(4, 6); // Extract season (01, 02, or 03) + + // Define the cutoff month for each season + const seasonCutoff: { [key: string]: string } = { + "01": `${year}-06`, // Summer cutoff (June) + "02": `${year}-09`, // Fall cutoff (September) + "03": `${Number(year) + 1}-01`, // Spring cutoff (January of next year) + }; + + return currentYearMonth >= seasonCutoff[season] ? "#E1E9F8" : "#F5F5F5"; +} diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index dd7f056..1bf1005 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -7,8 +7,8 @@ export const Ryan: User = { onboard: true, FYP: { studentSemesters: [ - { season: 202203, studentCourses: [{ term: 202203, status: "DA_COMPLETE", course: { codes: ["CPSC 223"], title: "Data Structures", credit: 1, dist: ["QR"], seasons: [] } }] }, - { season: 202301, studentCourses: [{ term: 202301, status: "MA_HYPOTHETICAL", course: { codes: ["CPSC 323"], title: "Systems Programming", credit: 1, dist: ["QR"], seasons: [] } }] }, + { season: 202203, studentCourses: [{ term: 202203, status: "DA", course: { codes: ["CPSC 223"], title: "Data Structures", credit: 1, dist: ["QR"], seasons: [] } }] }, + { season: 202301, studentCourses: [{ term: 202503, status: "MA", course: { codes: ["CPSC 323"], title: "Systems Programming", credit: 1, dist: ["QR"], seasons: [] } }] }, ], languageRequirement: "", degreeDeclarations: [], diff --git a/frontend/src/types/type-user.ts b/frontend/src/types/type-user.ts index 1fd1884..90e2125 100644 --- a/frontend/src/types/type-user.ts +++ b/frontend/src/types/type-user.ts @@ -12,7 +12,7 @@ export interface Course { export interface StudentCourse { course: Course; term: number; // 202401 - status: string; // "DA_COMPLETE" | "DA_PROSPECT" | "MA_VALID" | "MA_HYPOTHETICAL" + status: string; // "DA" || "MA" } export interface StudentSemester { From 10d34c4f67bb5a19e009b8d2f5e2e3dfd428f152 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Mon, 3 Feb 2025 23:39:29 -0500 Subject: [PATCH 06/38] toggle columns --- .firebaserc | 5 - firebase.json | 66 -------- frontend/src/app/courses/Courses.module.css | 5 + frontend/src/app/courses/CoursesUtils.tsx | 42 ++++++ frontend/src/app/courses/page.tsx | 36 ++--- .../app/courses/semester/course/CourseBox.tsx | 35 ----- .../semester/course/CourseBoxUtils.tsx | 85 ----------- .../src/app/courses/years/YearBox.Module.css | 10 ++ frontend/src/app/courses/years/YearBox.tsx | 47 ++++++ .../add-semester/AddSemesterButton.module.css | 0 .../add-semester/AddSemesterButton.tsx | 2 +- .../semester/SemesterBox.module.css | 0 .../{ => years}/semester/SemesterBox.tsx | 5 +- .../add-course/AddCourseButton.module.css | 0 .../semester/add-course/AddCourseButton.tsx | 0 .../semester/add-course/AddCourseUtils.ts | 0 .../semester/course/CourseBox.module.css | 2 +- .../years/semester/course/CourseBox.tsx | 61 ++++++++ frontend/src/database/data-user.ts | 11 +- frontend/src/pages/Courses/Courses.tsx | 142 ------------------ frontend/src/pages/Courses/CoursesUtils.ts | 101 ------------- .../year/semester/course/CourseBox.tsx | 120 --------------- frontend/src/pages/Majors/NavBar.module.css | 127 ---------------- frontend/src/types/type-user.ts | 11 +- frontend/src/utils/CourseDisplay.module.css | 66 ++++++++ frontend/src/utils/CourseDisplay.tsx | 83 ++++++++++ package-lock.json | 35 ----- package.json | 9 -- 28 files changed, 353 insertions(+), 753 deletions(-) delete mode 100644 .firebaserc delete mode 100644 firebase.json create mode 100644 frontend/src/app/courses/CoursesUtils.tsx delete mode 100644 frontend/src/app/courses/semester/course/CourseBox.tsx delete mode 100644 frontend/src/app/courses/semester/course/CourseBoxUtils.tsx create mode 100644 frontend/src/app/courses/years/YearBox.Module.css create mode 100644 frontend/src/app/courses/years/YearBox.tsx rename frontend/src/app/courses/{ => years}/add-semester/AddSemesterButton.module.css (100%) rename frontend/src/app/courses/{ => years}/add-semester/AddSemesterButton.tsx (98%) rename frontend/src/app/courses/{ => years}/semester/SemesterBox.module.css (100%) rename frontend/src/app/courses/{ => years}/semester/SemesterBox.tsx (74%) rename frontend/src/app/courses/{ => years}/semester/add-course/AddCourseButton.module.css (100%) rename frontend/src/app/courses/{ => years}/semester/add-course/AddCourseButton.tsx (100%) rename frontend/src/app/courses/{ => years}/semester/add-course/AddCourseUtils.ts (100%) rename frontend/src/app/courses/{ => years}/semester/course/CourseBox.module.css (98%) create mode 100644 frontend/src/app/courses/years/semester/course/CourseBox.tsx delete mode 100644 frontend/src/pages/Courses/Courses.tsx delete mode 100644 frontend/src/pages/Courses/CoursesUtils.ts delete mode 100644 frontend/src/pages/Courses/year/semester/course/CourseBox.tsx delete mode 100644 frontend/src/pages/Majors/NavBar.module.css create mode 100644 frontend/src/utils/CourseDisplay.module.css create mode 100644 frontend/src/utils/CourseDisplay.tsx delete mode 100644 package-lock.json delete mode 100644 package.json diff --git a/.firebaserc b/.firebaserc deleted file mode 100644 index 43475cc..0000000 --- a/.firebaserc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "projects": { - "default": "majoraudit" - } -} diff --git a/firebase.json b/firebase.json deleted file mode 100644 index 2817460..0000000 --- a/firebase.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "functions": [ - { - "source": "backend", - "codebase": "default", - "ignore": [ - "__pycache__", - "venv", - ".git", - "firebase-debug.log", - "firebase-debug.*.log" - ] - } - ], - "database": { - "rules": "database.rules.json" - }, - "hosting": { - "public": "frontend/build", - "ignore": [ - "firebase.json", - "**/.*", - "**/node_modules/**", - "README.md" - ], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - }], - "cleanUrls": true - }, - "storage": { - "rules": "storage.rules" - }, - "emulators": { - "auth": { - "port": 9099 - }, - "functions": { - "port": 5001 - }, - "firestore": { - "port": 8080 - }, - "database": { - "port": 9000 - }, - "hosting": { - "port": 3000 - }, - "pubsub": { - "port": 8085 - }, - "storage": { - "port": 9199 - }, - "eventarc": { - "port": 9299 - }, - "ui": { - "enabled": true - }, - "singleProjectMode": true - } -} diff --git a/frontend/src/app/courses/Courses.module.css b/frontend/src/app/courses/Courses.module.css index bd394d4..68558cf 100644 --- a/frontend/src/app/courses/Courses.module.css +++ b/frontend/src/app/courses/Courses.module.css @@ -4,6 +4,11 @@ flex-direction: column; } +.Row { + display: flex; + flex-direction: row; +} + .CoursesPage { position: absolute; top: 75px; diff --git a/frontend/src/app/courses/CoursesUtils.tsx b/frontend/src/app/courses/CoursesUtils.tsx new file mode 100644 index 0000000..0abdaa3 --- /dev/null +++ b/frontend/src/app/courses/CoursesUtils.tsx @@ -0,0 +1,42 @@ + +import { StudentCourse, StudentSemester, StudentYear } from "@/types/type-user"; + +export function BuildStudentYears(studentCourses: StudentCourse[]): StudentYear[]{ + + const grades = ["First-Year", "Sophomore", "Junior", "Senior"]; + const studentYears: StudentYear[] = grades.map(grade => ({ + grade, + studentSemesters: [], + })); + + studentCourses.sort((a, b) => a.term - b.term); + const semesterMap: Map = new Map(); + + for (const course of studentCourses) { + if (!semesterMap.has(course.term)) { + semesterMap.set(course.term, { term: course.term, studentCourses: [] }); + } + semesterMap.get(course.term)!.studentCourses.push(course); + } + + const studentSemesters = Array.from(semesterMap.values()).sort((a, b) => a.term - b.term); + + let yearIndex = 0; + let termsInYear = 0; + + for (const semester of studentSemesters) { + const isSummer = semester.term % 100 === 2; + + if (!isSummer) { + if (termsInYear >= 2 && yearIndex < 3) { + yearIndex++; + termsInYear = 0; + } + termsInYear++; + } + + studentYears[yearIndex].studentSemesters.push(semester); + } + + return studentYears; +} diff --git a/frontend/src/app/courses/page.tsx b/frontend/src/app/courses/page.tsx index ef80a8a..65cdc1d 100644 --- a/frontend/src/app/courses/page.tsx +++ b/frontend/src/app/courses/page.tsx @@ -4,39 +4,39 @@ import React, { useState, useEffect } from "react"; import Style from "./Courses.module.css"; import { useAuth } from "../providers"; -import { StudentSemester } from "@/types/type-user"; +import { StudentYear } from "@/types/type-user"; +import { BuildStudentYears } from "./CoursesUtils"; import NavBar from "@/components/navbar/NavBar"; -import SemesterBox from "./semester/SemesterBox"; -import AddSemesterButton from "./add-semester/AddSemesterButton"; +import YearBox from "./years/YearBox"; -function Courses() -{ +function Courses(){ const { user, setUser } = useAuth(); - const [renderedSemesters, setRenderedSemesters] = useState([]); + + const studentYears = BuildStudentYears(user.FYP.studentCourses); + const [renderedYears, setRenderedYears] = useState([]); const [edit, setEdit] = useState(false); - const toggleEdit = () => { - setEdit(!edit); - }; + const toggleEdit = () => { setEdit(!edit); }; + + const [columns, setColumns] = useState(true); + const toggleColumns = () => { setColumns(!columns); } useEffect(() => { - const newRenderedSemesters = user.FYP.studentSemesters.map((semester: StudentSemester, index: number) => ( - + const newRenderedYears = studentYears.map((studentYear: StudentYear, index: number) => ( + )); - setRenderedSemesters(newRenderedSemesters); - }, [edit, user, setUser]); + setRenderedYears(newRenderedYears); + }, [edit, columns, user]); return(
- + +
- {renderedSemesters} - {edit && } + {renderedYears}
diff --git a/frontend/src/app/courses/semester/course/CourseBox.tsx b/frontend/src/app/courses/semester/course/CourseBox.tsx deleted file mode 100644 index bc65b6c..0000000 --- a/frontend/src/app/courses/semester/course/CourseBox.tsx +++ /dev/null @@ -1,35 +0,0 @@ - -import Style from "./CourseBox.module.css"; -import { User, StudentCourse } from "@/types/type-user"; - -import { RenderMark, SeasonIcon, CourseBoxColor } from "./CourseBoxUtils"; -import DistributionCircle from "@/components/distribution-circle/DistributionsCircle"; - -// import { useModal } from "../../../hooks/modalContext"; -// const { setModalOpen } = useModal(); function openModal() { setModalOpen(props.SC.course) } // onClick={openModal} - -function CourseBox(props: {edit: boolean, studentCourse: StudentCourse, user: User, setUser: Function }){ - return( -
-
- - -
-
- {props.studentCourse.course.codes[0]} -
-
- {props.studentCourse.course.title} -
-
-
-
-
- -
-
-
- ); -} - -export default CourseBox; diff --git a/frontend/src/app/courses/semester/course/CourseBoxUtils.tsx b/frontend/src/app/courses/semester/course/CourseBoxUtils.tsx deleted file mode 100644 index 1e6ed6e..0000000 --- a/frontend/src/app/courses/semester/course/CourseBoxUtils.tsx +++ /dev/null @@ -1,85 +0,0 @@ - -import Style from "./CourseBox.module.css" -import Image from "next/image"; - -import { StudentCourse, User } from "@/types/type-user"; - -function RemoveCourse(props: { studentCourse: StudentCourse; user: User; setUser: Function }) -{ - const remove = () => { - const updatedStudentSemesters = props.user.FYP.studentSemesters.map((semester) => { - if(semester.season === props.studentCourse.term){ - return{ - ...semester, - studentCourses: semester.studentCourses.filter( - (studentCourse) => - studentCourse.course.title !== props.studentCourse.course.title - ), - }; - } - return semester; - }); - - const updatedUser = { ...props.user, FYP: { ...props.user.FYP, studentSemesters: updatedStudentSemesters } }; - props.setUser(updatedUser); - }; - - return ( -
- -
- ); -} - -export function RenderMark(props: { edit: boolean, studentCourse: StudentCourse, user: User, setUser: Function }) -{ - if(props.studentCourse.status === "DA"){ - return( -
- ✓ -
- ); - }else - if(props.studentCourse.status === "MA"){ - return( -
- {props.edit && } -
- ⚠ -
-
- - ); - } - return
; -} - -export function SeasonIcon(props: { studentCourse: StudentCourse }) -{ - const getSeasonImage = () => (String(props.studentCourse.term).endsWith("3") ? "/fall.svg" : "/spring.svg"); - return( -
- -
- ) -} - -export function CourseBoxColor(term: number) { - const currentYearMonth = new Date().toISOString().slice(0, 7); // "YYYY-MM" - - // Convert number to string and validate length - const termStr = String(term); - if (termStr.length !== 6) return "#E1E9F8"; // Default color if term format is invalid - - const year = termStr.slice(0, 4); // Extract year (YYYY) - const season = termStr.slice(4, 6); // Extract season (01, 02, or 03) - - // Define the cutoff month for each season - const seasonCutoff: { [key: string]: string } = { - "01": `${year}-06`, // Summer cutoff (June) - "02": `${year}-09`, // Fall cutoff (September) - "03": `${Number(year) + 1}-01`, // Spring cutoff (January of next year) - }; - - return currentYearMonth >= seasonCutoff[season] ? "#E1E9F8" : "#F5F5F5"; -} diff --git a/frontend/src/app/courses/years/YearBox.Module.css b/frontend/src/app/courses/years/YearBox.Module.css new file mode 100644 index 0000000..49940ad --- /dev/null +++ b/frontend/src/app/courses/years/YearBox.Module.css @@ -0,0 +1,10 @@ + +.Column { + display: flex; + flex-direction: column; +} + +.Row { + display: flex; + flex-direction: row; +} diff --git a/frontend/src/app/courses/years/YearBox.tsx b/frontend/src/app/courses/years/YearBox.tsx new file mode 100644 index 0000000..07e3e6e --- /dev/null +++ b/frontend/src/app/courses/years/YearBox.tsx @@ -0,0 +1,47 @@ + +import { useState, useEffect } from "react"; +import Style from "./YearBox.module.css"; +import { User, StudentYear, StudentSemester } from "@/types/type-user"; + +import SemesterBox from "./semester/SemesterBox" +import AddSemesterButton from "./add-semester/AddSemesterButton" + +function RenderSemesters(props: { edit: boolean; columns: boolean; studentSemesters: StudentSemester[], user: User, setUser: Function }) +{ + const newRenderedSemesters = props.studentSemesters.map((studentSemester: StudentSemester) => ( + + )); + + const addSemesterVis = props.studentSemesters.length < 3; + + return( +
+ {newRenderedSemesters} + {/* {(props.edit && addSemesterVis) && } */} +
+ ); +} + +function YearBox(props: { edit: boolean, columns: boolean, studentYear: StudentYear, user: User, setUser: Function }) +{ + const [renderedSemesters, setRenderedSemesters] = useState(null); + + useEffect(() => { + setRenderedSemesters( + + ); + }, [props.edit, props.columns, props.user]); + + return( +
+
+ {props.studentYear.grade} +
+
+ {renderedSemesters} +
+
+ ); +} + +export default YearBox; diff --git a/frontend/src/app/courses/add-semester/AddSemesterButton.module.css b/frontend/src/app/courses/years/add-semester/AddSemesterButton.module.css similarity index 100% rename from frontend/src/app/courses/add-semester/AddSemesterButton.module.css rename to frontend/src/app/courses/years/add-semester/AddSemesterButton.module.css diff --git a/frontend/src/app/courses/add-semester/AddSemesterButton.tsx b/frontend/src/app/courses/years/add-semester/AddSemesterButton.tsx similarity index 98% rename from frontend/src/app/courses/add-semester/AddSemesterButton.tsx rename to frontend/src/app/courses/years/add-semester/AddSemesterButton.tsx index 322de2f..55b7421 100644 --- a/frontend/src/app/courses/add-semester/AddSemesterButton.tsx +++ b/frontend/src/app/courses/years/add-semester/AddSemesterButton.tsx @@ -15,7 +15,7 @@ function executeAddSemester(props: { user: User; setUser: Function }, inputRef: const newTermNumber = Number(newTermString); const newSemester: StudentSemester = { - season: newTermNumber, + term: newTermNumber, studentCourses: [], }; diff --git a/frontend/src/app/courses/semester/SemesterBox.module.css b/frontend/src/app/courses/years/semester/SemesterBox.module.css similarity index 100% rename from frontend/src/app/courses/semester/SemesterBox.module.css rename to frontend/src/app/courses/years/semester/SemesterBox.module.css diff --git a/frontend/src/app/courses/semester/SemesterBox.tsx b/frontend/src/app/courses/years/semester/SemesterBox.tsx similarity index 74% rename from frontend/src/app/courses/semester/SemesterBox.tsx rename to frontend/src/app/courses/years/semester/SemesterBox.tsx index 020d34e..1dcc065 100644 --- a/frontend/src/app/courses/semester/SemesterBox.tsx +++ b/frontend/src/app/courses/years/semester/SemesterBox.tsx @@ -3,6 +3,7 @@ import React from "react"; import Style from "./SemesterBox.module.css" import { StudentSemester, User } from "@/types/type-user"; +import { TransformTermNumber, IsTermActive } from "@/utils/CourseDisplay"; import CourseBox from "./course/CourseBox"; import AddCourseButton from "./add-course/AddCourseButton"; @@ -16,11 +17,11 @@ function SemesterBox(props: { edit: boolean, studentSemester: StudentSemester, u return(
- {props.studentSemester.season} + {TransformTermNumber(props.studentSemester.term)}
{studentCourseBoxes} - {props.edit && } + {(props.edit && IsTermActive(props.studentSemester.term)) && }
); diff --git a/frontend/src/app/courses/semester/add-course/AddCourseButton.module.css b/frontend/src/app/courses/years/semester/add-course/AddCourseButton.module.css similarity index 100% rename from frontend/src/app/courses/semester/add-course/AddCourseButton.module.css rename to frontend/src/app/courses/years/semester/add-course/AddCourseButton.module.css diff --git a/frontend/src/app/courses/semester/add-course/AddCourseButton.tsx b/frontend/src/app/courses/years/semester/add-course/AddCourseButton.tsx similarity index 100% rename from frontend/src/app/courses/semester/add-course/AddCourseButton.tsx rename to frontend/src/app/courses/years/semester/add-course/AddCourseButton.tsx diff --git a/frontend/src/app/courses/semester/add-course/AddCourseUtils.ts b/frontend/src/app/courses/years/semester/add-course/AddCourseUtils.ts similarity index 100% rename from frontend/src/app/courses/semester/add-course/AddCourseUtils.ts rename to frontend/src/app/courses/years/semester/add-course/AddCourseUtils.ts diff --git a/frontend/src/app/courses/semester/course/CourseBox.module.css b/frontend/src/app/courses/years/semester/course/CourseBox.module.css similarity index 98% rename from frontend/src/app/courses/semester/course/CourseBox.module.css rename to frontend/src/app/courses/years/semester/course/CourseBox.module.css index 739f3ee..c44cf5f 100644 --- a/frontend/src/app/courses/semester/course/CourseBox.module.css +++ b/frontend/src/app/courses/years/semester/course/CourseBox.module.css @@ -37,7 +37,7 @@ justify-content: space-between; align-items: center; - width: 425px; + width: 420px; height: 36px; border-radius: 16px; diff --git a/frontend/src/app/courses/years/semester/course/CourseBox.tsx b/frontend/src/app/courses/years/semester/course/CourseBox.tsx new file mode 100644 index 0000000..f79207c --- /dev/null +++ b/frontend/src/app/courses/years/semester/course/CourseBox.tsx @@ -0,0 +1,61 @@ + +import Style from "./CourseBox.module.css"; +import { User, StudentCourse } from "@/types/type-user"; + +import { RenderMark, SeasonIcon, GetCourseColor, IsTermActive } from "./../../../../../utils/CourseDisplay"; +import DistributionCircle from "@/components/distribution-circle/DistributionsCircle"; + +// import { useModal } from "../../../hooks/modalContext"; +// const { setModalOpen } = useModal(); function openModal() { setModalOpen(props.SC.course) } // onClick={openModal} + +// function RemoveCourse(props: { studentCourse: StudentCourse; user: User; setUser: Function }) +// { +// const remove = () => { +// const updatedStudentSemesters = props.user.FYP.studentSemesters.map((semester) => { +// if(semester.season === props.studentCourse.term){ +// return{ +// ...semester, +// studentCourses: semester.studentCourses.filter( +// (studentCourse) => +// studentCourse.course.title !== props.studentCourse.course.title +// ), +// }; +// } +// return semester; +// }); + +// const updatedUser = { ...props.user, FYP: { ...props.user.FYP, studentSemesters: updatedStudentSemesters } }; +// props.setUser(updatedUser); +// }; + +// return ( +//
+// ); +// } + +function CourseBox(props: {edit: boolean, studentCourse: StudentCourse, user: User, setUser: Function }){ + return( +
+
+ {/* {(props.edit && IsTermActive(props.studentCourse.term)) && } */} + + +
+
+ {props.studentCourse.course.codes[0]} +
+
+ {props.studentCourse.course.title} +
+
+
+
+
+ +
+
+
+ ); +} + +export default CourseBox; diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index 1bf1005..ad22248 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -6,9 +6,14 @@ export const Ryan: User = { netID: "rgg32", onboard: true, FYP: { - studentSemesters: [ - { season: 202203, studentCourses: [{ term: 202203, status: "DA", course: { codes: ["CPSC 223"], title: "Data Structures", credit: 1, dist: ["QR"], seasons: [] } }] }, - { season: 202301, studentCourses: [{ term: 202503, status: "MA", course: { codes: ["CPSC 323"], title: "Systems Programming", credit: 1, dist: ["QR"], seasons: [] } }] }, + studentCourses: [ + { term: 202203, status: "DA", course: { codes: ["CPSC 201"], title: "Intro To Computer Science", credit: 1, dist: ["QR"], seasons: [] } }, + { term: 202301, status: "DA", course: { codes: ["MATH 244"], title: "Discrete Mathematics", credit: 1, dist: ["QR"], seasons: [] } }, + { term: 202303, status: "DA", course: { codes: ["CPSC 223"], title: "Data Structures", credit: 1, dist: ["QR"], seasons: [] } }, + { term: 202401, status: "DA", course: { codes: ["CPSC 323"], title: "Systems Programming", credit: 1, dist: ["QR"], seasons: [] } }, + { term: 202403, status: "DA", course: { codes: ["CPSC 365"], title: "Algorithms", credit: 1, dist: ["QR"], seasons: [] } }, + { term: 202501, status: "DA", course: { codes: ["CPSC 381"], title: "Machine Learning", credit: 1, dist: ["QR"], seasons: [] } }, + { term: 202503, status: "MA", course: { codes: ["CPSC 477"], title: "Quantum Computing", credit: 1, dist: ["QR"], seasons: [] } } ], languageRequirement: "", degreeDeclarations: [], diff --git a/frontend/src/pages/Courses/Courses.tsx b/frontend/src/pages/Courses/Courses.tsx deleted file mode 100644 index 0dacf99..0000000 --- a/frontend/src/pages/Courses/Courses.tsx +++ /dev/null @@ -1,142 +0,0 @@ - -import { useState, useEffect } from "react"; -import { Year } from "../../commons/types/TypeUser"; - -import Style from "./Courses.module.css"; - -import YearBox from "./year/YearBox"; -import nav_styles from "./../../navbar/NavBar.module.css"; -import logo from "./../../commons/images/ma_logo.png"; -import PageLinks from "./../../navbar/PageLinks"; - -import { User } from "../../commons/types/TypeUser"; -// import { StudentCourse } from "../../commons/types/TypeCourse"; - -import { yearTreeify } from "./CoursesUtils"; - -function NavBar() { - return ( -
-
- -
- -
- ); -} - -function Courses(props: { user: User, setUser: Function }){ - - const [yearTree, setYearTree] = useState([]); - const [renderedYears, setRenderedYears] = useState([]); - const [edit, setEdit] = useState(false); - - const updateEdit = () => { - setEdit(!edit); - }; - - useEffect(() => { - const transformedData = yearTreeify(props.user.FYP.studentCourses); - setYearTree(transformedData); - }, [props.user.FYP.studentCourses]); - - useEffect(() => { - const newRenderedYears = yearTree.map((year, index) => ( - - )); - setRenderedYears(newRenderedYears); - }, [edit, yearTree, props.setUser, props.user]); - - return( -
- -
- -
- {renderedYears} -
-
-
- ); -} - -export default Courses; - - - - -// export interface DisplaySetting { -// rating: boolean; -// workload: boolean; -// } - -// const defaultDisplaySetting = { rating: true, workload: true }; - -// function Settings(props: { -// displaySetting: DisplaySetting; -// updateDisplaySetting: Function; -// }) { -// const [isOpen, setIsOpen] = useState(false); -// const toggleDropdown = () => { -// setIsOpen(!isOpen); -// }; - -// const throwBack = (key: string) => { -// if (key === "rating") { -// const newSetting = { -// ...props.displaySetting, -// rating: !props.displaySetting.rating, -// }; -// props.updateDisplaySetting(newSetting); -// } else if (key === "workload") { -// const newSetting = { -// ...props.displaySetting, -// workload: !props.displaySetting.workload, -// }; -// props.updateDisplaySetting(newSetting); -// } -// }; - -// return ( -//
-// -// {isOpen && ( -//
-// -// -//
-// )} -//
-// ); -// } - - // const [displaySetting, setDisplaySetting] = useState(defaultDisplaySetting); - // const updateDisplaySetting = (newSetting: DisplaySetting) => { - // setDisplaySetting(newSetting); - // }; - // useEffect(() => {}, [displaySetting]); - - // yearTree \ No newline at end of file diff --git a/frontend/src/pages/Courses/CoursesUtils.ts b/frontend/src/pages/Courses/CoursesUtils.ts deleted file mode 100644 index cb1e679..0000000 --- a/frontend/src/pages/Courses/CoursesUtils.ts +++ /dev/null @@ -1,101 +0,0 @@ - -import { User, Year } from "../../commons/types/TypeUser"; -import { StudentCourse } from "../../commons/types/TypeCourse"; - -export const yearTreeify = (courses: StudentCourse[]): Year[] => { - const academicYears: { [key: number]: Year } = {}; - - courses.forEach(course => { - const year = Math.floor(course.term / 100); - const seasonCode = course.term % 100; - const academicYearKey = seasonCode === 3 ? year : year - 1; - - if (!academicYears[academicYearKey]) { - academicYears[academicYearKey] = { - grade: 0, - terms: [academicYearKey * 100 + 3, (academicYearKey + 1) * 100 + 1], - fall: [], - spring: [], - }; - } - - if (seasonCode === 3) { - academicYears[academicYearKey].fall.push(course); - } else { - academicYears[academicYearKey].spring.push(course); - } - }); - - const sortedYears = Object.keys(academicYears) - .map(key => parseInt(key)) - .sort((a, b) => a - b) - .map((key, idx) => { - academicYears[key].grade = idx + 1; - return academicYears[key]; - }); - - const lastYearKey = parseInt(Object.keys(academicYears).pop()!); - for (let i = sortedYears.length; i < 4; i++) { - const nextYearKey = lastYearKey + i - sortedYears.length + 1; - sortedYears.push({ - grade: sortedYears.length + 1, - terms: [nextYearKey * 100 + 3, (nextYearKey + 1) * 100 + 1], - fall: [], - spring: [], - }); - } - - return sortedYears; -}; - - - -export const xCheckMajorsAndSet = ( user: User, newCourse: StudentCourse, setUser: Function ): void => { - - // Update student courses - let updatedStudentCourses = user.FYP.studentCourses.map(existingCourse => { - if (existingCourse.course.codes.some(code => newCourse.course.codes.includes(code))) { - return newCourse; - } - return existingCourse; - }); - - // Check if newCourse was added to studentCourses - const courseExists = updatedStudentCourses.some(course => - course.course.codes.some(code => newCourse.course.codes.includes(code)) - ); - if (!courseExists) { - updatedStudentCourses.push(newCourse); - } - - // const updatedDegreeConfigurations = user.FYP.degreeConfigurations.map(configurationList => - // configurationList.map(configuration => { - // const updatedRequirements = configuration.degreeRequirements.map(requirement => { - // const updatedSubsections = requirement.subsections.map(subsection => ({ - // ...subsection, - // courses: subsection.courses.map(course => { - // if (course.course.codes.some(code => newCourse.course.codes.includes(code))) { - // return { ...course, term: newCourse.term, status: newCourse.status }; - // } - // return course; - // }) - // })); - // return { ...requirement, subsections: updatedSubsections }; - // }); - - // return { - // ...configuration, - // degreeRequirements: updatedRequirements - // }; - // }) - // ); - - // setUser({ - // ...user, - // FYP: { - // ...user.FYP, - // studentCourses: updatedStudentCourses, - // degreeConfigurations: updatedDegreeConfigurations - // } - // }); -}; diff --git a/frontend/src/pages/Courses/year/semester/course/CourseBox.tsx b/frontend/src/pages/Courses/year/semester/course/CourseBox.tsx deleted file mode 100644 index 2bb1dfe..0000000 --- a/frontend/src/pages/Courses/year/semester/course/CourseBox.tsx +++ /dev/null @@ -1,120 +0,0 @@ - -import Style from "./CourseBox.module.css"; -import "react-tooltip/dist/react-tooltip.css"; - -import img_fall from "./../../../../../commons/images/fall.png"; -import img_spring from "./../../../../../commons/images/spring.png"; -import DistributionsCircle from "./../../../../../commons/components/icons/DistributionsCircle" - -import { StudentCourse } from "../../../../../commons/types/TypeCourse"; -import { User } from "../../../../../commons/types/TypeUser"; -// import { useModal } from "../../../hooks/modalContext"; - -function RemoveCourse(props: { SC: StudentCourse, user: User, setUser: Function }){ - - const remove = () => { - // const updatedStudentCourses = props.user.FYP.studentCourses.filter( - // (course) => course.course.title !== props.SC.course.title || course.term !== props.SC.term - // ); - - const updatedDegreeConfigurations = props.user.FYP.degreeConfigurations.map((configurationList) => - configurationList.map((configuration) => { - // const updatedRequirements = configuration.degreeRequirements.map((requirement) => { - // const updatedSubsections = requirement.subsections.map((subsection) => { - // const updatedCourses = subsection.courses.filter( - // (course) => course.course.title !== props.SC.course.title - // ); - // return { ...subsection, courses: updatedCourses }; - // }); - - // return { ...requirement, subsections: updatedSubsections }; - // }); - - const newCodesAdded = configuration.codesAdded.filter( - (code) => !props.SC.course.codes.includes(code) - ); - - return { - ...configuration, - codesAdded: newCodesAdded - }; - }) - ); - - const updatedUser = { - ...props.user, - FYP: { - ...props.user.FYP, - degreeConfigurations: updatedDegreeConfigurations - } - }; - - props.setUser(updatedUser); - }; - - return( -
- -
- ) -} - -function CourseBox(props: {edit: boolean, SC: StudentCourse, user: User, setUser: Function }) { - - // const { setModalOpen } = useModal(); - // function openModal() { - // setModalOpen(props.SC.course) - // } - - const { status, term, course } = props.SC; - - const renderMark = () => { - if(status === "DA_COMPLETE" || status === "DA_PROSPECT"){ - return ( -
- ✓ -
- ); - }else if(status === "MA_HYPOTHETICAL" || "MA_VALID"){ - const mark = (status === "MA_HYPOTHETICAL") ? "⚠" : "☑"; - return ( -
- {props.edit && } -
- {mark} -
-
- ); - } - return
; - }; - - const getBackgroundColor = () => (status === "DA_COMPLETE" ? "#E1E9F8" : "#F5F5F5"); - const getSeasonImage = () => (String(term).endsWith("3") ? img_fall : img_spring); - - return ( -
- {/* onClick={openModal} */} - -
- {renderMark()} - -
-
- {course.codes[0]} -
-
- {course.title} -
-
-
-
-
- -
-
-
- ); -} - -export default CourseBox; diff --git a/frontend/src/pages/Majors/NavBar.module.css b/frontend/src/pages/Majors/NavBar.module.css deleted file mode 100644 index 8733628..0000000 --- a/frontend/src/pages/Majors/NavBar.module.css +++ /dev/null @@ -1,127 +0,0 @@ -.program { - background-color: #E1E9F8; - font-size: small; - font-weight:500; - padding-left:10px; - padding-right:10px; - padding-top:2px; - padding-bottom:2px; - margin-top: 5px; - margin-right: 5px; - border-radius: 15px; -} - -.program:hover { - filter: brightness(95%); -} - -.program:hover .close { - display: inline-block; -} - -.close { - display:none; - border:0px; - background-color: #E1E9F8; - padding:0px; - padding-right:6px; - margin:0px; -} - -.container { - flex-direction: column; - row-gap: 20px; -} - -.subheading { - color: #727272; - font-size: small; - font-weight: 500; - margin-top: 9px; - margin-right:10px; -} - -.search { - width: 350px; - height: 10px; - - padding: 10px; - - border: 1.5px solid #C2C2C2; - border-radius: 5px; -} - -.list { - display: none; - position:absolute; - margin-top:5px; - width:375px; - height: 100px; - overflow-y: scroll; - box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); - background-color: white; - border-radius: 5px; - z-index:1; -} - -.item { - height: 25px; - font-size: small; - padding-left: 10px; - padding-top: 5px; - background-color: white; -} - -.item:hover { - background-color: #C2C2C2; -} - -.row { - display: flex; - flex-direction: row; -} - -.column { - display: flex; - flex-direction: column; - - margin-right: auto; -} - -.NavBar { - position: fixed; - z-index: 2; - - left: 0; - width: 100%; - top: 0; - height: 75px; - - display: flex; - justify-content: space-between; - align-items: center; - box-sizing: border-box; - - background-color: #ffffff; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); -} - -.NavBar .activeLink { - color: #598ff4; - font-size: 20px; - /* Increase font size */ - text-decoration: none; - margin-right: 10px; - margin-left: 10px; - transition: color 0.3s; -} - -.NavBar .dormantLink { - color: #000000; - font-size: 20px; - /* Increase font size */ - text-decoration: none; - margin-right: 10px; - margin-left: 10px; - transition: color 0.3s; -} \ No newline at end of file diff --git a/frontend/src/types/type-user.ts b/frontend/src/types/type-user.ts index 90e2125..af4f769 100644 --- a/frontend/src/types/type-user.ts +++ b/frontend/src/types/type-user.ts @@ -12,17 +12,22 @@ export interface Course { export interface StudentCourse { course: Course; term: number; // 202401 - status: string; // "DA" || "MA" + status: string; // "DA" or "MA" } export interface StudentSemester { - season: number; + term: number; studentCourses: StudentCourse[]; } +export interface StudentYear { + grade: string; // "First-Year" | "Sophomore" | "Junior" | "Senior" + studentSemesters: StudentSemester[]; +} + export interface FYP { languageRequirement: string; - studentSemesters: StudentSemester[] + studentCourses: StudentCourse[]; degreeConfigurations: DegreeConfiguration[][]; degreeDeclarations: StudentDegree[]; } diff --git a/frontend/src/utils/CourseDisplay.module.css b/frontend/src/utils/CourseDisplay.module.css new file mode 100644 index 0000000..c44cf5f --- /dev/null +++ b/frontend/src/utils/CourseDisplay.module.css @@ -0,0 +1,66 @@ + +.row { + display: flex; + flex-direction: row; +} + + +.RemoveButton { + display: flex; + justify-content: center; + align-items: center; + width: 16px; /* adjust size as needed */ + height: 16px; + border-radius: 50%; + background-color: #ededed; + cursor: pointer; + margin-right: 5px; + /* optional border for better visibility */ + /* border: 1px solid #C0C0C0; */ +} + +.RemoveButton:hover { + background-color: #D3D3D3; /* slightly darker grey on hover */ +} + +.Checkmark { + justify-content: center; + text-align: center; + + font-weight: 550; + margin-right: 2px; +} + +.courseBox { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + width: 420px; + height: 36px; + + border-radius: 16px; + margin-bottom: 5px; + + padding-left: 10px; + padding-right: 10px; + + background-color: #F5F5F5; + transition: filter 0.4s ease; +} + +.CourseCode { + font-size: 12px; + font-weight: 500; +} + +.CourseTitle { + font-size: 8px; + font-weight: 500; +} + +.SeasonImage { + margin-top: 3px; + margin-right: 6px; +} diff --git a/frontend/src/utils/CourseDisplay.tsx b/frontend/src/utils/CourseDisplay.tsx new file mode 100644 index 0000000..dcfaeed --- /dev/null +++ b/frontend/src/utils/CourseDisplay.tsx @@ -0,0 +1,83 @@ + +import Style from "./CourseDisplay.module.css"; +import Image from "next/image"; +import { User, StudentCourse } from "@/types/type-user"; + +export function TransformTermNumber(term: number | string): string { + const termStr = term.toString(); + if (termStr.length !== 6) { + return "Invalid term format"; + } + + const year = termStr.substring(0, 4); + const seasonCode = termStr.substring(4, 6); + + let season = ""; + switch (seasonCode) { + case "01": + season = "Spring"; + break; + case "02": + season = "Summer"; + break; + case "03": + season = "Fall"; + break; + default: + return "Invalid term format"; + } + + return `${season} ${year}`; +} + +export function IsTermActive(term: number): boolean { + const currentYearMonth = new Date().toISOString().slice(0, 7); // "YYYY-MM" + + const termStr = String(term); + if (termStr.length !== 6) return true; // Treat invalid term formats as ended + + const year = termStr.slice(0, 4); // Extract year (YYYY) + const season = termStr.slice(4, 6); // Extract season (01, 02, or 03) + + // Define the cutoff month for each season + const seasonCutoff: { [key: string]: string } = { + "01": `${year}-06`, // Spring ends in June + "02": `${year}-09`, // Summer ends in September + "03": `${Number(year) + 1}-01`, // Fall ends in January of next year + }; + + return currentYearMonth < seasonCutoff[season]; +} + +export function GetCourseColor(term: number): string { + return IsTermActive(term) ? "#F5F5F5" : "#E1E9F8"; +} + +export function RenderMark(props: { edit: boolean, studentCourse: StudentCourse, user: User, setUser: Function }) +{ + if(props.studentCourse.status === "DA"){ + return( +
+ ✓ +
+ ); + }else + if(props.studentCourse.status === "MA"){ + return( +
+ ⚠ +
+ ); + } + return
; +} + +export function SeasonIcon(props: { studentCourse: StudentCourse }) +{ + const getSeasonImage = () => (String(props.studentCourse.term).endsWith("3") ? "/fall.svg" : "/spring.svg"); + return( +
+ +
+ ) +} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 5d0f8a1..0000000 --- a/package-lock.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "MajorAudit", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "jquery": "^3.7.1" - }, - "devDependencies": { - "@types/jquery": "^3.5.29" - } - }, - "node_modules/@types/jquery": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.29.tgz", - "integrity": "sha512-oXQQC9X9MOPRrMhPHHOsXqeQDnWeCDT3PelUIg/Oy8FAbzSZtFHRjc7IpbfFVmpLtJ+UOoywpRsuO5Jxjybyeg==", - "dev": true, - "dependencies": { - "@types/sizzle": "*" - } - }, - "node_modules/@types/sizzle": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", - "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", - "dev": true - }, - "node_modules/jquery": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", - "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 85d3341..0000000 --- a/package.json +++ /dev/null @@ -1,9 +0,0 @@ - -{ - "dependencies": { - "jquery": "^3.7.1" - }, - "devDependencies": { - "@types/jquery": "^3.5.29" - } -} From 91dfda77ebc0796e65d27d76529ef1d40655df8b Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Tue, 4 Feb 2025 18:58:00 -0500 Subject: [PATCH 07/38] stuff --- frontend/src/app/courses/CoursesUtils.tsx | 23 +++- frontend/src/app/courses/page.tsx | 14 +- .../src/app/courses/years/YearBox.Module.css | 7 + frontend/src/app/courses/years/YearBox.tsx | 20 ++- .../add-semester/AddSemesterButton.module.css | 25 +--- .../years/add-semester/AddSemesterButton.tsx | 123 ++++++++++-------- .../courses/years/semester/SemesterBox.tsx | 29 ++++- .../add-course/AddCourseButton.module.css | 2 +- .../years/semester/course/CourseBox.tsx | 48 ++++--- .../src/components/navbar/NavBar.module.css | 4 +- frontend/src/components/navbar/NavBar.tsx | 2 +- frontend/src/database/data-user.ts | 10 +- frontend/src/types/type-user.ts | 2 + 13 files changed, 173 insertions(+), 136 deletions(-) diff --git a/frontend/src/app/courses/CoursesUtils.tsx b/frontend/src/app/courses/CoursesUtils.tsx index 0abdaa3..2747480 100644 --- a/frontend/src/app/courses/CoursesUtils.tsx +++ b/frontend/src/app/courses/CoursesUtils.tsx @@ -1,20 +1,25 @@ import { StudentCourse, StudentSemester, StudentYear } from "@/types/type-user"; +import { IsTermActive } from "@/utils/CourseDisplay"; -export function BuildStudentYears(studentCourses: StudentCourse[]): StudentYear[]{ - +export function BuildStudentYears(studentCourses: StudentCourse[]): StudentYear[] { const grades = ["First-Year", "Sophomore", "Junior", "Senior"]; const studentYears: StudentYear[] = grades.map(grade => ({ grade, studentSemesters: [], + active: true, // Default to true, will update later })); studentCourses.sort((a, b) => a.term - b.term); const semesterMap: Map = new Map(); - + for (const course of studentCourses) { if (!semesterMap.has(course.term)) { - semesterMap.set(course.term, { term: course.term, studentCourses: [] }); + semesterMap.set(course.term, { + term: course.term, + studentCourses: [], + active: IsTermActive(course.term) + }); } semesterMap.get(course.term)!.studentCourses.push(course); } @@ -38,5 +43,15 @@ export function BuildStudentYears(studentCourses: StudentCourse[]): StudentYear[ studentYears[yearIndex].studentSemesters.push(semester); } + // Determine `active` status for each StudentYear + for (let i = 0; i < studentYears.length - 1; i++) { + const nextYear = studentYears[i + 1]; // Look ahead to the next year + + if (nextYear.studentSemesters.some(sem => !sem.active)) { + studentYears[i].active = false; + } + } + return studentYears; } + diff --git a/frontend/src/app/courses/page.tsx b/frontend/src/app/courses/page.tsx index 65cdc1d..07e44a5 100644 --- a/frontend/src/app/courses/page.tsx +++ b/frontend/src/app/courses/page.tsx @@ -13,21 +13,25 @@ import YearBox from "./years/YearBox"; function Courses(){ const { user, setUser } = useAuth(); - const studentYears = BuildStudentYears(user.FYP.studentCourses); - const [renderedYears, setRenderedYears] = useState([]); - const [edit, setEdit] = useState(false); const toggleEdit = () => { setEdit(!edit); }; const [columns, setColumns] = useState(true); const toggleColumns = () => { setColumns(!columns); } + const [studentYears, setStudentYears] = useState(() => BuildStudentYears(user.FYP.studentCourses)); + const [renderedYears, setRenderedYears] = useState([]); + + useEffect(() => { + setStudentYears(BuildStudentYears(user.FYP.studentCourses)); + }, [user.FYP.studentCourses]); + useEffect(() => { const newRenderedYears = studentYears.map((studentYear: StudentYear, index: number) => ( - + )); setRenderedYears(newRenderedYears); - }, [edit, columns, user]); + }, [edit, columns, studentYears, user]); return(
diff --git a/frontend/src/app/courses/years/YearBox.Module.css b/frontend/src/app/courses/years/YearBox.Module.css index 49940ad..1e68602 100644 --- a/frontend/src/app/courses/years/YearBox.Module.css +++ b/frontend/src/app/courses/years/YearBox.Module.css @@ -8,3 +8,10 @@ display: flex; flex-direction: row; } + +.Grade { + font-weight: 600; + font-size: 25px; + margin-right: 10px; + } + \ No newline at end of file diff --git a/frontend/src/app/courses/years/YearBox.tsx b/frontend/src/app/courses/years/YearBox.tsx index 07e3e6e..96e1372 100644 --- a/frontend/src/app/courses/years/YearBox.tsx +++ b/frontend/src/app/courses/years/YearBox.tsx @@ -6,39 +6,37 @@ import { User, StudentYear, StudentSemester } from "@/types/type-user"; import SemesterBox from "./semester/SemesterBox" import AddSemesterButton from "./add-semester/AddSemesterButton" -function RenderSemesters(props: { edit: boolean; columns: boolean; studentSemesters: StudentSemester[], user: User, setUser: Function }) +function RenderSemesters(props: { edit: boolean; columns: boolean; studentYear: StudentYear, setStudentYears: Function, user: User, setUser: Function }) { - const newRenderedSemesters = props.studentSemesters.map((studentSemester: StudentSemester) => ( + const newRenderedSemesters = props.studentYear.studentSemesters.map((studentSemester: StudentSemester) => ( )); - const addSemesterVis = props.studentSemesters.length < 3; - - return( + return(
{newRenderedSemesters} - {/* {(props.edit && addSemesterVis) && } */}
); } -function YearBox(props: { edit: boolean, columns: boolean, studentYear: StudentYear, user: User, setUser: Function }) +function YearBox(props: { edit: boolean, columns: boolean, studentYear: StudentYear, setStudentYears: Function, user: User, setUser: Function }) { const [renderedSemesters, setRenderedSemesters] = useState(null); useEffect(() => { setRenderedSemesters( - + ); - }, [props.edit, props.columns, props.user]); + }, [props.edit, props.columns, props.studentYear, props.user]); return(
-
+
{props.studentYear.grade}
-
+
{renderedSemesters} + {(props.edit && props.studentYear.active) && }
); diff --git a/frontend/src/app/courses/years/add-semester/AddSemesterButton.module.css b/frontend/src/app/courses/years/add-semester/AddSemesterButton.module.css index b6dc39b..1a8922f 100644 --- a/frontend/src/app/courses/years/add-semester/AddSemesterButton.module.css +++ b/frontend/src/app/courses/years/add-semester/AddSemesterButton.module.css @@ -27,7 +27,7 @@ flex-direction: row; justify-content: space-between; align-items: center; - width: 425px; + width: 200px; height: 36px; border-radius: 16px; margin-bottom: 5px; @@ -123,31 +123,14 @@ /* */ -.ConfirmButton { - display: flex; - justify-content: center; - align-items: center; - width: 18px; - height: 18px; - border-radius: 50%; - background-color: #dbdbdb; - cursor: pointer; - margin-right: 5px; -} -.ConfirmButton:hover { - background-color: #D3D3D3; -} - -/* */ - .RemoveButton { display: flex; justify-content: center; align-items: center; - width: 16px; - height: 16px; + width: 20px; + height: 20px; border-radius: 50%; - background-color: #ededed; + background-color: white; cursor: pointer; margin-right: 5px; } diff --git a/frontend/src/app/courses/years/add-semester/AddSemesterButton.tsx b/frontend/src/app/courses/years/add-semester/AddSemesterButton.tsx index 55b7421..58a896b 100644 --- a/frontend/src/app/courses/years/add-semester/AddSemesterButton.tsx +++ b/frontend/src/app/courses/years/add-semester/AddSemesterButton.tsx @@ -2,82 +2,99 @@ import { useRef, useState, useEffect } from "react"; import Style from "./AddSemesterButton.module.css"; -import { User, StudentSemester } from "@/types/type-user"; +import { StudentSemester, StudentYear } from "@/types/type-user"; -function executeAddSemester(props: { user: User; setUser: Function }, inputRef: React.RefObject, setDropVis: Function) -{ - if(inputRef.current) - { +function executeAddSemester( + props: { studentYear: StudentYear, setStudentYears: Function }, + inputRef: React.RefObject, + setDropVis: Function +) { + if (inputRef.current) { const newTermString = inputRef.current.value.trim(); - if(!/^\d{6}$/.test(newTermString)){ + + // Ensure input is a valid 6-digit number + if (!/^\d{6}$/.test(newTermString)) { return; } const newTermNumber = Number(newTermString); + + // Create a new semester object const newSemester: StudentSemester = { term: newTermNumber, studentCourses: [], + active: true, }; - const updatedSemesters = [...props.user.FYP.studentSemesters, newSemester]; - props.setUser({ ...props.user, FYP: { ...props.user.FYP, studentSemesters: updatedSemesters } }); + // Update studentYears with a new array reference + props.setStudentYears((prevYears: StudentYear[]) => { + return prevYears.map(year => { + if (year.grade === props.studentYear.grade) { + return { + ...year, + studentSemesters: [...year.studentSemesters, newSemester] // Create new array + }; + } + return year; + }); + }); + setDropVis(false); } } -function AddSemesterButton(props: { user: User; setUser: Function }) -{ - const [dropVis, setDropVis] = useState(false); + +function AddSemesterButton(props: { studentYear: StudentYear, setStudentYears: Function }) { + const [dropVis, setDropVis] = useState(false); const buttonRef = useRef(null); - const inputRef = useRef(null); - - - useEffect(() => { - function handleClickOutside(event: MouseEvent) { - if (buttonRef.current && !buttonRef.current.contains(event.target as Node)) { - setDropVis(false); - } - } - - if(dropVis){ - document.addEventListener("mousedown", handleClickOutside); - } - - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, [dropVis]); - - const handleKeyPress = (event: React.KeyboardEvent) => { - if(event.key === "Enter"){ - executeAddSemester(props, inputRef, setDropVis); - } - }; - - return ( + const inputRef = useRef(null); + + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (buttonRef.current && !buttonRef.current.contains(event.target as Node)) { + setDropVis(false); + } + } + + if (dropVis) { + document.addEventListener("mousedown", handleClickOutside); + } + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [dropVis]); + + // const handleKeyPress = (event: React.KeyboardEvent) => { + // if (event.key === "Enter") { + // executeAddSemester(props, inputRef, setDropVis); + // } + // }; + + return (
{!dropVis ? ( - + ) : ( -
-
-
setDropVis(false)}> - -
- - - -
executeAddSemester(props, inputRef, setDropVis)}> - -
-
-
+
+
+
setDropVis(false)}>
+ +
executeAddSemester(props, inputRef, setDropVis)}>
+
+
)}
); } + export default AddSemesterButton; diff --git a/frontend/src/app/courses/years/semester/SemesterBox.tsx b/frontend/src/app/courses/years/semester/SemesterBox.tsx index 1dcc065..e0af037 100644 --- a/frontend/src/app/courses/years/semester/SemesterBox.tsx +++ b/frontend/src/app/courses/years/semester/SemesterBox.tsx @@ -1,5 +1,5 @@ -import React from "react"; +import React, {useState, useEffect} from "react"; import Style from "./SemesterBox.module.css" import { StudentSemester, User } from "@/types/type-user"; @@ -8,19 +8,36 @@ import { TransformTermNumber, IsTermActive } from "@/utils/CourseDisplay"; import CourseBox from "./course/CourseBox"; import AddCourseButton from "./add-course/AddCourseButton"; +function RenderCourses(props: { edit: boolean, studentSemester: StudentSemester, user: User, setUser: Function }) +{ + const renderedCourses = props.studentSemester.studentCourses.map((studentCourse, index) => ( + + )); + + return( +
+ {renderedCourses} +
+ ); + } + function SemesterBox(props: { edit: boolean, studentSemester: StudentSemester, user: User, setUser: Function }) { - let studentCourseBoxes = props.studentSemester.studentCourses.map((studentCourse, index) => ( - - )); + const [renderedCourses, setRenderedCourses] = useState(null); + + useEffect(() => { + setRenderedCourses( + + ); + }, [props.edit, props.studentSemester, props.user]); return(
{TransformTermNumber(props.studentSemester.term)}
-
- {studentCourseBoxes} +
+ {renderedCourses} {(props.edit && IsTermActive(props.studentSemester.term)) && }
diff --git a/frontend/src/app/courses/years/semester/add-course/AddCourseButton.module.css b/frontend/src/app/courses/years/semester/add-course/AddCourseButton.module.css index b6dc39b..46a67f9 100644 --- a/frontend/src/app/courses/years/semester/add-course/AddCourseButton.module.css +++ b/frontend/src/app/courses/years/semester/add-course/AddCourseButton.module.css @@ -27,7 +27,7 @@ flex-direction: row; justify-content: space-between; align-items: center; - width: 425px; + width: 420px; height: 36px; border-radius: 16px; margin-bottom: 5px; diff --git a/frontend/src/app/courses/years/semester/course/CourseBox.tsx b/frontend/src/app/courses/years/semester/course/CourseBox.tsx index f79207c..7c460ec 100644 --- a/frontend/src/app/courses/years/semester/course/CourseBox.tsx +++ b/frontend/src/app/courses/years/semester/course/CourseBox.tsx @@ -8,36 +8,34 @@ import DistributionCircle from "@/components/distribution-circle/DistributionsCi // import { useModal } from "../../../hooks/modalContext"; // const { setModalOpen } = useModal(); function openModal() { setModalOpen(props.SC.course) } // onClick={openModal} -// function RemoveCourse(props: { studentCourse: StudentCourse; user: User; setUser: Function }) -// { -// const remove = () => { -// const updatedStudentSemesters = props.user.FYP.studentSemesters.map((semester) => { -// if(semester.season === props.studentCourse.term){ -// return{ -// ...semester, -// studentCourses: semester.studentCourses.filter( -// (studentCourse) => -// studentCourse.course.title !== props.studentCourse.course.title -// ), -// }; -// } -// return semester; -// }); - -// const updatedUser = { ...props.user, FYP: { ...props.user.FYP, studentSemesters: updatedStudentSemesters } }; -// props.setUser(updatedUser); -// }; - -// return ( -//
-// ); -// } +function RemoveCourse(props: { studentCourse: StudentCourse; user: User; setUser: Function }) +{ + const remove = () => { + const updatedStudentCourses = props.user.FYP.studentCourses.filter( + (course) => course.course.title !== props.studentCourse.course.title + ); + + const updatedUser = { + ...props.user, + FYP: { + ...props.user.FYP, + studentCourses: updatedStudentCourses + } + }; + + props.setUser(updatedUser); + }; + + return ( +
+ ); +} function CourseBox(props: {edit: boolean, studentCourse: StudentCourse, user: User, setUser: Function }){ return(
- {/* {(props.edit && IsTermActive(props.studentCourse.term)) && } */} + {(props.edit && IsTermActive(props.studentCourse.term)) && }
diff --git a/frontend/src/components/navbar/NavBar.module.css b/frontend/src/components/navbar/NavBar.module.css index 59bc85f..a0dc20d 100644 --- a/frontend/src/components/navbar/NavBar.module.css +++ b/frontend/src/components/navbar/NavBar.module.css @@ -42,8 +42,8 @@ } .Logo { - width: 150px; - height: auto; + /* width: 150px; + height: auto; */ margin-right: 10px; margin-left: 20px; } \ No newline at end of file diff --git a/frontend/src/components/navbar/NavBar.tsx b/frontend/src/components/navbar/NavBar.tsx index aa313f1..2fdadc2 100644 --- a/frontend/src/components/navbar/NavBar.tsx +++ b/frontend/src/components/navbar/NavBar.tsx @@ -8,7 +8,7 @@ function NavBar({utility}: {utility?: React.ReactNode}) { return(
- + {utility}
diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index ad22248..b5af992 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -7,13 +7,9 @@ export const Ryan: User = { onboard: true, FYP: { studentCourses: [ - { term: 202203, status: "DA", course: { codes: ["CPSC 201"], title: "Intro To Computer Science", credit: 1, dist: ["QR"], seasons: [] } }, - { term: 202301, status: "DA", course: { codes: ["MATH 244"], title: "Discrete Mathematics", credit: 1, dist: ["QR"], seasons: [] } }, - { term: 202303, status: "DA", course: { codes: ["CPSC 223"], title: "Data Structures", credit: 1, dist: ["QR"], seasons: [] } }, - { term: 202401, status: "DA", course: { codes: ["CPSC 323"], title: "Systems Programming", credit: 1, dist: ["QR"], seasons: [] } }, - { term: 202403, status: "DA", course: { codes: ["CPSC 365"], title: "Algorithms", credit: 1, dist: ["QR"], seasons: [] } }, - { term: 202501, status: "DA", course: { codes: ["CPSC 381"], title: "Machine Learning", credit: 1, dist: ["QR"], seasons: [] } }, - { term: 202503, status: "MA", course: { codes: ["CPSC 477"], title: "Quantum Computing", credit: 1, dist: ["QR"], seasons: [] } } + { term: 202403, status: "DA", course: { codes: ["CPSC 201"], title: "Intro To Computer Science", credit: 1, dist: ["QR"], seasons: [] } }, + { term: 202501, status: "DA", course: { codes: ["MATH 244"], title: "Discrete Mathematics", credit: 1, dist: ["QR"], seasons: [] } }, + { term: 202503, status: "MA", course: { codes: ["CPSC 223"], title: "Data Structures", credit: 1, dist: ["QR"], seasons: [] } }, ], languageRequirement: "", degreeDeclarations: [], diff --git a/frontend/src/types/type-user.ts b/frontend/src/types/type-user.ts index af4f769..c558864 100644 --- a/frontend/src/types/type-user.ts +++ b/frontend/src/types/type-user.ts @@ -17,11 +17,13 @@ export interface StudentCourse { export interface StudentSemester { term: number; + active: boolean; studentCourses: StudentCourse[]; } export interface StudentYear { grade: string; // "First-Year" | "Sophomore" | "Junior" | "Senior" + active: boolean; studentSemesters: StudentSemester[]; } From 977f2e0ddc16b8c6a1380d6171471de2e044ebf8 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Fri, 7 Feb 2025 18:11:18 -0500 Subject: [PATCH 08/38] middleware --- frontend/public/guy.jpg | Bin 0 -> 3258 bytes frontend/src/api/api.ts | 19 ++++++ frontend/src/app/api/login/route.ts | 7 +++ frontend/src/app/login/Login.module.css | 62 ++++++++++++++++++++ frontend/src/app/login/page.tsx | 55 +++++++++++++++++ frontend/src/app/not-found.tsx | 20 +++++++ frontend/src/app/onboard/Onboard.module.css | 0 frontend/src/app/onboard/page.tsx | 0 frontend/src/app/page.tsx | 14 ++--- frontend/src/app/providers.tsx | 14 ++--- frontend/src/database/data-user.ts | 21 +++++++ frontend/src/middleware.ts | 29 +++++++++ frontend/tsconfig.json | 2 +- 13 files changed, 226 insertions(+), 17 deletions(-) create mode 100644 frontend/public/guy.jpg create mode 100644 frontend/src/api/api.ts create mode 100644 frontend/src/app/api/login/route.ts create mode 100644 frontend/src/app/login/Login.module.css create mode 100644 frontend/src/app/login/page.tsx create mode 100644 frontend/src/app/not-found.tsx create mode 100644 frontend/src/app/onboard/Onboard.module.css create mode 100644 frontend/src/app/onboard/page.tsx create mode 100644 frontend/src/middleware.ts diff --git a/frontend/public/guy.jpg b/frontend/public/guy.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dabaf1d0b91bd18d69a2d55af4d820b77b923d85 GIT binary patch literal 3258 zcmbW1dpy(MAICqlF`G#%1S2=XsxV*50js1~ytT zEExbu0suhb2CTgY_5spTQcx*LX($v5gGs{?3bF_p83bA$C8waeSq-bY8H2%T>TBcF z^)xUT9jdM#fk-Bk)wF5GMxTp%X|=fw$%#BsRd zXb_j<9qPrzh4`|9aY4SE5DtzNi1Q4L#QAWzY<;-cqVYr`nNB1U>39P=o<10DXSsWqJ=p-VYN}^KNDav06|L1Zz1lLLfLsj)lVsq&Zpjy)-V+|DB8afX&Rj1 z#=_uAo0QR;wYF;O=x!rZ45>ym`o8^U408)htHTbCPDh-Nx_EgreNOta{DMP5!@?sX zqv9^aUrb26#JiT3&d?K~Qz&)Pd1(dH)H}ikn1+G9Nbv;s4O~f+Jg7Chj`kPXe+L%(f06wQ z?B87d00ImW7Y~dCOn{}II=ScII;CpQkE6&Pa4WXFVeJ!DF9yirCu(xqvb=J$&3N(< zZ+X(;$NMrj*^Jo!X9s81rA)HDoiHZIXsb#Y>f&f~&i8xu`KNw5&=V^xZ`pQ`8X(sy z_d^6B0q7W!*8p#MeC6<40nBLzr5A^2Fdh3oyzG=5Kg(q6FImIHsbvU;urhMfG4*`HWDOQZ=EIdp(w?b6Y~} zh+C)$o4kmVcJ&On=;@z%i$+6FOuBcx{WcMK@8K1z9>Un7~9 z^oPsm0d_Q$YmgNFRAZJhwCc-(f9oiXEb;l)_O)1jH1F~3E1{<=VS-_~yKX{d4KRdp z)wcg`t6#le$;!3SF+a5I#eZ7*u%q#yo_V`dn6c6C?b_t5a#l5l98T@>WasWQ7ZrY9 zRa7p|aZ{>F%KvDAt(X=-FW}36+?AEjlGC%ApW*dYiQ2Q(nI)e!7j`?Bd(D z{85c`-oz7B7w5>Ti>X0-Bu1^<=|d>S(@fzINbJxTF*|ChAMrg~$CfG|)})O)pI?Tz zb(a4+{lQ+x4mKx8oB!t5dsVJ4n)BfOvZ--qSAZFZwk zI}w%6g0G%+$Ly<{0_-=OnR9L5&|Cch{h%b=E;1RMVqRf88tV}oQ(-^!(>Fj6RGcF1 zx=0yYdZIb2+55y{b49tu+4#Fq%;Hckq1rxEw|DYT$s$S@LrUQq7dPX*lsX5-&Z}U0 z?=?jZK`Voed-y|MU%;cBrUH{2k)uCLsH6ksts z99p1Kq?ob>NCgEMSFg;ur@4oVPFh>5N$d-|eX%YqHQ_aXl|Rn^q8|K9NXAGM@RDJ?>Qj1lPd2mMH;8+;ptEXnr@@=1 zz2q$Z8X#@8>ylP0>xSH?liDU_^;UISyp|Bn{YatFAtShPg6FiGw{K}J0@PaK=9u}* z% zb-Xlg;B?zg9Tk^CE3`iqZq}aTUDCe8M)YC&L33awNxIuJ_1)t$ExXRpVc!_4CfmhBaXG0{Ftjw&WXLiZe;`%Xvv^`upFt>{9L1m>nvW4`fhSS~A$8 zT-KrSD13ev=jyh8LL3;qR6K9zo;ob4V7F)8F$v8!Qj@N)HIxX;l}=qSM^FQ^0RgSY zttyTx8J5Y=EmfWcqZA`@JYsjAcyh5!clKkdRst{0-ps_O4j_W#QxM1>=j7^l^W%Np zTqm0xS>O3X#C_}<#`24mV)ZqkX^~!=l}=A5Z5i+FYLkG z8Qs&NJ@=*5rQgq#V>mRv%QJbC2UPW8@cz4Z*xYQY1urgRIl-@bu#P_DX4>ifC~c~B z>zu(k&~t`)nMQ>pu`N`S@E*~+1rDtiU$e!q#`boVP#W^K>oEH10FaVy!i7NpJk7Bijafd{s^Hd~M zM#kdi{RtT4^w`R_H*d)eITMCC+_N#YF%@$&&1y+lTwRZT$qFSbqd(xl8<;%hWKIY^ zqa8G++|c5J53>C;)3*CAe?Kbd;J#z9d^7Utqy*8lt89AXL?1Kh8#Kk-?wFIygLw~z z)JhPE;NNy?>NN4%L}<#_jKcH$FLMU1C!pUI{k;#FUmH6eeczz_mlLI=Cg~mNX-Yxy zle2qW!d?iAEv77uJCsZMDK1mVR~v|FLoh_bDeEu|94A$(NG|k5V2I|Cwg1Q8RE$G`@MZ>r+qw2&cP$-)sKylGw$(_Zp995g|7Qk z{&r@H!gd8aGay34Kv$!nGo6qgkjgm7D3-&Hcc>9Y9IWMQlIA^AoiqA6eh6sO^Zr-$ zNmF~i{vP=5|7Y~~GWThW|0%E;X*4r#mC_WEHu%eSIyC>`^RbMi-(BZon6Q;K z;7*y>>wxLAj|Q(eOaq<{UTP0)ocI0|be0*hUN?5vnkgkDmPYq!Z|5g6_G%zEp(Yae^=~>)t1-Y_5zliQnD6cr(C;JA*!n<8NU=v3H8wR<;kS++cQ2 zj3@^vR{MN@mHcYuQP*Ugdv>8EmS1o}x^dZ8o~#P>FyZ^#yCK^%4~sw6_)A_Y(moHX zHtCUs`}d7aL_G8IbKmh+BW+rj^z_1@ha&=5()1RLkyX?c1`d4m{vO?F5tX)z?ZH;; zULL+=YwNGQ)iJs8Q%lrk)4Zm@mTH~Il`Dz~Odq8l>IRE8h3f0`+gh;=RT~!E6=VaX zS2r=$_E_ZdAePvDQZ2^Ynl@13I{A{3{LN@|DtF^Z>a>lh-$(nbz4S_9ZW=gm+vZ(J z*T-eK&VGUmHm4*qF09l&G?pt|B-@#WsgS4frY}e>-~x(uv+^~wBrG*<+0IO2M;~%S pcGdYb*D3a5TWI=7yoXNic|m`EO%40ydw%;y?9R|!o(^)Y@9)a}yCwht literal 0 HcmV?d00001 diff --git a/frontend/src/api/api.ts b/frontend/src/api/api.ts new file mode 100644 index 0000000..bba7e6b --- /dev/null +++ b/frontend/src/api/api.ts @@ -0,0 +1,19 @@ + +import { Ryan } from "@/database/data-user"; +import { NextRequest, NextResponse } from "next/server"; + +export function login(req: NextRequest) { + const user = Ryan; + + const response = NextResponse.json(user, { status: 200 }); + + response.cookies.set("session", "true", { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 60 * 60, + path: "/", + }); + + return response; +} diff --git a/frontend/src/app/api/login/route.ts b/frontend/src/app/api/login/route.ts new file mode 100644 index 0000000..7f9e03a --- /dev/null +++ b/frontend/src/app/api/login/route.ts @@ -0,0 +1,7 @@ + +import { NextRequest } from "next/server"; +import { login } from "@/api/api"; + +export async function GET(req: NextRequest) { + return login(req); +} diff --git a/frontend/src/app/login/Login.module.css b/frontend/src/app/login/Login.module.css new file mode 100644 index 0000000..11b2fcd --- /dev/null +++ b/frontend/src/app/login/Login.module.css @@ -0,0 +1,62 @@ +.centerDiv { + min-height: 100vh; + text-align: center; + max-width: 1000px; + margin-left: auto; + margin-right: auto; + justify-content: center; + align-items: center; + padding: 1rem; + display: flex; + flex-wrap: wrap; +} + +.featureListStyle { + list-style: none; + display: flex; + flex-direction: column; + gap: .5rem; + margin: 0; + padding: 0; +} + +.featureItemStyle { + font-weight: 500; + transition: transform .3s; + cursor: default; + text-align: left; +} + +.featureItemStyle:hover { + transform: translateX(7px); +} + +.loginButtons { + display: flex; + margin-left: auto; + margin-right: auto; + margin-top: 3px; + justify-content: flex-start; + justify-content: center; +} + +.btn { + text-decoration: none; + padding: 8px 24px; + border-radius: 10px; + font-weight: 500; + color: black; + background-color: lightblue; + transition: + transform 0.3s, + filter 0.3s; + margin-top: 8px; + font-size: large; + cursor: pointer; +} + +.btn:hover { + text-decoration: none; + transform: translateY(-5px); + filter: brightness(90%); +} \ No newline at end of file diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx new file mode 100644 index 0000000..6c893e5 --- /dev/null +++ b/frontend/src/app/login/page.tsx @@ -0,0 +1,55 @@ + +"use client"; +import { useRouter } from "next/navigation"; +import Style from "./Login.module.css"; + +import { useAuth } from "../providers"; + +function Login() +{ + const router = useRouter(); + const { setAuth, setUser } = useAuth(); + + const handleLogin = async () => { + try { + const response = await fetch("/api/login", { method: "GET" }); + + if (!response.ok) { + throw new Error("Login failed"); + } + + const data = await response.json(); + setAuth({ loggedIn: true }); + setUser(data); + + router.push("/graduation"); + } catch (error) { + console.error("❌ Login error:", error); + } + }; + + + return ( +
+
+
+

Plan Your Major @ Yale

+
    +
  • Explore 80+ Majors
  • +
  • Check Distributional Requirements
  • +
  • Plan Four-Year Plan
  • +
  • Cool Guy
  • +
+
+
+ Login w/ CAS +
+
+
+ Landing Page +
+
+ ); +} + +export default Login; diff --git a/frontend/src/app/not-found.tsx b/frontend/src/app/not-found.tsx new file mode 100644 index 0000000..0b7ddd6 --- /dev/null +++ b/frontend/src/app/not-found.tsx @@ -0,0 +1,20 @@ + +"use client"; +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { useAuth } from "@/app/providers"; + +export default function NotFoundPage() { + const { auth } = useAuth(); + const router = useRouter(); + + useEffect(() => { + if (auth.loggedIn) { + router.replace("/graduation"); + } else { + router.replace("/login"); + } + }, [auth.loggedIn, router]); + + return
Redirecting...
; +} diff --git a/frontend/src/app/onboard/Onboard.module.css b/frontend/src/app/onboard/Onboard.module.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/onboard/page.tsx b/frontend/src/app/onboard/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index b0171eb..839aa68 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -4,18 +4,16 @@ import { useEffect } from "react"; import { useRouter } from "next/navigation"; import { useAuth } from "./providers"; -export default function MajorAudit() +export default function MajorAudit() { const router = useRouter(); - const { auth } = useAuth(); + const { auth } = useAuth(); useEffect(() => { - if(!auth.loggedIn){ - router.push("/login"); - }else if(!auth.onboard){ - router.push("/onboard"); - }else{ - router.push("/graduation"); + if (auth.loggedIn) { + router.replace("/graduation"); + } else { + router.replace("/login"); } }, [auth, router]); diff --git a/frontend/src/app/providers.tsx b/frontend/src/app/providers.tsx index 7b73a64..9cd4599 100644 --- a/frontend/src/app/providers.tsx +++ b/frontend/src/app/providers.tsx @@ -2,15 +2,14 @@ "use client"; import { createContext, useContext, useState } from "react"; -import { User } from "../types/type-user"; -import { Ryan } from "./../database/data-user"; +import { User } from "@/types/type-user"; +import { NullUser } from "@/database/data-user"; const AuthContext = createContext(null); -export function AuthProvider({children}: {children: React.ReactNode}) -{ - const [auth, setAuth] = useState({ loggedIn: true, onboard: true }); - const [user, setUser] = useState(Ryan); +export function AuthProvider({ children }: { children: React.ReactNode }) { + const [auth, setAuth] = useState({ loggedIn: false }); + const [user, setUser] = useState(NullUser); return( @@ -19,7 +18,6 @@ export function AuthProvider({children}: {children: React.ReactNode}) ); } -export function useAuth() -{ +export function useAuth() { return useContext(AuthContext); } diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index b5af992..32dfeb7 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -25,3 +25,24 @@ export const Ryan: User = { ], } } + +export const NullUser: User = { + name: "", + netID: "", + onboard: false, + FYP: { + studentCourses: [], + languageRequirement: "", + degreeDeclarations: [], + degreeConfigurations: [ + [ + ], + [ + ], + [ + ], + [ + ] + ], + } +} diff --git a/frontend/src/middleware.ts b/frontend/src/middleware.ts new file mode 100644 index 0000000..d0737e3 --- /dev/null +++ b/frontend/src/middleware.ts @@ -0,0 +1,29 @@ + +import { NextRequest, NextResponse } from "next/server"; + +const protectedRoutes = ["/graduation", "/courses", "/majors"]; + +export default function middleware(req: NextRequest) { + const path = req.nextUrl.pathname; + + const sessionCookie = req.cookies.get("session")?.value; + const isLoggedIn = sessionCookie === "true"; + + if (protectedRoutes.includes(path) && !isLoggedIn) { + return NextResponse.redirect(new URL("/login", req.nextUrl)); + } + + if (path === "/login" && isLoggedIn) { + return NextResponse.redirect(new URL("/graduation", req.nextUrl)); + } + + if (path === "/") { + return NextResponse.redirect(new URL(isLoggedIn ? "/graduation" : "/login", req.nextUrl)); + } + + return NextResponse.next(); +} + +export const config = { + matcher: ["/((?!api|_next/static|_next/image|.*\\.png$).*)"], +}; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index c133409..cbe75e2 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -22,6 +22,6 @@ "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/middleware.ts"], "exclude": ["node_modules"] } From acc8b84cbdb04b72fbeb3faf118addb66a21280f Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Wed, 12 Feb 2025 00:17:59 -0500 Subject: [PATCH 09/38] account start --- .../public/account.png | Bin frontend/public/profile.png | Bin 0 -> 4726 bytes frontend/src/app/account/Account.module.css | 35 ++++++ .../account/meta-inputs/MetaInputs.module.css | 90 +++++++++++++++ .../app/account/meta-inputs/MetaInputs.tsx | 103 ++++++++++++++++++ frontend/src/app/account/page.tsx | 26 +++++ frontend/src/app/login/page.tsx | 9 +- frontend/src/app/onboard/Onboard.module.css | 0 frontend/src/app/onboard/page.tsx | 0 .../src/components/navbar/NavBar.module.css | 19 +++- frontend/src/components/navbar/NavBar.tsx | 41 ++++++- frontend/src/components/navbar/PageLinks.tsx | 26 ----- frontend/src/database/data-user.ts | 2 +- 13 files changed, 317 insertions(+), 34 deletions(-) rename cra-oldend/src/commons/images/profile.png => frontend/public/account.png (100%) create mode 100644 frontend/public/profile.png create mode 100644 frontend/src/app/account/Account.module.css create mode 100644 frontend/src/app/account/meta-inputs/MetaInputs.module.css create mode 100644 frontend/src/app/account/meta-inputs/MetaInputs.tsx create mode 100644 frontend/src/app/account/page.tsx delete mode 100644 frontend/src/app/onboard/Onboard.module.css delete mode 100644 frontend/src/app/onboard/page.tsx delete mode 100644 frontend/src/components/navbar/PageLinks.tsx diff --git a/cra-oldend/src/commons/images/profile.png b/frontend/public/account.png similarity index 100% rename from cra-oldend/src/commons/images/profile.png rename to frontend/public/account.png diff --git a/frontend/public/profile.png b/frontend/public/profile.png new file mode 100644 index 0000000000000000000000000000000000000000..abcb8706cfd6e2c993eafe296e3723e4784011ea GIT binary patch literal 4726 zcmV-+5{d1JP)X+uL$Nkc;* zP;zf(X>4Tx07!|IR|i;A$rhell8}&u5teyKyh#CF_m%IR@0)x7`_GyGoH=)H82~g+@FbEP zR1<()alSM*B!D?4IfY5;0tlc1DzF7~9zRdQj*gB1&<4(bw|6(7H?*sca;<;%|5wuz z2=n*=Akh$~3G(>45FY>lui{Ij`2Y~*yv3A!i5$ZQbfhprh!@Inh7woFahekEQTW8i zav*L2K#j`dNizVTUxqxhfS&<1=)Dj-iv=Pv02)k)eRFds2_Q~|*cSRoAeQU1(tgm% z_?u?hpeB!(F^H9OQJ7kwC{L2ZoA!T}<9}Ojj%*-?HEh&OX=to`&aiH`vL}VhF%{w- zaav>)#M%&l5D66i5Tle#S!jaNA2s6VabV4W9>mT9USK%HMi2*!b0Q;Dd|J9FmeTWwd^Mc}3J{zTzVpXxw`gAFWt>Vw|q>8xmSPx{`32c@A>r5e6rH|1I zGvkvWra)|2AWDpcm=3X1UUpo#%4bwzCMR;hM=Fbz=WhvdtWX>hpw!0-(xt(%D*uw) zyn*>*8#6`RNEP0fpBW#jtPj@0=PB}sbHOeP#q5Lu9bw*>h=I8Xgn>cIxno^IaYCF* z=Yu3aAXYgRr$}<5RsOiSFegOLn?vlHR}iNfiznv81*z1>(@3Yq$_$06eF_1Q;2gxILLw5GQ}wIV6;T=1Tvz3MCZZAhxz8fCh!0E z`~dr%VM8WD#mELCGBpM~i9y6bVkj|~Xonl(zPL9Y0gwK$x!^3^b6_6d{RvMA?Sw~! z>x2%%?TMly=}%dX1|3j8SXGO(A8UbILrph40QZ6FP#M?54S^%>3gfVGKN#5!vK&?3 z@~SX@(*D~Fez+SdKe96!B@ZF{lWl(F7CVWZ$BtkPfQcQ)8nIUFs2mTjt|yAsH4rUUkxw?%5&a|Hz@3#>GS9iz zx!d`RvM*maRsN0!oJo>tQc*@`K9dc<2tp=T%y$~bba8fZ1wj6NQNF(3#3;TSh+gY= zZr&dN_#E%=??3#V3p);n5h12_Us zf-~SExD2j?yKwb%f)}6{yaS&R1R)_bgn<|!ric~dfVdzm#1{!dB9K@l8A(Ml5HTV{ zW+3yBQe*{Efowu*kiEzuVnP{VE8!sFG<=pi3GawRq8`zP=mDR_WMUR^I&m>^9dS4D1hJj?nAk@m zkqk%hu8=xOAJo*gYE)jv|TsrFP3bsSYjT|(VPJwd%qeM6(stZDwV6k0xQ39W{9iuQo^ zfzF^i(YbU1eKx(4UQfS5f2l#%u+m^_OwcIQDAU-laY>^`ldNg2$!7th6RJe;4|hiwlGdJI(3LT zHac9LES*xFT{>+#y}H`EuDXf3Q*|qKkLq^lVS3hjT)iBg)VmyRBL^x#8 zkh&pPhP>Aws_(Ba)GyKBt$$trlL6Bp$ROKbxj}=$eM8*P-Y~{+is5?0(}q1mb%uHm ze_SxLVJi)xke7E_13yKBHBHdz@MYBb( zrK#m;%NdrnmUpbkRxGPbt2I_>o2hr(ZDjDs z*&`cAzH)VN6}ql>z2(Mm8|}8ht=a9XyNA2fz0SSc!_p(wqsrqZONSNBTFg45?|3$F-&6k#7Bjc6K;jt(2W za`e4Ot4MKVLllVOMy-sxA8ivoIr?x6F(x{uGUjRQh}apiXX6-g6XSNoy^9ZsUmAZm z!8Spb(444|I4-d!@qJQY(u$;xF~i5q9CI<*Fj!H#TVO>am^U+{P7; zyD{Eo{M7O1Cm2r1nQ(NX#zfx4{i%f1q|_a$UwBcxYF=L&H?1nIm(Ss^;lB|03swob zg+9U+!e{9|=_}H^GJG;tW^`xzWtL_3h&ZAO(W|V`tPNT3v!k-NW%uVKKf(;Atg`$ORi$*S5 zyXaeqpyWcSb7@)Wm&N?W7k(Y_Yx%GJOVXFLEp=bIei?C@c-f8Re#^J7&{|Qr;_=GS zE9+M=SCy=KyL#g4^JT7O8`ewX)joo#S?1-W9m3vCgq>({8=prF+00*`DWnQ}^E37rw7~KWl&8 z0jmR52N?%T>XG_s^}P+54IPcijqQiR4mCGklvm=#9b&oDRMmaYBSpV_D z<9#P2Cwfk1oP69ovH9+)F{iGz#I&@X4nKYFOz@dgXW3_upYu6)yg*nZzSBfb#vU! zj$49TUAJ>?zq(U!=kwh;_Xzh&?rYvJdtmgS`VYH5c6YdU9DNx0u=P>gqq~p!k6%2= zd-A1o{!`l1vS+5xYPwvyj&uiiUw%IJdFKnsi_bm9e`@_%_0sm`fxrC!YV94<+xbfR zs{i%kHwJIEy>)%t+!x*V;9c&!&+kh<7<}0Nk@fNHr=(BMK2P~V`cnSY?rYPx@Nf6} zbNl-xJSk7{D}cdd6k+#@Q?9e_Ic{~pr= z|88yquxAV0enEYWaR5Zq0EqVl=*W?SZD6ma*jLJp2=@USi1(U77rUF6*;oe<)2_EZ7vL(l#+;c9n^`746 zTDHjHaQ9|BNKlFf*VVYfND?QotoV%a2p+)2g92^A96Wonfd6|?fr|v2{|@>J*XF-P zoO-v;UQC4!bFfW_K63?PCg7~`fbK%=I+NJjxhnXG#!O9zo}ZnFUBu%~Eh=`|wX+dX zK?GL;a`#ut zQi`_`>|ITdSSUT1n7_fh)@)qIMx@=q-I*9y`x_LNn09spfwMq@E^McAQxN*| z^(G6jX}8^LteF+Mh-eeZWKUeht_nqgi)!?k5&|MDp6|1?PF&TR?NYQBl zHihuL=dR=7vRI_(yo-=rlYuMnYPh>v=GTe&ebnGPkfQMZ5+!bq;6LD@B8D4o3fvU9 zDR5KZrocoM7}F`tledkOw&WibN{pw9)ztNdYY0e@l8WnUR2Vt$1B!SAIX+j{^*wHf z!B7HfwDT9pk&KB0f>TKT!xpb63ysRRAb)C|uSEV@*?|h23^Xdu!E1?T%FCKkaI{EK zSpk|F7|T3sWUZrtMkP}auK56i2-)WlQ4nqiYA^&!1gXh*-}uJ`?yGBsaRnyTec#~= z0(=0=zJYw!zlU?5!y(7|ColyS*cBA<0c0%3Ja;5}*?`fpLWa+vZ=o!32c{rn!~$bK zfP1jZ17?j1i~@zI1?VGQ4*v}pQ#6Ec1#_n03+PibdsJXhB#7qoL@AsOT0y^Htc5kX z3dvsCa}YctimP}9e07klae_P`ENgTXlD+o=6S^kLdSx1RF>7`0tFwr%uWf;1f^GK! zbyUd3+HFF1dYxT^?@_=CoGMuR6X+tM@@dlL$z(nONAAmBY$CqE$7!={9fC38+>oIm zn}{E6O(zaPm}suZ(3nj`Ej3?Q0`Pk=ci6s()Tk9HW+nBF3tOX2dy?NfNsCQedlbmD zh)20rGmRq`W9~9!r`5u&H=Vku+QCeLW4)4$u$L3g2S_5 zTZ&22K-&Z?m!gu;r=-D(0`fgKVQn%jXkOA_bq67MSV-V^fLtn+)Hnq!K+#DGuyEUv zukfdZi_oY2XYN3L1Gx*OZ(*M#b6s1avA^r)5?oM3eh+W7Yrwx;Q?uWoaJL6<0?HcX z&`5#*g0FBB3KP2GJw{a#!{Sh);}duU`9GxgAn*UjPt+S0=&hFPd;kCd07*qoM6N<$ Ef+;!RM*si- literal 0 HcmV?d00001 diff --git a/frontend/src/app/account/Account.module.css b/frontend/src/app/account/Account.module.css new file mode 100644 index 0000000..e7cf6b9 --- /dev/null +++ b/frontend/src/app/account/Account.module.css @@ -0,0 +1,35 @@ + +.Row { + display: flex; + flex-direction: row; +} + +.Column { + display: flex; + flex-direction: column; +} + +.AccountPage { + position: absolute; + top: 75px; + + display: flex; + flex-direction: column; + align-items: center; + + width: 100%; + padding-top: 50px; + padding-bottom: 200px; +} + +.AccountContent { + width: 600px; /* Adjust the width as needed */ + padding: 20px; + background-color: white; /* Optional, just to make the box visible */ + border-radius: 10px; /* Optional, for rounded corners */ + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Optional, for a subtle shadow */ + + display: flex; + flex-direction: column; + align-items: flex-start; /* Ensures text and elements inside are left-aligned */ +} diff --git a/frontend/src/app/account/meta-inputs/MetaInputs.module.css b/frontend/src/app/account/meta-inputs/MetaInputs.module.css new file mode 100644 index 0000000..dc3d033 --- /dev/null +++ b/frontend/src/app/account/meta-inputs/MetaInputs.module.css @@ -0,0 +1,90 @@ + + +.Row { + display: flex; + flex-direction: row; +} + +.Column { + display: flex; + flex-direction: column; +} + +.InputContainer { + display: flex; + flex-direction: row; + align-items: flex-end; +} + +.Label { + font-weight: bold; + margin-right: 4px; + margin-bottom: 3px; +} + +.InputBox { + height: 10px; + padding: 4px; + border: 2px solid #ccc; + border-radius: 5px; + font-size: 14px; + transition: border-color 0.3s ease-in-out; +} + +.InputBox:focus { + border-color: #82beff; /* Blue border on focus */ + outline: none; +} + +.LanguageLevelBox { + height: 24px; /* Matches InputBox height */ + padding: 5px 7px; /* Matches InputBox padding */ + border: 2px solid #ccc; /* Matches InputBox border */ + border-radius: 5px; /* Matches InputBox border-radius */ + font-size: 14px; /* Matches InputBox font-size */ + background-color: white; + cursor: pointer; + display: flex; + align-items: center; + box-sizing: border-box; + color: black; + position: relative; + transition: border-color 0.3s ease-in-out; +} + +.LanguageLevel:hover { + border-color: #aaa; +} + +.LanguageLevelOptions { + position: absolute; + background-color: #fff; + border: 1px solid #ccc; + border-radius: 4px; + top: 100%; + left: 0; + margin-top: 5px; + z-index: 9999; + /* width: 90px; */ + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); + max-height: 200px; + overflow-y: auto; + font-size: 12px; + box-sizing: border-box; +} + +.LanguageLevelOptions div { + padding: 8px; + cursor: pointer; + color: black; + background-color: white; +} + +.LanguageLevelOptions div:hover { + background-color: #f0f0f0; +} + +.SelectedTerm { + background-color: #93c7ff !important; /* Light blue background for the selected term */ + color: black; /* Optional: change text color for selected term */ +} \ No newline at end of file diff --git a/frontend/src/app/account/meta-inputs/MetaInputs.tsx b/frontend/src/app/account/meta-inputs/MetaInputs.tsx new file mode 100644 index 0000000..c442537 --- /dev/null +++ b/frontend/src/app/account/meta-inputs/MetaInputs.tsx @@ -0,0 +1,103 @@ + +"use client"; +import { useState, useEffect, useRef } from "react"; +import Style from "./MetaInputs.module.css"; + +const languageList: string[] = ["Spanish", "French", "German", "Chinese", "Japanese", "Italian", "Latin", "Greek"]; +const levelList: string[] = ["L1", "L2", "L3", "L4", "L5"]; + +function LanguagePlacement(){ + const [languageDropdownVisible, setLanguageDropdownVisible] = useState(false); + const [levelDropdownVisible, setLevelDropdownVisible] = useState(false); + const [selectedLanguage, setSelectedLanguage] = useState(""); + const [selectedLevel, setSelectedLevel] = useState(""); + + const languageDropdownRef = useRef(null); + const levelDropdownRef = useRef(null); + + // Click outside handler + const handleClickOutside = (event: MouseEvent) => { + if (languageDropdownRef.current && !languageDropdownRef.current.contains(event.target as Node)) { + setLanguageDropdownVisible(false); + } + if (levelDropdownRef.current && !levelDropdownRef.current.contains(event.target as Node)) { + setLevelDropdownVisible(false); + } + }; + + // Attach event listener when dropdowns are open + useEffect(() => { + if (languageDropdownVisible || levelDropdownVisible) { + document.addEventListener("mousedown", handleClickOutside); + } else { + document.removeEventListener("mousedown", handleClickOutside); + } + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [languageDropdownVisible, levelDropdownVisible]); + + return ( +
+
+
Language Placement:
+
setLanguageDropdownVisible(!languageDropdownVisible)}> + {selectedLanguage} + {languageDropdownVisible && ( +
+ {languageList.map((lang, index) => ( +
{ setSelectedLanguage(lang); setLanguageDropdownVisible(false); }} className={lang === selectedLanguage ? Style.SelectedLanguageLevel : ""}> + {lang} +
+ ))} +
+ )} +
+
setLevelDropdownVisible(!levelDropdownVisible)}> + {selectedLevel} + {levelDropdownVisible && ( +
+ {levelList.map((level, index) => ( +
{ setSelectedLevel(level); setLevelDropdownVisible(false); }} className={level === selectedLevel ? Style.SelectedLanguageLevel : ""}> + {level} +
+ ))} +
+ )} +
+
+
+ ); +} + + + +function FirstShelf() { + const [formData, setFormData] = useState({ + name: "", + gradYear: "", + languagePlacement: "", + }); + + const handleChange = (e: any) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + return ( +
+
+
+ Name +
+ +
+
+
+ Year +
+ +
+ +
+ ); +} + +export default FirstShelf; \ No newline at end of file diff --git a/frontend/src/app/account/page.tsx b/frontend/src/app/account/page.tsx new file mode 100644 index 0000000..99924e5 --- /dev/null +++ b/frontend/src/app/account/page.tsx @@ -0,0 +1,26 @@ + +import Style from "./Account.module.css"; +import NavBar from "@/components/navbar/NavBar"; + +import FirstShelf from "./meta-inputs/MetaInputs"; + +function Account(){ + return( +
+ +
+
+
+ Profile +
+
+ Configure basic degree data. +
+ +
+
+
+ ) +} + +export default Account; \ No newline at end of file diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx index 6c893e5..4eb3ab4 100644 --- a/frontend/src/app/login/page.tsx +++ b/frontend/src/app/login/page.tsx @@ -4,6 +4,7 @@ import { useRouter } from "next/navigation"; import Style from "./Login.module.css"; import { useAuth } from "../providers"; +import NavBar from "@/components/navbar/NavBar"; function Login() { @@ -22,7 +23,12 @@ function Login() setAuth({ loggedIn: true }); setUser(data); - router.push("/graduation"); + if(!data.onboard){ + router.push("/account"); + }else{ + router.push("/graduation"); + } + } catch (error) { console.error("❌ Login error:", error); } @@ -31,6 +37,7 @@ function Login() return (
+

Plan Your Major @ Yale

diff --git a/frontend/src/app/onboard/Onboard.module.css b/frontend/src/app/onboard/Onboard.module.css deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/app/onboard/page.tsx b/frontend/src/app/onboard/page.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/components/navbar/NavBar.module.css b/frontend/src/components/navbar/NavBar.module.css index a0dc20d..95ce719 100644 --- a/frontend/src/components/navbar/NavBar.module.css +++ b/frontend/src/components/navbar/NavBar.module.css @@ -42,8 +42,23 @@ } .Logo { - /* width: 150px; - height: auto; */ margin-right: 10px; margin-left: 20px; +} + +.Circle { + width: 35px; + height: 35px; + display: flex; + align-items: center; + justify-content: center; + background-color: white; + border-radius: 50%; + border: 2px solid transparent; /* Default border is invisible */ + + transition: background-color 0.3s ease-in-out, border-color 0.3s ease-in-out; +} + +.Circle:hover { + border-color: #cce5ff; /* Blue border appears on hover */ } \ No newline at end of file diff --git a/frontend/src/components/navbar/NavBar.tsx b/frontend/src/components/navbar/NavBar.tsx index 2fdadc2..c0806ad 100644 --- a/frontend/src/components/navbar/NavBar.tsx +++ b/frontend/src/components/navbar/NavBar.tsx @@ -1,17 +1,50 @@ "use client"; -import Image from "next/image"; import Style from "./NavBar.module.css"; -import PageLinks from "./PageLinks"; -function NavBar({utility}: {utility?: React.ReactNode}) { +import { usePathname } from "next/navigation"; +import Image from "next/image"; +import Link from "next/link"; + +function AccountButton() { + return( +
+ +
+ ); +} + +function PageLinks() +{ + const pathname = usePathname(); + + return( +
+ + Graduation + + + Courses + + + Majors + + + + +
+ ); +} + + +function NavBar({utility, loggedIn = true }: { utility?: React.ReactNode; loggedIn?: boolean }) { return(
{utility}
- + {loggedIn && }
); } diff --git a/frontend/src/components/navbar/PageLinks.tsx b/frontend/src/components/navbar/PageLinks.tsx deleted file mode 100644 index 13a9d62..0000000 --- a/frontend/src/components/navbar/PageLinks.tsx +++ /dev/null @@ -1,26 +0,0 @@ - -"use client"; -import { usePathname } from "next/navigation"; -import Link from "next/link"; -import Style from "./NavBar.module.css"; - -function PageLinks() -{ - const pathname = usePathname(); - - return( -
- - Graduation - - - Courses - - - Majors - -
- ); -} - -export default PageLinks; diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index 32dfeb7..bbf09d1 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -4,7 +4,7 @@ import { User } from "./../types/type-user"; export const Ryan: User = { name: "Ryan", netID: "rgg32", - onboard: true, + onboard: false, FYP: { studentCourses: [ { term: 202403, status: "DA", course: { codes: ["CPSC 201"], title: "Intro To Computer Science", credit: 1, dist: ["QR"], seasons: [] } }, From 9084ba9c53db3f56f39b2a0b8ed5ab98cd966f3e Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Sun, 23 Feb 2025 17:26:08 -0500 Subject: [PATCH 10/38] arrangement --- .../app/account/meta-inputs/MetaInputs.tsx | 2 +- frontend/src/app/courses/CoursesUtils.tsx | 78 +++++--------- frontend/src/app/courses/page.tsx | 6 +- frontend/src/app/courses/years/YearBox.tsx | 2 +- .../courses/years/semester/SemesterBox.tsx | 2 +- .../add-course/AddCourseButton.module.css | 28 ++++- .../semester/add-course/AddCourseButton.tsx | 100 ++++++++++-------- frontend/src/app/providers.tsx | 11 +- frontend/src/database/data-catalog.ts | 10 ++ frontend/src/database/data-cpsc.ts | 86 +++++++++++++++ frontend/src/database/data-user.ts | 44 +++++--- frontend/src/types/type-program.ts | 40 +++++-- frontend/src/types/type-user.ts | 18 +++- 13 files changed, 287 insertions(+), 140 deletions(-) create mode 100644 frontend/src/database/data-cpsc.ts diff --git a/frontend/src/app/account/meta-inputs/MetaInputs.tsx b/frontend/src/app/account/meta-inputs/MetaInputs.tsx index c442537..00f103a 100644 --- a/frontend/src/app/account/meta-inputs/MetaInputs.tsx +++ b/frontend/src/app/account/meta-inputs/MetaInputs.tsx @@ -82,7 +82,7 @@ function FirstShelf() { }; return ( -
+
Name diff --git a/frontend/src/app/courses/CoursesUtils.tsx b/frontend/src/app/courses/CoursesUtils.tsx index 2747480..0a341f4 100644 --- a/frontend/src/app/courses/CoursesUtils.tsx +++ b/frontend/src/app/courses/CoursesUtils.tsx @@ -1,57 +1,27 @@ - -import { StudentCourse, StudentSemester, StudentYear } from "@/types/type-user"; -import { IsTermActive } from "@/utils/CourseDisplay"; - -export function BuildStudentYears(studentCourses: StudentCourse[]): StudentYear[] { - const grades = ["First-Year", "Sophomore", "Junior", "Senior"]; - const studentYears: StudentYear[] = grades.map(grade => ({ - grade, - studentSemesters: [], - active: true, // Default to true, will update later - })); - - studentCourses.sort((a, b) => a.term - b.term); - const semesterMap: Map = new Map(); - - for (const course of studentCourses) { - if (!semesterMap.has(course.term)) { - semesterMap.set(course.term, { - term: course.term, - studentCourses: [], - active: IsTermActive(course.term) - }); - } - semesterMap.get(course.term)!.studentCourses.push(course); - } - - const studentSemesters = Array.from(semesterMap.values()).sort((a, b) => a.term - b.term); - - let yearIndex = 0; - let termsInYear = 0; - - for (const semester of studentSemesters) { - const isSummer = semester.term % 100 === 2; - - if (!isSummer) { - if (termsInYear >= 2 && yearIndex < 3) { - yearIndex++; - termsInYear = 0; - } - termsInYear++; - } - - studentYears[yearIndex].studentSemesters.push(semester); - } - - // Determine `active` status for each StudentYear - for (let i = 0; i < studentYears.length - 1; i++) { - const nextYear = studentYears[i + 1]; // Look ahead to the next year - - if (nextYear.studentSemesters.some(sem => !sem.active)) { - studentYears[i].active = false; - } - } +import { User, StudentSemester, StudentYear } from "@/types/type-user"; + +export function BuildStudentYears(user: User): StudentYear[] +{ + const { studentTermArrangement, studentCourses } = user.FYP; + + const firstYearTerms = studentTermArrangement.first_year; + const sophomoreTerms = studentTermArrangement.sophomore; + const juniorTerms = studentTermArrangement.junior; + const seniorTerms = studentTermArrangement.senior; + + const buildSemesters = (terms: number[]): StudentSemester[] => { + return terms.map(term => ({ + term, + studentCourses: studentCourses.filter(course => course.term === term), + })); + }; + + const studentYears: StudentYear[] = [ + { grade: "First-Year", studentSemesters: buildSemesters(firstYearTerms) }, + { grade: "Sophomore", studentSemesters: buildSemesters(sophomoreTerms) }, + { grade: "Junior", studentSemesters: buildSemesters(juniorTerms) }, + { grade: "Senior", studentSemesters: buildSemesters(seniorTerms) }, + ]; return studentYears; } - diff --git a/frontend/src/app/courses/page.tsx b/frontend/src/app/courses/page.tsx index 07e44a5..c237f74 100644 --- a/frontend/src/app/courses/page.tsx +++ b/frontend/src/app/courses/page.tsx @@ -19,12 +19,12 @@ function Courses(){ const [columns, setColumns] = useState(true); const toggleColumns = () => { setColumns(!columns); } - const [studentYears, setStudentYears] = useState(() => BuildStudentYears(user.FYP.studentCourses)); + const [studentYears, setStudentYears] = useState(() => BuildStudentYears(user)); const [renderedYears, setRenderedYears] = useState([]); useEffect(() => { - setStudentYears(BuildStudentYears(user.FYP.studentCourses)); - }, [user.FYP.studentCourses]); + setStudentYears(BuildStudentYears(user)); + }, [user]); useEffect(() => { const newRenderedYears = studentYears.map((studentYear: StudentYear, index: number) => ( diff --git a/frontend/src/app/courses/years/YearBox.tsx b/frontend/src/app/courses/years/YearBox.tsx index 96e1372..6a0d9a9 100644 --- a/frontend/src/app/courses/years/YearBox.tsx +++ b/frontend/src/app/courses/years/YearBox.tsx @@ -36,7 +36,7 @@ function YearBox(props: { edit: boolean, columns: boolean, studentYear: StudentY
{renderedSemesters} - {(props.edit && props.studentYear.active) && } + {(props.edit && (props.studentYear.studentSemesters.length < 3)) && }
); diff --git a/frontend/src/app/courses/years/semester/SemesterBox.tsx b/frontend/src/app/courses/years/semester/SemesterBox.tsx index e0af037..b8be76a 100644 --- a/frontend/src/app/courses/years/semester/SemesterBox.tsx +++ b/frontend/src/app/courses/years/semester/SemesterBox.tsx @@ -38,7 +38,7 @@ function SemesterBox(props: { edit: boolean, studentSemester: StudentSemester, u
{renderedCourses} - {(props.edit && IsTermActive(props.studentSemester.term)) && } + {props.edit && }
); diff --git a/frontend/src/app/courses/years/semester/add-course/AddCourseButton.module.css b/frontend/src/app/courses/years/semester/add-course/AddCourseButton.module.css index 46a67f9..86542a7 100644 --- a/frontend/src/app/courses/years/semester/add-course/AddCourseButton.module.css +++ b/frontend/src/app/courses/years/semester/add-course/AddCourseButton.module.css @@ -59,9 +59,29 @@ border-color: #aaa; } +.ResultBox { + border: 1px solid #ccc; + padding: 0 8px; + background-color: white; + cursor: pointer; + border-radius: 4px; + font-size: 12px; + height: 22px; + width: 60px; + display: flex; + align-items: center; + box-sizing: border-box; + color: black; + position: relative; + transition: border-color 0.3s ease; +} +.ResultBox:hover { + border-color: #aaa; +} + /* */ -.TermOptions { +.DropdownOptions { position: absolute; background-color: #fff; border: 1px solid #ccc; @@ -78,18 +98,18 @@ box-sizing: border-box; } -.TermOptions div { +.DropdownOptions div { padding: 8px; cursor: pointer; color: black; background-color: white; } -.TermOptions div:hover { +.DropdownOptions div:hover { background-color: #f0f0f0; } -.SelectedTerm { +.SelectedDropdownOption { background-color: #93c7ff !important; /* Light blue background for the selected term */ color: black; /* Optional: change text color for selected term */ } diff --git a/frontend/src/app/courses/years/semester/add-course/AddCourseButton.tsx b/frontend/src/app/courses/years/semester/add-course/AddCourseButton.tsx index 567ab5c..bffdeaf 100644 --- a/frontend/src/app/courses/years/semester/add-course/AddCourseButton.tsx +++ b/frontend/src/app/courses/years/semester/add-course/AddCourseButton.tsx @@ -3,19 +3,19 @@ import { useRef, useState, useEffect } from "react"; import Style from "./AddCourseButton.module.css"; import { User, StudentCourse } from "@/types/type-user"; -import { getCatalogCourse } from "@/database/data-catalog"; +import { getCatalogCourse, getCatalogTerms } from "@/database/data-catalog"; interface AddCourseDisplay { active: boolean; - dropVis: boolean; + termDropVis: boolean; + resultDropVis: boolean; } -const terms = [202203, 202301]; - function executeAddCourse( props: { term: number; user: User; setUser: Function }, inputRef: React.RefObject, selectedTerm: number, + selectedResult: string, setAddDisplay: Function ){ if(inputRef.current){ @@ -24,16 +24,11 @@ function executeAddCourse( if(targetCourse){ const status = selectedTerm === props.term ? "DA" : "MA"; - const newCourse: StudentCourse = { course: targetCourse, status, term: props.term }; + const newCourse: StudentCourse = { course: targetCourse, status, term: props.term, result: selectedResult }; - const updatedSemesters = props.user.FYP.studentSemesters.map((semester) => { - if (semester.season === selectedTerm) { - return { ...semester, studentCourses: [...semester.studentCourses, newCourse] }; - } - return semester; - }); + const updatedCourses = [...props.user.FYP.studentCourses, newCourse]; - props.setUser({ ...props.user, FYP: { ...props.user.FYP, studentSemesters: updatedSemesters } }); + props.setUser({ ...props.user, FYP: { ...props.user.FYP, studentCourses: updatedCourses } }); setAddDisplay((prevState: AddCourseDisplay) => ({ ...prevState, active: false })); } } @@ -44,8 +39,12 @@ function AddCourseButton(props: { term: number; user: User; setUser: Function }) const inputRef = useRef(null); const addRef = useRef(null); - const [addDisplay, setAddDisplay] = useState({ active: false, dropVis: false }); - const [selectedTerm, setSelectedTerm] = useState(props.term); + const [addDisplay, setAddDisplay] = useState({ active: false, termDropVis: false, resultDropVis: false }); + const [selectedTerm, setSelectedTerm] = useState(props.term); + const [selectedResult, setSelectedResult] = useState(""); + const resultOptions = ["GRADE", "CR", "D/F"]; + + const catalogTerms = getCatalogTerms() useEffect(() => { if(addDisplay.active){ @@ -54,30 +53,30 @@ function AddCourseButton(props: { term: number; user: User; setUser: Function }) } return () => { - if(addDisplay.active){ - document.removeEventListener("mousedown", handleClickOutside); - } + if(addDisplay.active){ + document.removeEventListener("mousedown", handleClickOutside); + } }; }, [addDisplay]); - const handleClickOutside = (event: MouseEvent) => { + const handleClickOutside = (event: MouseEvent) => { if(addRef.current && !addRef.current.contains(event.target as Node)){ - if(addDisplay.dropVis){ - setAddDisplay((prevState) => ({...prevState, dropVis: false})); - setTimeout(() => { - if(inputRef.current){ - inputRef.current.focus(); - } - }, 0); + if(addDisplay.termDropVis || addDisplay.resultDropVis){ + setAddDisplay((prevState) => ({...prevState, termDropVis: false, resultDropVis: false})); + setTimeout(() => { + if(inputRef.current){ + inputRef.current.focus(); + } + }, 0); }else{ - setAddDisplay((prevState) => ({...prevState, active: false})); + setAddDisplay((prevState) => ({...prevState, active: false})); } } }; const handleKeyPress = (event: React.KeyboardEvent) => { if(event.key === "Enter"){ - executeAddCourse(props, inputRef, selectedTerm, setAddDisplay); + executeAddCourse(props, inputRef, selectedTerm, selectedResult, setAddDisplay); } }; @@ -91,26 +90,35 @@ function AddCourseButton(props: { term: number; user: User; setUser: Function })
setAddDisplay((prevState) => ({...prevState, active: false}))}> - -
-
setAddDisplay((prevState) => ({...prevState, dropVis: !addDisplay.dropVis}))}> +
+
setAddDisplay((prevState) => ({...prevState, termDropVis: !prevState.termDropVis}))}> {selectedTerm} - {addDisplay.dropVis && ( -
- {terms.map((term, index) => ( -
setSelectedTerm(term)} className={term === selectedTerm ? Style.SelectedTerm : ""}> - {term} -
- ))} -
- )} -
- - - -
executeAddCourse(props, inputRef, selectedTerm, setAddDisplay)}> - -
+ {addDisplay.termDropVis && ( +
+ {catalogTerms.map((term, index) => ( +
setSelectedTerm(term)} className={term === selectedTerm ? Style.SelectedDropdownOption : ""}> + {term} +
+ ))} +
+ )} +
+ + +
setAddDisplay((prevState) => ({...prevState, resultDropVis: !prevState.resultDropVis}))}> + {selectedResult || ""} + {addDisplay.resultDropVis && ( +
+ {resultOptions.map((result, index) => ( +
setSelectedResult(result)} className={result === selectedResult ? Style.SelectedDropdownOption : ""}> + {result} +
+ ))} +
+ )} +
+
executeAddCourse(props, inputRef, selectedTerm, selectedResult, setAddDisplay)}> +
)} diff --git a/frontend/src/app/providers.tsx b/frontend/src/app/providers.tsx index 9cd4599..020a3cb 100644 --- a/frontend/src/app/providers.tsx +++ b/frontend/src/app/providers.tsx @@ -1,15 +1,20 @@ "use client"; -import { createContext, useContext, useState } from "react"; +import { createContext, useContext, useState, useEffect } from "react"; import { User } from "@/types/type-user"; -import { NullUser } from "@/database/data-user"; +import { NullUser, Ryan } from "@/database/data-user"; const AuthContext = createContext(null); export function AuthProvider({ children }: { children: React.ReactNode }) { const [auth, setAuth] = useState({ loggedIn: false }); - const [user, setUser] = useState(NullUser); + const [user, setUser] = useState(Ryan); + + // uh this isnt right + useEffect(() => { + setUser(Ryan); + }, []); return( diff --git a/frontend/src/database/data-catalog.ts b/frontend/src/database/data-catalog.ts index 212e244..336d1ba 100644 --- a/frontend/src/database/data-catalog.ts +++ b/frontend/src/database/data-catalog.ts @@ -9,6 +9,12 @@ interface Catalog { export const Catalogs: Catalog[] = [ { number: 202203, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, { number: 202301, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, + { number: 202302, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, + { number: 202303, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, + { number: 202401, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, + { number: 202402, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, + { number: 202403, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, + { number: 202501, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, ] export const getCatalogCourse = (catalogNumber: number, courseCode: string): Course | null => { @@ -19,3 +25,7 @@ export const getCatalogCourse = (catalogNumber: number, courseCode: string): Cou const course = catalog.courses.find((course) => course.codes.includes(courseCode)); return course || null; }; + +export const getCatalogTerms = (): number[] => { + return Catalogs.map((catalog) => catalog.number).sort((a, b) => a - b); +} diff --git a/frontend/src/database/data-cpsc.ts b/frontend/src/database/data-cpsc.ts new file mode 100644 index 0000000..06dadb4 --- /dev/null +++ b/frontend/src/database/data-cpsc.ts @@ -0,0 +1,86 @@ + + +import { DegreeConfiguration, TypeOneRequirement, TypeOneSubrequirement } from "@/types/type-program"; + + +const CPSC_201: TypeOneSubrequirement = { + requirement_name: "INTRODUCTION", + requirement_description: "", + must_choose_n_courses: 1, + course_options: [{ codes: ["CPSC 201"], title: "Introduction To Computer Science", credit: 1, dist: ["QR"], seasons: [] }], + any: true, +} + +const CPSC_202: TypeOneSubrequirement = { + requirement_name: "MATH", + requirement_description: "", + must_choose_n_courses: 1, + course_options: [ + { codes: ["CPSC 202"], title: "Math Tools For Computer Scientists", credit: 1, dist: ["QR"], seasons: [] }, + { codes: ["MATH 244"], title: "Discrete Mathematics", credit: 1, dist: ["QR"], seasons: [] }, + ], + any: true, +} + +const CPSC_223: TypeOneSubrequirement = { + requirement_name: "SYSTEMS", + requirement_description: "", + must_choose_n_courses: 1, + course_options: [{ codes: ["CPSC 223"], title: "Data Structures", credit: 1, dist: ["QR"], seasons: [] }], +} + +const CPSC_323: TypeOneSubrequirement = { + requirement_name: "SYSTEMS", + requirement_description: "", + must_choose_n_courses: 1, + course_options: [{ codes: ["CPSC 323"], title: "Systems", credit: 1, dist: ["QR"], seasons: [] }], +} + +const CPSC_365: TypeOneSubrequirement = { + requirement_name: "ALGORITHMS", + requirement_description: "", + must_choose_n_courses: 1, + course_options: [ + { codes: ["CPSC 365"], title: "Algorithms", credit: 1, dist: ["QR"], seasons: [] }, + { codes: ["CPSC 366"], title: "Intensive Algorithms", credit: 1, dist: ["QR"], seasons: [] }, + ], +} + +const CPSC_CORE: TypeOneRequirement = { + requirement_name: "CORE", + requirement_description: "", + must_choose_n_subrequirements: 5, + subrequirements: [CPSC_201, CPSC_202, CPSC_223, CPSC_323, CPSC_365] +} + +const CPSC_ELEC: TypeOneSubrequirement = { + requirement_name: "", + requirement_description: "", + must_choose_n_courses: 1, + elective_range: { department: "CPSC", min_code: 300, max_code: 999 } +} + +const CPSC_ELECTIVE: TypeOneRequirement = { + requirement_name: "ELECTIVES", + requirement_description: "", + must_choose_n_subrequirements: 4, + subrequirements: [CPSC_ELEC, CPSC_ELEC, CPSC_ELEC, CPSC_ELEC] +} + +const CPSC_SENIOR_CLASS: TypeOneSubrequirement = { + requirement_name: "", + requirement_description: "", + must_choose_n_courses: 1, + course_options: [{ codes: ["CPSC 323"], title: "Systems", credit: 1, dist: ["QR"], seasons: [] }], +} + +const CPSC_SENIOR: TypeOneRequirement = { + requirement_name: "SENIOR", + requirement_description: "", + must_choose_n_subrequirements: 1, + subrequirements: [CPSC_SENIOR_CLASS] +} + +export const CPSC_CONFIG: DegreeConfiguration = { + degreeRequirements: [CPSC_CORE, CPSC_ELECTIVE, CPSC_SENIOR] +} \ No newline at end of file diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index bbf09d1..0a02b75 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -1,5 +1,6 @@ import { User } from "./../types/type-user"; +import { CPSC_CONFIG } from "./data-cpsc"; export const Ryan: User = { name: "Ryan", @@ -7,21 +8,26 @@ export const Ryan: User = { onboard: false, FYP: { studentCourses: [ - { term: 202403, status: "DA", course: { codes: ["CPSC 201"], title: "Intro To Computer Science", credit: 1, dist: ["QR"], seasons: [] } }, - { term: 202501, status: "DA", course: { codes: ["MATH 244"], title: "Discrete Mathematics", credit: 1, dist: ["QR"], seasons: [] } }, - { term: 202503, status: "MA", course: { codes: ["CPSC 223"], title: "Data Structures", credit: 1, dist: ["QR"], seasons: [] } }, + { term: 202403, status: "DA", result: "GRADE_PASS", course: { codes: ["CPSC 201"], title: "Intro To Computer Science", credit: 1, dist: ["QR"], seasons: [] } }, + { term: 202501, status: "DA", result: "GRADE_PASS", course: { codes: ["MATH 244"], title: "Discrete Mathematics", credit: 1, dist: ["QR"], seasons: [] } }, + { term: 202503, status: "MA", result: "IP", course: { codes: ["CPSC 223"], title: "Data Structures", credit: 1, dist: ["QR"], seasons: [] } }, ], - languageRequirement: "", + studentTermArrangement: { + first_year: [0, 202403, 202501], + sophomore: [0, 202503, 202601], + junior: [0, 202603, 202701], + senior: [0, 202703, 202801], + }, + languagePlacement: { + language: "Spanish", + level: 5, + }, degreeDeclarations: [], degreeConfigurations: [ - [ - ], - [ - ], - [ - ], - [ - ] + [CPSC_CONFIG], + [], + [], + [] ], } } @@ -32,7 +38,16 @@ export const NullUser: User = { onboard: false, FYP: { studentCourses: [], - languageRequirement: "", + studentTermArrangement: { + first_year: [], + sophomore: [], + junior: [], + senior: [], + }, + languagePlacement: { + language: "", + level: 0, + }, degreeDeclarations: [], degreeConfigurations: [ [ @@ -46,3 +61,6 @@ export const NullUser: User = { ], } } + + + diff --git a/frontend/src/types/type-program.ts b/frontend/src/types/type-program.ts index 4aae385..f07363d 100644 --- a/frontend/src/types/type-program.ts +++ b/frontend/src/types/type-program.ts @@ -1,5 +1,5 @@ -import { StudentCourse } from "./type-user"; +import { Course, StudentCourse } from "./type-user"; interface DUS { name: string; @@ -34,21 +34,39 @@ export interface DegreeMetadata { // \BEGIN{MAJOR MAGIC} -interface DegreeRequirementsSubsection { - name?: string; - description?: string; - flexible: boolean; - courses: StudentCourse[]; +export interface Range { + department: string; + min_code: number; + max_code: number; } -interface DegreeRequirement { - name: string; - description?: string; - subsections: DegreeRequirementsSubsection[]; +export interface TypeOneSubrequirement { + requirement_name: string; + requirement_description: string; + must_choose_n_courses: number; + course_options?: Course[]; + elective_range?: Range; + any?: boolean; +} + +export interface TypeOneRequirement { + requirement_name: string; + requirement_description: string; + must_choose_n_subrequirements: number; + subrequirements: TypeOneSubrequirement[]; } +export interface TypeTwoRequirement { + requirement_name: string; + requirement_description: string; + checkbox_boolean: boolean; +} + + +export type DegreeRequirement = TypeOneRequirement | TypeTwoRequirement; + export interface DegreeConfiguration { - degreeRequirements: DegreeRequirement[]; + degreeRequirements: DegreeRequirement[]; } export interface Degree { diff --git a/frontend/src/types/type-user.ts b/frontend/src/types/type-user.ts index c558864..ce21d91 100644 --- a/frontend/src/types/type-user.ts +++ b/frontend/src/types/type-user.ts @@ -1,6 +1,11 @@ import { DegreeConfiguration, StudentDegree } from "./type-program"; +export interface LanguagePlacement { + language: string; + level: number; +} + export interface Course { codes: string[]; // ["FREN 403", "HUMS 409"] title: string; // "Proust Interpretations: Reading Remembrance of Things Past" @@ -13,23 +18,30 @@ export interface StudentCourse { course: Course; term: number; // 202401 status: string; // "DA" or "MA" + result: string; // "IP" or "GRADE_PASS" or "GRADE_FAIL" or "CR" or "W" } export interface StudentSemester { term: number; - active: boolean; studentCourses: StudentCourse[]; } export interface StudentYear { grade: string; // "First-Year" | "Sophomore" | "Junior" | "Senior" - active: boolean; studentSemesters: StudentSemester[]; } +export interface StudentTermArrangement { + first_year: number[]; + sophomore: number[]; + junior: number[]; + senior: number[]; +} + export interface FYP { - languageRequirement: string; + languagePlacement: LanguagePlacement; studentCourses: StudentCourse[]; + studentTermArrangement: StudentTermArrangement; degreeConfigurations: DegreeConfiguration[][]; degreeDeclarations: StudentDegree[]; } From e4d8e13b710411f75a8d059486f41aa0de650caf Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Sun, 23 Feb 2025 23:36:39 -0500 Subject: [PATCH 11/38] reqs r back kinda --- frontend/src/app/courses/years/YearBox.tsx | 18 ++--- .../courses/years/semester/SemesterBox.tsx | 2 +- frontend/src/app/majors/Majors.module.css | 6 +- .../app/majors/metadata/Metadata.module.css | 5 ++ frontend/src/app/majors/metadata/Metadata.tsx | 2 +- .../requirements/Requirements.module.css | 33 ++++++---- .../app/majors/requirements/Requirements.tsx | 53 ++++++++++++--- .../src/components/course-icon/CourseIcon.tsx | 66 +++++++++---------- frontend/src/database/data-cpsc.ts | 25 +++---- frontend/src/types/type-program.ts | 26 +++----- frontend/src/utils/CourseDisplay.tsx | 6 +- 11 files changed, 144 insertions(+), 98 deletions(-) diff --git a/frontend/src/app/courses/years/YearBox.tsx b/frontend/src/app/courses/years/YearBox.tsx index 6a0d9a9..bc166bf 100644 --- a/frontend/src/app/courses/years/YearBox.tsx +++ b/frontend/src/app/courses/years/YearBox.tsx @@ -8,15 +8,17 @@ import AddSemesterButton from "./add-semester/AddSemesterButton" function RenderSemesters(props: { edit: boolean; columns: boolean; studentYear: StudentYear, setStudentYears: Function, user: User, setUser: Function }) { - const newRenderedSemesters = props.studentYear.studentSemesters.map((studentSemester: StudentSemester) => ( - - )); + const newRenderedSemesters = props.studentYear.studentSemesters + .filter((studentSemester: StudentSemester) => studentSemester.term !== 0) + .map((studentSemester: StudentSemester) => ( + + )); - return( -
- {newRenderedSemesters} -
- ); + return( +
+ {newRenderedSemesters} +
+ ); } function YearBox(props: { edit: boolean, columns: boolean, studentYear: StudentYear, setStudentYears: Function, user: User, setUser: Function }) diff --git a/frontend/src/app/courses/years/semester/SemesterBox.tsx b/frontend/src/app/courses/years/semester/SemesterBox.tsx index b8be76a..5cc3625 100644 --- a/frontend/src/app/courses/years/semester/SemesterBox.tsx +++ b/frontend/src/app/courses/years/semester/SemesterBox.tsx @@ -32,7 +32,7 @@ function SemesterBox(props: { edit: boolean, studentSemester: StudentSemester, u }, [props.edit, props.studentSemester, props.user]); return( -
+
{TransformTermNumber(props.studentSemester.term)}
diff --git a/frontend/src/app/majors/Majors.module.css b/frontend/src/app/majors/Majors.module.css index df741d6..22f3d96 100644 --- a/frontend/src/app/majors/Majors.module.css +++ b/frontend/src/app/majors/Majors.module.css @@ -1,11 +1,13 @@ .MajorsPage { position: absolute; + top: 75px; display: flex; flex-direction: row; + justify-content: center; - padding: 20px calc(50% - 500px); - margin-top: 100px; + width: 100%; + padding-top: 50px; } diff --git a/frontend/src/app/majors/metadata/Metadata.module.css b/frontend/src/app/majors/metadata/Metadata.module.css index adf4b8b..263f80f 100644 --- a/frontend/src/app/majors/metadata/Metadata.module.css +++ b/frontend/src/app/majors/metadata/Metadata.module.css @@ -1,4 +1,9 @@ + +.MetadataContainer{ + width: 600px; +} + .countBox { display: inline-block; /* Display as inline block */ width: max-content; /* Set width to maximum content size */ diff --git a/frontend/src/app/majors/metadata/Metadata.tsx b/frontend/src/app/majors/metadata/Metadata.tsx index e5d4fc4..5471e7f 100644 --- a/frontend/src/app/majors/metadata/Metadata.tsx +++ b/frontend/src/app/majors/metadata/Metadata.tsx @@ -152,7 +152,7 @@ function Metadata(props: { peekProgram: Function }) { return ( -
+
diff --git a/frontend/src/app/majors/requirements/Requirements.module.css b/frontend/src/app/majors/requirements/Requirements.module.css index 8d4817d..fe338cc 100644 --- a/frontend/src/app/majors/requirements/Requirements.module.css +++ b/frontend/src/app/majors/requirements/Requirements.module.css @@ -1,10 +1,25 @@ -.row{ +.Column { + display: flex; + flex-direction: column; +} + +.Row{ display: flex; flex-direction: row; } -.reqsList { +.RequirementsContainer { + padding: 20px; + border-radius: 25px; + width: 500px; + height: 500px; + min-width: 390px; + background-color: white; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); +} + +.ReqsList { height: 430px; scrollbar-color: rgb(131, 131, 131) transparent; scrollbar-width: thin; @@ -13,20 +28,16 @@ overflow-x: hidden; } -.subsectionHeader { +.ReqHeader { color: grey; font-size: 18px; font-weight: 501; } -.reqsContainer { - padding: 20px; - border-radius: 25px; - width: 490px; - height: 490px; - min-width: 390px; - background-color: white; - box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); +.SubHeader { + color: grey; + font-size: 14px; + font-weight: 500; } diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index a8aa215..e51e1ee 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -3,21 +3,54 @@ import { useState } from "react"; import Style from "./Requirements.module.css"; import { User } from "@/types/type-user"; -import { DegreeConfiguration } from "@/types/type-program"; +import { DegreeConfiguration, TypeOneRequirement, TypeOneSubrequirement } from "@/types/type-program"; +import { CourseIcon } from "@/components/course-icon/CourseIcon"; + + + // import AddableCourse from "./icons/AddableCourse"; // import RemovableCourse from "./icons/RemovableCourse"; // import { addCourseToSubsection, removeCourseFromSubsection, resetDegree } from "./RequirementsUtils"; -function RequirementsContent(props: { - edit: boolean, - degreeConfiguration: DegreeConfiguration, - user: User, - setUser: Function -}){ +function RenderSubrequirement(props: { subrequirement: TypeOneSubrequirement }){ + return( +
+
+ {props.subrequirement.requirement_name} +
+
+ {props.subrequirement.courses.map((course, index) => ( +
+ +
+ ))} +
+
+ ) +} + +function RenderRequirement(props: { requirement: TypeOneRequirement }){ + return( +
+
+ {props.requirement.requirement_name} +
+
+ {props.requirement.subrequirements.map((subrequirement, index) => ( + + ))} +
+
+ ) +} + +function RequirementsContent(props: { edit: boolean, degreeConfiguration: DegreeConfiguration, user: User, setUser: Function }){ return( -
- +
+ {props.degreeConfiguration.degreeRequirements.map((requirement, index) => ( + + ))}
); } @@ -34,7 +67,7 @@ function Requirements(props: { }; return( -
+
Requirements diff --git a/frontend/src/components/course-icon/CourseIcon.tsx b/frontend/src/components/course-icon/CourseIcon.tsx index cdaf3f3..e08a68a 100644 --- a/frontend/src/components/course-icon/CourseIcon.tsx +++ b/frontend/src/components/course-icon/CourseIcon.tsx @@ -1,18 +1,21 @@ import React from "react"; import styles from "./CourseIcon.module.css"; -import "react-tooltip/dist/react-tooltip.css"; -import { StudentCourse } from "@/types/type-user"; -import fall from "./fall.svg"; +import { StudentCourse, Course } from "@/types/type-user"; +import { RenderMark, GetCourseColor } from "@/utils/CourseDisplay"; + import DistributionCircle from "../distribution-circle/DistributionsCircle"; + + // import { useModal } from "../../../hooks/modalContext"; +// import "react-tooltip/dist/react-tooltip.css"; function CourseSeasonIcon(props: { seasons: Array }) { const seasonImageMap: { [key: string]: string } = { - "Fall": fall, - "Spring": fall, + "Fall": "./fall.svg", + "Spring": "./spring.svg", }; return ( @@ -32,12 +35,17 @@ function CourseSeasonIcon(props: { seasons: Array }) { ); } -function DistCircDiv(props: { dist: Array }) { - if (!Array.isArray(props.dist) || props.dist.length === 0) { - return
; +function DistCircDiv(props: { dist: string[] }) +{ + if(!Array.isArray(props.dist) || props.dist.length === 0){ + return( +
+ +
+ ); } - return ( + return(
@@ -47,39 +55,29 @@ function DistCircDiv(props: { dist: Array }) { export function StudentCourseIcon(props: { studentCourse: StudentCourse, utilityButton?: React.ReactNode }) { - const mark = (status: string) => { - let mark = ""; - switch (status) { - case "DA_COMPLETE": - case "DA_PROSPECT": - mark = "✓"; - break; - case "MA_HYPOTHETICAL": - mark = "⚠"; - break; - case "MA_VALID": - mark = "☑"; - break; - default: - return
; - } - return
{mark}
; - }; - const dist = props.studentCourse.course.dist || []; return ( -
+
{props.utilityButton && props.utilityButton} - {props.studentCourse.status === "NA" + {props.studentCourse.status === "" ? - : mark(props.studentCourse.status) + : } {props.studentCourse.course.codes[0]}
); } + + +export function CourseIcon(props: { course: Course }){ + + return( +
+ + {props.course.codes[0]} + +
+ ); +} diff --git a/frontend/src/database/data-cpsc.ts b/frontend/src/database/data-cpsc.ts index 06dadb4..0abb444 100644 --- a/frontend/src/database/data-cpsc.ts +++ b/frontend/src/database/data-cpsc.ts @@ -4,10 +4,10 @@ import { DegreeConfiguration, TypeOneRequirement, TypeOneSubrequirement } from " const CPSC_201: TypeOneSubrequirement = { - requirement_name: "INTRODUCTION", + requirement_name: "INTRO", requirement_description: "", must_choose_n_courses: 1, - course_options: [{ codes: ["CPSC 201"], title: "Introduction To Computer Science", credit: 1, dist: ["QR"], seasons: [] }], + courses: [{ codes: ["CPSC 201"], title: "Introduction To Computer Science", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }], any: true, } @@ -15,34 +15,34 @@ const CPSC_202: TypeOneSubrequirement = { requirement_name: "MATH", requirement_description: "", must_choose_n_courses: 1, - course_options: [ - { codes: ["CPSC 202"], title: "Math Tools For Computer Scientists", credit: 1, dist: ["QR"], seasons: [] }, - { codes: ["MATH 244"], title: "Discrete Mathematics", credit: 1, dist: ["QR"], seasons: [] }, + courses: [ + { codes: ["CPSC 202"], title: "Math Tools For Computer Scientists", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }, + { codes: ["MATH 244"], title: "Discrete Mathematics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }, ], any: true, } const CPSC_223: TypeOneSubrequirement = { - requirement_name: "SYSTEMS", + requirement_name: "DATA STRUCTURES", requirement_description: "", must_choose_n_courses: 1, - course_options: [{ codes: ["CPSC 223"], title: "Data Structures", credit: 1, dist: ["QR"], seasons: [] }], + courses: [{ codes: ["CPSC 223"], title: "Data Structures", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }], } const CPSC_323: TypeOneSubrequirement = { requirement_name: "SYSTEMS", requirement_description: "", must_choose_n_courses: 1, - course_options: [{ codes: ["CPSC 323"], title: "Systems", credit: 1, dist: ["QR"], seasons: [] }], + courses: [{ codes: ["CPSC 323"], title: "Systems", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }], } const CPSC_365: TypeOneSubrequirement = { requirement_name: "ALGORITHMS", requirement_description: "", must_choose_n_courses: 1, - course_options: [ - { codes: ["CPSC 365"], title: "Algorithms", credit: 1, dist: ["QR"], seasons: [] }, - { codes: ["CPSC 366"], title: "Intensive Algorithms", credit: 1, dist: ["QR"], seasons: [] }, + courses: [ + { codes: ["CPSC 365"], title: "Algorithms", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }, + { codes: ["CPSC 366"], title: "Intensive Algorithms", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }, ], } @@ -57,6 +57,7 @@ const CPSC_ELEC: TypeOneSubrequirement = { requirement_name: "", requirement_description: "", must_choose_n_courses: 1, + courses: [], elective_range: { department: "CPSC", min_code: 300, max_code: 999 } } @@ -71,7 +72,7 @@ const CPSC_SENIOR_CLASS: TypeOneSubrequirement = { requirement_name: "", requirement_description: "", must_choose_n_courses: 1, - course_options: [{ codes: ["CPSC 323"], title: "Systems", credit: 1, dist: ["QR"], seasons: [] }], + courses: [{ codes: ["CPSC 490"], title: "Systems", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }], } const CPSC_SENIOR: TypeOneRequirement = { diff --git a/frontend/src/types/type-program.ts b/frontend/src/types/type-program.ts index f07363d..3ec84f0 100644 --- a/frontend/src/types/type-program.ts +++ b/frontend/src/types/type-program.ts @@ -1,5 +1,5 @@ -import { Course, StudentCourse } from "./type-user"; +import { Course } from "./type-user"; interface DUS { name: string; @@ -44,7 +44,7 @@ export interface TypeOneSubrequirement { requirement_name: string; requirement_description: string; must_choose_n_courses: number; - course_options?: Course[]; + courses: Course[]; elective_range?: Range; any?: boolean; } @@ -56,17 +56,17 @@ export interface TypeOneRequirement { subrequirements: TypeOneSubrequirement[]; } -export interface TypeTwoRequirement { - requirement_name: string; - requirement_description: string; - checkbox_boolean: boolean; -} +// export interface TypeTwoRequirement { +// requirement_name: string; +// requirement_description: string; +// checkbox_boolean: boolean; +// } -export type DegreeRequirement = TypeOneRequirement | TypeTwoRequirement; +// export type DegreeRequirement = TypeOneRequirement | TypeTwoRequirement; export interface DegreeConfiguration { - degreeRequirements: DegreeRequirement[]; + degreeRequirements: TypeOneRequirement[]; } export interface Degree { @@ -76,14 +76,8 @@ export interface Degree { // \END{MAJOR MAGIC} - - - - - - export interface StudentDegree { - status: string; // DA | ADD | PIN + status: string; // DA | ADD | PIN programIndex: number; degreeIndex: number; } diff --git a/frontend/src/utils/CourseDisplay.tsx b/frontend/src/utils/CourseDisplay.tsx index dcfaeed..b09f91d 100644 --- a/frontend/src/utils/CourseDisplay.tsx +++ b/frontend/src/utils/CourseDisplay.tsx @@ -53,16 +53,16 @@ export function GetCourseColor(term: number): string { return IsTermActive(term) ? "#F5F5F5" : "#E1E9F8"; } -export function RenderMark(props: { edit: boolean, studentCourse: StudentCourse, user: User, setUser: Function }) +export function RenderMark(props: { status: string }) { - if(props.studentCourse.status === "DA"){ + if(props.status === "DA"){ return(
); }else - if(props.studentCourse.status === "MA"){ + if(props.status === "MA"){ return(
⚠ From 2f8a8ecfea8cf4ecc317d3939101e541d7bffb21 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Sat, 8 Mar 2025 08:30:29 -0800 Subject: [PATCH 12/38] data stuff --- .../app/majors/requirements/Requirements.tsx | 57 ++++--- .../src/components/course-icon/CourseIcon.tsx | 13 +- frontend/src/database/data-catalog.ts | 28 +++- frontend/src/database/data-cpsc.ts | 155 +++++++++++------- frontend/src/types/type-program.ts | 71 ++++---- 5 files changed, 195 insertions(+), 129 deletions(-) diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index e51e1ee..0982d21 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -2,64 +2,77 @@ import { useState } from "react"; import Style from "./Requirements.module.css"; -import { User } from "@/types/type-user"; -import { DegreeConfiguration, TypeOneRequirement, TypeOneSubrequirement } from "@/types/type-program"; +import { User, Course } from "@/types/type-user"; +import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; import { CourseIcon } from "@/components/course-icon/CourseIcon"; +function RenderSubrequirementCourse(props: { course: Course, subreq: DegreeSubrequirement; user: User }){ -// import AddableCourse from "./icons/AddableCourse"; -// import RemovableCourse from "./icons/RemovableCourse"; -// import { addCourseToSubsection, removeCourseFromSubsection, resetDegree } from "./RequirementsUtils"; -function RenderSubrequirement(props: { subrequirement: TypeOneSubrequirement }){ + return( +
+ +
+ ) +} + +function RenderSubrequirement(props: { subreq: DegreeSubrequirement, user: User }) +{ return(
-
- {props.subrequirement.requirement_name} + +
+
+ {props.subreq.subreq_name} +
+
+ {props.subreq.user_courses_satisfying.length}/{props.subreq.courses_required} +
+
- {props.subrequirement.courses.map((course, index) => ( + {props.subreq.courses_options.map((course, index) => (
- +
))}
+
) } -function RenderRequirement(props: { requirement: TypeOneRequirement }){ +function RenderRequirement(props: { req: DegreeRequirement, user: User }) +{ return(
- {props.requirement.requirement_name} + {props.req.req_name}
- {props.requirement.subrequirements.map((subrequirement, index) => ( - + {props.req.subreqs_list.map((subreq, index) => ( + ))}
) } -function RequirementsContent(props: { edit: boolean, degreeConfiguration: DegreeConfiguration, user: User, setUser: Function }){ +function RequirementsContent(props: { edit: boolean, degreeConfiguration: DegreeConfiguration, user: User, setUser: Function }) +{ return(
- {props.degreeConfiguration.degreeRequirements.map((requirement, index) => ( - + {props.degreeConfiguration.reqs_list.map((req, index) => ( + ))}
); } -function Requirements(props: { - user: User, - setUser: Function, - degreeConfiguration: DegreeConfiguration -}){ +function Requirements(props: { user: User, setUser: Function, degreeConfiguration: DegreeConfiguration }) +{ const [edit, setEdit] = useState(false); const updateEdit = () => { diff --git a/frontend/src/components/course-icon/CourseIcon.tsx b/frontend/src/components/course-icon/CourseIcon.tsx index e08a68a..073a50b 100644 --- a/frontend/src/components/course-icon/CourseIcon.tsx +++ b/frontend/src/components/course-icon/CourseIcon.tsx @@ -8,10 +8,6 @@ import { RenderMark, GetCourseColor } from "@/utils/CourseDisplay"; import DistributionCircle from "../distribution-circle/DistributionsCircle"; - -// import { useModal } from "../../../hooks/modalContext"; -// import "react-tooltip/dist/react-tooltip.css"; - function CourseSeasonIcon(props: { seasons: Array }) { const seasonImageMap: { [key: string]: string } = { "Fall": "./fall.svg", @@ -35,6 +31,7 @@ function CourseSeasonIcon(props: { seasons: Array }) { ); } + function DistCircDiv(props: { dist: string[] }) { if(!Array.isArray(props.dist) || props.dist.length === 0){ @@ -71,8 +68,14 @@ export function StudentCourseIcon(props: { studentCourse: StudentCourse, utility } -export function CourseIcon(props: { course: Course }){ +export function CourseIcon(props: { course: Course, studentCourse?: StudentCourse }){ + if(props.studentCourse){ + return( + + ); + } + return(
diff --git a/frontend/src/database/data-catalog.ts b/frontend/src/database/data-catalog.ts index 336d1ba..957bd25 100644 --- a/frontend/src/database/data-catalog.ts +++ b/frontend/src/database/data-catalog.ts @@ -6,15 +6,27 @@ interface Catalog { courses: Course[]; } +export const CPSC_201: Course = { codes: ["CPSC 201"], title: "Introduction To Computer Science", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_202: Course = { codes: ["CPSC 202"], title: "Math Tools For Computer Scientists", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const MATH_244: Course = { codes: ["MATH 244"], title: "Discrete Mathematics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_223: Course = { codes: ["CPSC 223"], title: "Data Structures And Programming Techniques", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_323: Course = { codes: ["CPSC 323"], title: "Introduction To Systems Programming", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_365: Course = { codes: ["CPSC 365"], title: "Algorithms", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_366: Course = { codes: ["CPSC 366"], title: "Intensive Algorithms", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_490: Course = { codes: ["CPSC 490"], title: "Senior Project", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } + +export const HSAR_401: Course = { codes: ["HSAR 401"], title: "Critical Approaches To Art History", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } + + export const Catalogs: Catalog[] = [ - { number: 202203, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, - { number: 202301, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, - { number: 202302, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, - { number: 202303, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, - { number: 202401, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, - { number: 202402, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, - { number: 202403, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, - { number: 202501, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, + { number: 202203, courses: [HSAR_401] }, + { number: 202301, courses: [HSAR_401] }, + { number: 202302, courses: [HSAR_401] }, + { number: 202303, courses: [HSAR_401] }, + { number: 202401, courses: [HSAR_401] }, + { number: 202402, courses: [HSAR_401] }, + { number: 202403, courses: [HSAR_401] }, + { number: 202501, courses: [HSAR_401] }, ] export const getCatalogCourse = (catalogNumber: number, courseCode: string): Course | null => { diff --git a/frontend/src/database/data-cpsc.ts b/frontend/src/database/data-cpsc.ts index 0abb444..13f53a6 100644 --- a/frontend/src/database/data-cpsc.ts +++ b/frontend/src/database/data-cpsc.ts @@ -1,87 +1,120 @@ -import { DegreeConfiguration, TypeOneRequirement, TypeOneSubrequirement } from "@/types/type-program"; +import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; +import { CPSC_201, CPSC_202, MATH_244, CPSC_223, CPSC_323, CPSC_365, CPSC_366, CPSC_490 } from "./data-catalog"; +const CPSC_INTRO: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "INTRO", + subreq_desc: "", + courses_required: 1, + courses_options: [CPSC_201], + courses_elective_range: null, + courses_any_bool: false, + user_courses_satisfying: [CPSC_201], +} -const CPSC_201: TypeOneSubrequirement = { - requirement_name: "INTRO", - requirement_description: "", - must_choose_n_courses: 1, - courses: [{ codes: ["CPSC 201"], title: "Introduction To Computer Science", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }], - any: true, +const CPSC_MATH: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "DISCRETE MATH", + subreq_desc: "", + courses_required: 1, + courses_options: [CPSC_202, MATH_244], + courses_elective_range: null, + courses_any_bool: false, + user_courses_satisfying: [], } -const CPSC_202: TypeOneSubrequirement = { - requirement_name: "MATH", - requirement_description: "", - must_choose_n_courses: 1, - courses: [ - { codes: ["CPSC 202"], title: "Math Tools For Computer Scientists", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }, - { codes: ["MATH 244"], title: "Discrete Mathematics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }, - ], - any: true, +const CPSC_DATA: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "DATA STRUCTURES", + subreq_desc: "", + courses_required: 1, + courses_options: [CPSC_223], + courses_elective_range: null, + courses_any_bool: false, + user_courses_satisfying: [CPSC_223], } -const CPSC_223: TypeOneSubrequirement = { - requirement_name: "DATA STRUCTURES", - requirement_description: "", - must_choose_n_courses: 1, - courses: [{ codes: ["CPSC 223"], title: "Data Structures", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }], +const CPSC_SYSTEMS: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "SYSTEMS", + subreq_desc: "", + courses_required: 1, + courses_options: [CPSC_323], + courses_elective_range: null, + courses_any_bool: false, + user_courses_satisfying: [CPSC_323], } -const CPSC_323: TypeOneSubrequirement = { - requirement_name: "SYSTEMS", - requirement_description: "", - must_choose_n_courses: 1, - courses: [{ codes: ["CPSC 323"], title: "Systems", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }], +const CPSC_ALGOS: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "ALGORITHMS", + subreq_desc: "", + courses_required: 1, + courses_options: [CPSC_365, CPSC_366], + courses_elective_range: null, + courses_any_bool: false, + user_courses_satisfying: [], } -const CPSC_365: TypeOneSubrequirement = { - requirement_name: "ALGORITHMS", - requirement_description: "", - must_choose_n_courses: 1, - courses: [ - { codes: ["CPSC 365"], title: "Algorithms", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }, - { codes: ["CPSC 366"], title: "Intensive Algorithms", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }, - ], +const CPSC_CORE: DegreeRequirement = { + req_type_id: 1, + req_name: "CORE", + req_desc: "", + subreqs_required: 5, + subreqs_list: [CPSC_INTRO, CPSC_MATH, CPSC_DATA, CPSC_SYSTEMS, CPSC_ALGOS] } -const CPSC_CORE: TypeOneRequirement = { - requirement_name: "CORE", - requirement_description: "", - must_choose_n_subrequirements: 5, - subrequirements: [CPSC_201, CPSC_202, CPSC_223, CPSC_323, CPSC_365] +const CPSC_RANGE_ELECS: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "", + subreq_desc: "", + courses_required: 1, + courses_options: [], + courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, + courses_any_bool: false, + user_courses_satisfying: [] } -const CPSC_ELEC: TypeOneSubrequirement = { - requirement_name: "", - requirement_description: "", - must_choose_n_courses: 1, - courses: [], - elective_range: { department: "CPSC", min_code: 300, max_code: 999 } +const CPSC_SUB_ELEC: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "", + subreq_desc: "", + courses_required: 3, + courses_options: [], + courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, + courses_any_bool: true, + user_courses_satisfying: [] } -const CPSC_ELECTIVE: TypeOneRequirement = { - requirement_name: "ELECTIVES", - requirement_description: "", - must_choose_n_subrequirements: 4, - subrequirements: [CPSC_ELEC, CPSC_ELEC, CPSC_ELEC, CPSC_ELEC] +const CPSC_ELECTIVES: DegreeRequirement = { + req_type_id: 1, + req_name: "ELECTIVE", + req_desc: "", + subreqs_required: 4, + subreqs_list: [CPSC_RANGE_ELECS, CPSC_SUB_ELEC] } -const CPSC_SENIOR_CLASS: TypeOneSubrequirement = { - requirement_name: "", - requirement_description: "", - must_choose_n_courses: 1, - courses: [{ codes: ["CPSC 490"], title: "Systems", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] }], +const CPSC_SENPROJ: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "SENIOR PROJECT", + subreq_desc: "", + courses_required: 1, + courses_options: [CPSC_490], + courses_elective_range: null, + courses_any_bool: false, + user_courses_satisfying: [] } -const CPSC_SENIOR: TypeOneRequirement = { - requirement_name: "SENIOR", - requirement_description: "", - must_choose_n_subrequirements: 1, - subrequirements: [CPSC_SENIOR_CLASS] +const CPSC_SENIOR: DegreeRequirement = { + req_type_id: 1, + req_name: "SENIOR", + req_desc: "", + subreqs_required: 1, + subreqs_list: [CPSC_SENPROJ] } export const CPSC_CONFIG: DegreeConfiguration = { - degreeRequirements: [CPSC_CORE, CPSC_ELECTIVE, CPSC_SENIOR] + reqs_list: [CPSC_CORE, CPSC_ELECTIVES, CPSC_SENIOR] } \ No newline at end of file diff --git a/frontend/src/types/type-program.ts b/frontend/src/types/type-program.ts index 3ec84f0..95148d4 100644 --- a/frontend/src/types/type-program.ts +++ b/frontend/src/types/type-program.ts @@ -1,6 +1,12 @@ import { Course } from "./type-user"; +export interface StudentDegree { + status: string; // DA | ADD | PIN + programIndex: number; + degreeIndex: number; +} + interface DUS { name: string; address: string; @@ -26,47 +32,52 @@ export interface DegreeMetadata { wesbiteLink: string; } - - - - - - // \BEGIN{MAJOR MAGIC} -export interface Range { - department: string; +export interface ElectiveRange { + dept: string; min_code: number; max_code: number; } -export interface TypeOneSubrequirement { - requirement_name: string; - requirement_description: string; - must_choose_n_courses: number; - courses: Course[]; - elective_range?: Range; - any?: boolean; +export type SubreqElectiveRange = ElectiveRange| null; + +// subreq: 1 | set options +export interface DegreeSubrequirement { + subreq_type_id: number; + subreq_name: string; + subreq_desc: string; + + courses_required: number; + courses_options: Course[]; + courses_elective_range: SubreqElectiveRange; + courses_any_bool: boolean; + + user_courses_satisfying: Course[]; } -export interface TypeOneRequirement { - requirement_name: string; - requirement_description: string; - must_choose_n_subrequirements: number; - subrequirements: TypeOneSubrequirement[]; +export interface DegreeRequirement { + req_type_id: number; + req_name: string; + req_desc: string; + + subreqs_required: number; + subreqs_list: DegreeSubrequirement[]; } -// export interface TypeTwoRequirement { -// requirement_name: string; -// requirement_description: string; -// checkbox_boolean: boolean; -// } +// export interface DegreeRequirementTypeTwo { +// req_type_id: number; +// req_name: string; +// req_desc: string; +// checkbox_bool: boolean; +// user_courses_satisfying: Course[]; +// } -// export type DegreeRequirement = TypeOneRequirement | TypeTwoRequirement; +// export type DegreeRequirement = DegreeRequirementTypeOne | DegreeRequirementTypeTwo; export interface DegreeConfiguration { - degreeRequirements: TypeOneRequirement[]; + reqs_list: DegreeRequirement[]; } export interface Degree { @@ -75,9 +86,3 @@ export interface Degree { } // \END{MAJOR MAGIC} - -export interface StudentDegree { - status: string; // DA | ADD | PIN - programIndex: number; - degreeIndex: number; -} From dd58660aebd37599cc6926e97fa2e49e0e06f1f3 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Sat, 8 Mar 2025 09:38:13 -0800 Subject: [PATCH 13/38] requirements liberated --- frontend/src/app/majors/Majors.module.css | 19 +++++++++++-- .../app/majors/metadata/Metadata.module.css | 19 +++++++++++-- frontend/src/app/majors/page.tsx | 3 +- .../requirements/Requirements.module.css | 25 +++++++++++------ .../app/majors/requirements/Requirements.tsx | 28 +++++++++---------- 5 files changed, 66 insertions(+), 28 deletions(-) diff --git a/frontend/src/app/majors/Majors.module.css b/frontend/src/app/majors/Majors.module.css index 22f3d96..010a158 100644 --- a/frontend/src/app/majors/Majors.module.css +++ b/frontend/src/app/majors/Majors.module.css @@ -1,7 +1,9 @@ .MajorsPage { + /* border: 1px solid blue; */ + position: absolute; - top: 75px; + top: 75px; display: flex; flex-direction: row; @@ -9,5 +11,18 @@ justify-content: center; width: 100%; - padding-top: 50px; + height: calc(100vh - 75px); + + /* padding-top: 50px; */ +} + +.Divider { + border-right: 1px solid gray; + + position: absolute; + top: 50px; + bottom: 50px; + + width: 5px; + height: calc(100% - 100px); } diff --git a/frontend/src/app/majors/metadata/Metadata.module.css b/frontend/src/app/majors/metadata/Metadata.module.css index 263f80f..7b9aea5 100644 --- a/frontend/src/app/majors/metadata/Metadata.module.css +++ b/frontend/src/app/majors/metadata/Metadata.module.css @@ -1,7 +1,22 @@ +.Column { + display: flex; + flex-direction: column; +} + +.Row{ + display: flex; + flex-direction: row; +} .MetadataContainer{ + /* border: 1px solid red; */ + + margin-top: 50px; + margin-right: 25px; + width: 600px; + height: 600px; } .countBox { @@ -58,12 +73,10 @@ } .majorContainer { - padding: 20px 20px 20px 0; + width: auto; height: 400px; background-color: white; - margin-right: 10px; - /* border: 1px solid black; */ } .thumbtack { diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index 4fb5166..1ebc057 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -20,7 +20,7 @@ function Majors() const allProgramMetadatas: DegreeMetadata[][] = ALL_PROGRAM_METADATAS; - const shiftProgramIndex = (dir: number) => { + const shiftProgramIndex: Function = (dir: number) => { setProgramIndex((programIndex + dir + allProgramMetadatas.length) % allProgramMetadatas.length); }; @@ -40,6 +40,7 @@ function Majors() shiftProgramIndex={shiftProgramIndex} peekProgram={peekProgram} /> +
- -
-
- {props.subreq.subreq_name} -
-
- {props.subreq.user_courses_satisfying.length}/{props.subreq.courses_required} -
+
+
+ {props.subreq.user_courses_satisfying.length}|{props.subreq.courses_required} {props.subreq.subreq_name}
- -
+
{props.subreq.courses_options.map((course, index) => (
@@ -47,10 +40,17 @@ function RenderRequirement(props: { req: DegreeRequirement, user: User }) { return(
-
- {props.req.req_name} + +
+
+ {props.req.req_name} +
+
+ 0|{props.req.subreqs_required} +
-
+ +
{props.req.subreqs_list.map((subreq, index) => ( ))} From b9ac1f3220987f8f3376e4930c44def6f5c2dd7e Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Sat, 8 Mar 2025 11:26:48 -0800 Subject: [PATCH 14/38] course overflow --- frontend/src/app/majors/Majors.module.css | 8 +-- .../app/majors/metadata/Metadata.module.css | 8 +-- frontend/src/app/majors/metadata/Metadata.tsx | 2 +- .../requirements/Requirements.module.css | 38 ++++++++++++- .../app/majors/requirements/Requirements.tsx | 56 ++++++++++++++++--- .../course-icon/CourseIcon.module.css | 2 - .../src/components/course-icon/CourseIcon.tsx | 8 ++- frontend/src/database/data-catalog.ts | 13 +---- frontend/src/database/data-courses.ts | 15 +++++ frontend/src/database/data-cpsc.ts | 46 +++++++++------ frontend/src/database/data-studentcourses.ts | 11 ++++ frontend/src/database/data-user.ts | 8 +-- frontend/src/types/type-program.ts | 10 ++-- 13 files changed, 160 insertions(+), 65 deletions(-) create mode 100644 frontend/src/database/data-courses.ts create mode 100644 frontend/src/database/data-studentcourses.ts diff --git a/frontend/src/app/majors/Majors.module.css b/frontend/src/app/majors/Majors.module.css index 010a158..0243411 100644 --- a/frontend/src/app/majors/Majors.module.css +++ b/frontend/src/app/majors/Majors.module.css @@ -19,10 +19,10 @@ .Divider { border-right: 1px solid gray; - position: absolute; - top: 50px; - bottom: 50px; + margin-top: 50px; + margin-right: 25px; + margin-left: 25px; - width: 5px; + width: 1px; height: calc(100% - 100px); } diff --git a/frontend/src/app/majors/metadata/Metadata.module.css b/frontend/src/app/majors/metadata/Metadata.module.css index 7b9aea5..eefa8bd 100644 --- a/frontend/src/app/majors/metadata/Metadata.module.css +++ b/frontend/src/app/majors/metadata/Metadata.module.css @@ -12,10 +12,9 @@ .MetadataContainer{ /* border: 1px solid red; */ - margin-top: 50px; - margin-right: 25px; + margin-top: 75px; - width: 600px; + width: 550px; height: 600px; } @@ -72,7 +71,8 @@ margin-right: 10px; } -.majorContainer { +.MajorContainer { + /* border: 1px solid gray; */ width: auto; height: 400px; diff --git a/frontend/src/app/majors/metadata/Metadata.tsx b/frontend/src/app/majors/metadata/Metadata.tsx index 5471e7f..87316bc 100644 --- a/frontend/src/app/majors/metadata/Metadata.tsx +++ b/frontend/src/app/majors/metadata/Metadata.tsx @@ -86,7 +86,7 @@ function MetadataContent(props: { programIndex: number, }){ return ( -
+
diff --git a/frontend/src/app/majors/requirements/Requirements.module.css b/frontend/src/app/majors/requirements/Requirements.module.css index aabdc39..4efc472 100644 --- a/frontend/src/app/majors/requirements/Requirements.module.css +++ b/frontend/src/app/majors/requirements/Requirements.module.css @@ -12,8 +12,7 @@ .RequirementsContainer { /* border: 1px solid green; */ - margin-top: 25px; - margin-left: 25px; + margin-top: 37px; width: 600px; height: 650px; @@ -27,7 +26,7 @@ } .ReqsList { - /* border: 1px solid orange; */ + border: 1px solid white; /* height: 430px; scrollbar-color: rgb(131, 131, 131) transparent; @@ -49,6 +48,39 @@ font-weight: 500; } +.SubDesc { + color: grey; + font-style: italic; + font-size: 12px; + font-weight: 500; +} + + +.EmptyCourse { + border-radius: 15px; + padding: 2px 2px; + background-color: #e7e7e7c3; + transition: filter 0.4s ease; + width: 15px; /* Adjust width to match CourseIcon */ + height: 15px; /* Adjust height to match CourseIcon */ + display: inline-block; /* Keeps it inline with other elements */ +} + +.EmptyCourse:hover { + cursor: pointer; + filter: brightness(95%); +} + +.ToggleButton { + margin-top: 3px; + + cursor: pointer; + background: none; + border: none; + color: #65a8f0; + font-size: 14px; +} + .ButtonRow { display: flex; diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index 843b864..238d988 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -6,32 +6,68 @@ import { User, Course } from "@/types/type-user"; import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; import { CourseIcon } from "@/components/course-icon/CourseIcon"; -function RenderSubrequirementCourse(props: { course: Course, subreq: DegreeSubrequirement; user: User }){ +function RenderSubrequirementCourse(props: { course: Course | null, subreq: DegreeSubrequirement; user: User }){ + + // TODO + + if(props.course === null){ + return( +
+ ); + } + + const matchingStudentCourse = props.subreq.student_courses_satisfying.find( + (studentCourse) => studentCourse.course === props.course + ); return( -
- +
+
) } function RenderSubrequirement(props: { subreq: DegreeSubrequirement, user: User }) { + const [showAll, setShowAll] = useState(false); + + // Extract non-null courses + const nonNullCourses = props.subreq.courses_options.filter((course) => course !== null) as Course[]; + const satisfiedCourses = props.subreq.student_courses_satisfying.map((studentCourse) => studentCourse.course); + + // Determine which courses to show based on satisfaction condition + const isSatisfied = props.subreq.student_courses_satisfying.length === props.subreq.courses_required; + const displayedCourses = showAll + ? nonNullCourses + : isSatisfied + ? satisfiedCourses + : nonNullCourses.slice(0, 4); + + const extraCoursesCount = showAll ? 0 : nonNullCourses.length - displayedCourses.length; + return(
- {props.subreq.user_courses_satisfying.length}|{props.subreq.courses_required} {props.subreq.subreq_name} + {props.subreq.student_courses_satisfying.length}|{props.subreq.courses_required} {props.subreq.subreq_name} +
+
+ {props.subreq.subreq_desc}
-
- {props.subreq.courses_options.map((course, index) => ( +
+ {displayedCourses.map((course, index) => (
))} + {/* Show More / Show Less Button */} + {nonNullCourses.length > 4 && ( +
setShowAll(!showAll)}> + {showAll ? "<<" : ">>"} +
) + }
-
) } @@ -46,7 +82,7 @@ function RenderRequirement(props: { req: DegreeRequirement, user: User }) {props.req.req_name}
- 0|{props.req.subreqs_required} + {props.req.courses_satisfied_count}|{props.req.courses_required_count}
@@ -91,7 +127,9 @@ function Requirements(props: { user: User, setUser: Function, degreeConfiguratio
- +
+ +
); } diff --git a/frontend/src/components/course-icon/CourseIcon.module.css b/frontend/src/components/course-icon/CourseIcon.module.css index 1c4e427..7efaaa5 100644 --- a/frontend/src/components/course-icon/CourseIcon.module.css +++ b/frontend/src/components/course-icon/CourseIcon.module.css @@ -15,8 +15,6 @@ background-color: #F5F5F5; transition: filter 0.4s ease; - - margin-right: 2px; } .CourseIcon:hover { diff --git a/frontend/src/components/course-icon/CourseIcon.tsx b/frontend/src/components/course-icon/CourseIcon.tsx index 073a50b..31eb639 100644 --- a/frontend/src/components/course-icon/CourseIcon.tsx +++ b/frontend/src/components/course-icon/CourseIcon.tsx @@ -54,15 +54,17 @@ export function StudentCourseIcon(props: { studentCourse: StudentCourse, utility const dist = props.studentCourse.course.dist || []; + // style={{ backgroundColor: GetCourseColor(props.studentCourse.term) }} + return ( -
+
{props.utilityButton && props.utilityButton} {props.studentCourse.status === "" ? - : + : } {props.studentCourse.course.codes[0]} - + {/* */}
); } diff --git a/frontend/src/database/data-catalog.ts b/frontend/src/database/data-catalog.ts index 957bd25..70a9827 100644 --- a/frontend/src/database/data-catalog.ts +++ b/frontend/src/database/data-catalog.ts @@ -1,23 +1,12 @@ import { Course } from "@/types/type-user"; +import { HSAR_401 } from "./data-courses"; interface Catalog { number: number; courses: Course[]; } -export const CPSC_201: Course = { codes: ["CPSC 201"], title: "Introduction To Computer Science", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_202: Course = { codes: ["CPSC 202"], title: "Math Tools For Computer Scientists", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const MATH_244: Course = { codes: ["MATH 244"], title: "Discrete Mathematics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_223: Course = { codes: ["CPSC 223"], title: "Data Structures And Programming Techniques", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_323: Course = { codes: ["CPSC 323"], title: "Introduction To Systems Programming", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_365: Course = { codes: ["CPSC 365"], title: "Algorithms", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_366: Course = { codes: ["CPSC 366"], title: "Intensive Algorithms", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_490: Course = { codes: ["CPSC 490"], title: "Senior Project", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } - -export const HSAR_401: Course = { codes: ["HSAR 401"], title: "Critical Approaches To Art History", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } - - export const Catalogs: Catalog[] = [ { number: 202203, courses: [HSAR_401] }, { number: 202301, courses: [HSAR_401] }, diff --git a/frontend/src/database/data-courses.ts b/frontend/src/database/data-courses.ts new file mode 100644 index 0000000..c2c42f8 --- /dev/null +++ b/frontend/src/database/data-courses.ts @@ -0,0 +1,15 @@ + +import { Course } from "@/types/type-user" + +// HSAR ONE +export const HSAR_401: Course = { codes: ["HSAR 401"], title: "Critical Approaches To Art History", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } + +// CPSC PROGRAM +export const CPSC_201: Course = { codes: ["CPSC 201"], title: "Introduction To Computer Science", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_202: Course = { codes: ["CPSC 202"], title: "Math Tools For Computer Scientists", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const MATH_244: Course = { codes: ["MATH 244"], title: "Discrete Mathematics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_223: Course = { codes: ["CPSC 223"], title: "Data Structures And Programming Techniques", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_323: Course = { codes: ["CPSC 323"], title: "Introduction To Systems Programming", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_365: Course = { codes: ["CPSC 365"], title: "Algorithms", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_366: Course = { codes: ["CPSC 366"], title: "Intensive Algorithms", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_490: Course = { codes: ["CPSC 490"], title: "Senior Project", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } diff --git a/frontend/src/database/data-cpsc.ts b/frontend/src/database/data-cpsc.ts index 13f53a6..6ddd52e 100644 --- a/frontend/src/database/data-cpsc.ts +++ b/frontend/src/database/data-cpsc.ts @@ -1,7 +1,8 @@ - import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; -import { CPSC_201, CPSC_202, MATH_244, CPSC_223, CPSC_323, CPSC_365, CPSC_366, CPSC_490 } from "./data-catalog"; + +import { CPSC_201, CPSC_202, MATH_244, CPSC_223, CPSC_323, CPSC_365, CPSC_366, CPSC_490 } from "./data-courses"; +import { SC_CPSC_201, SC_CPSC_202, SC_CPSC_223, SC_CPSC_323 } from "./data-studentcourses"; const CPSC_INTRO: DegreeSubrequirement = { subreq_type_id: 1, @@ -11,7 +12,7 @@ const CPSC_INTRO: DegreeSubrequirement = { courses_options: [CPSC_201], courses_elective_range: null, courses_any_bool: false, - user_courses_satisfying: [CPSC_201], + student_courses_satisfying: [SC_CPSC_201], } const CPSC_MATH: DegreeSubrequirement = { @@ -22,7 +23,7 @@ const CPSC_MATH: DegreeSubrequirement = { courses_options: [CPSC_202, MATH_244], courses_elective_range: null, courses_any_bool: false, - user_courses_satisfying: [], + student_courses_satisfying: [], } const CPSC_DATA: DegreeSubrequirement = { @@ -33,7 +34,7 @@ const CPSC_DATA: DegreeSubrequirement = { courses_options: [CPSC_223], courses_elective_range: null, courses_any_bool: false, - user_courses_satisfying: [CPSC_223], + student_courses_satisfying: [SC_CPSC_223], } const CPSC_SYSTEMS: DegreeSubrequirement = { @@ -44,7 +45,7 @@ const CPSC_SYSTEMS: DegreeSubrequirement = { courses_options: [CPSC_323], courses_elective_range: null, courses_any_bool: false, - user_courses_satisfying: [CPSC_323], + student_courses_satisfying: [SC_CPSC_323], } const CPSC_ALGOS: DegreeSubrequirement = { @@ -55,45 +56,51 @@ const CPSC_ALGOS: DegreeSubrequirement = { courses_options: [CPSC_365, CPSC_366], courses_elective_range: null, courses_any_bool: false, - user_courses_satisfying: [], + student_courses_satisfying: [], } const CPSC_CORE: DegreeRequirement = { req_type_id: 1, req_name: "CORE", req_desc: "", - subreqs_required: 5, + + courses_required_count: 5, + courses_satisfied_count: 3, + subreqs_list: [CPSC_INTRO, CPSC_MATH, CPSC_DATA, CPSC_SYSTEMS, CPSC_ALGOS] } const CPSC_RANGE_ELECS: DegreeSubrequirement = { subreq_type_id: 1, subreq_name: "", - subreq_desc: "", + subreq_desc: "Standard elective or DUS approved extra-department substitution.", courses_required: 1, - courses_options: [], + courses_options: [null], courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, courses_any_bool: false, - user_courses_satisfying: [] + student_courses_satisfying: [] } const CPSC_SUB_ELEC: DegreeSubrequirement = { subreq_type_id: 1, subreq_name: "", - subreq_desc: "", + subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", courses_required: 3, - courses_options: [], + courses_options: [null, null, null], courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, courses_any_bool: true, - user_courses_satisfying: [] + student_courses_satisfying: [] } const CPSC_ELECTIVES: DegreeRequirement = { req_type_id: 1, req_name: "ELECTIVE", req_desc: "", - subreqs_required: 4, - subreqs_list: [CPSC_RANGE_ELECS, CPSC_SUB_ELEC] + + courses_required_count: 4, + courses_satisfied_count: 0, + + subreqs_list: [CPSC_SUB_ELEC, CPSC_RANGE_ELECS] } const CPSC_SENPROJ: DegreeSubrequirement = { @@ -104,14 +111,17 @@ const CPSC_SENPROJ: DegreeSubrequirement = { courses_options: [CPSC_490], courses_elective_range: null, courses_any_bool: false, - user_courses_satisfying: [] + student_courses_satisfying: [] } const CPSC_SENIOR: DegreeRequirement = { req_type_id: 1, req_name: "SENIOR", req_desc: "", - subreqs_required: 1, + + courses_required_count: 1, + courses_satisfied_count: 0, + subreqs_list: [CPSC_SENPROJ] } diff --git a/frontend/src/database/data-studentcourses.ts b/frontend/src/database/data-studentcourses.ts new file mode 100644 index 0000000..1fedb89 --- /dev/null +++ b/frontend/src/database/data-studentcourses.ts @@ -0,0 +1,11 @@ + +import { StudentCourse } from "@/types/type-user" +import { CPSC_201, CPSC_202, CPSC_223, CPSC_323 } from "./data-courses" + +export const SC_CPSC_201: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_201 } +export const SC_CPSC_202: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_202 } +export const SC_CPSC_223: StudentCourse = { term: 202501, status: "DA", result: "GRADE_PASS", course: CPSC_223 } +export const SC_CPSC_323: StudentCourse = { term: 202503, status: "MA", result: "IP", course: CPSC_323 } + + + diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index 0a02b75..ea0c37b 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -1,5 +1,7 @@ import { User } from "./../types/type-user"; + +import { SC_CPSC_201, SC_CPSC_223, SC_CPSC_323 } from "./data-studentcourses"; import { CPSC_CONFIG } from "./data-cpsc"; export const Ryan: User = { @@ -7,11 +9,7 @@ export const Ryan: User = { netID: "rgg32", onboard: false, FYP: { - studentCourses: [ - { term: 202403, status: "DA", result: "GRADE_PASS", course: { codes: ["CPSC 201"], title: "Intro To Computer Science", credit: 1, dist: ["QR"], seasons: [] } }, - { term: 202501, status: "DA", result: "GRADE_PASS", course: { codes: ["MATH 244"], title: "Discrete Mathematics", credit: 1, dist: ["QR"], seasons: [] } }, - { term: 202503, status: "MA", result: "IP", course: { codes: ["CPSC 223"], title: "Data Structures", credit: 1, dist: ["QR"], seasons: [] } }, - ], + studentCourses: [SC_CPSC_201, SC_CPSC_223, SC_CPSC_323], studentTermArrangement: { first_year: [0, 202403, 202501], sophomore: [0, 202503, 202601], diff --git a/frontend/src/types/type-program.ts b/frontend/src/types/type-program.ts index 95148d4..2eda0b0 100644 --- a/frontend/src/types/type-program.ts +++ b/frontend/src/types/type-program.ts @@ -1,5 +1,5 @@ -import { Course } from "./type-user"; +import { Course, StudentCourse } from "./type-user"; export interface StudentDegree { status: string; // DA | ADD | PIN @@ -49,11 +49,11 @@ export interface DegreeSubrequirement { subreq_desc: string; courses_required: number; - courses_options: Course[]; + courses_options: (Course | null)[]; courses_elective_range: SubreqElectiveRange; courses_any_bool: boolean; - user_courses_satisfying: Course[]; + student_courses_satisfying: StudentCourse[]; } export interface DegreeRequirement { @@ -61,7 +61,9 @@ export interface DegreeRequirement { req_name: string; req_desc: string; - subreqs_required: number; + courses_required_count: number; + courses_satisfied_count: number; + subreqs_list: DegreeSubrequirement[]; } From c3f90132f45e343acce4ad9ea578e9d35337068f Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Sun, 9 Mar 2025 11:35:07 -0700 Subject: [PATCH 15/38] more majors --- frontend/src/app/majors/page.tsx | 1 + .../requirements/Requirements.module.css | 33 ++- .../app/majors/requirements/Requirements.tsx | 164 +++++++++----- .../course-icon/CourseIcon.module.css | 31 +-- .../src/database/{ => configs}/data-cpsc.ts | 4 +- frontend/src/database/configs/data-econ.ts | 162 ++++++++++++++ frontend/src/database/configs/data-hist.ts | 202 ++++++++++++++++++ frontend/src/database/configs/data-plsc.ts | 162 ++++++++++++++ frontend/src/database/data-courses.ts | 27 +++ frontend/src/database/data-degree.ts | 86 ++++---- frontend/src/database/data-studentcourses.ts | 5 +- frontend/src/database/data-user.ts | 36 ++-- frontend/src/types/type-program.ts | 6 +- 13 files changed, 763 insertions(+), 156 deletions(-) rename frontend/src/database/{ => configs}/data-cpsc.ts (97%) create mode 100644 frontend/src/database/configs/data-econ.ts create mode 100644 frontend/src/database/configs/data-hist.ts create mode 100644 frontend/src/database/configs/data-plsc.ts diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index 1ebc057..38552a1 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -44,6 +44,7 @@ function Majors()
diff --git a/frontend/src/app/majors/requirements/Requirements.module.css b/frontend/src/app/majors/requirements/Requirements.module.css index 4efc472..ab5aa0b 100644 --- a/frontend/src/app/majors/requirements/Requirements.module.css +++ b/frontend/src/app/majors/requirements/Requirements.module.css @@ -58,11 +58,11 @@ .EmptyCourse { border-radius: 15px; - padding: 2px 2px; - background-color: #e7e7e7c3; + /* padding: 2px 2px; e7e7e7c3 */ + background-color: #F5F5F5; transition: filter 0.4s ease; - width: 15px; /* Adjust width to match CourseIcon */ - height: 15px; /* Adjust height to match CourseIcon */ + width: 24px; /* Adjust width to match CourseIcon */ + height: 24px; /* Adjust height to match CourseIcon */ display: inline-block; /* Keeps it inline with other elements */ } @@ -110,3 +110,28 @@ .resetButton:active, .editButton:active { color: #333; /* Darker color on click */ } + + + + +.ButtonRow { + display: flex; + gap: 6px; + margin-bottom: 2px; +} + +.SubreqButton { + font-size: 10px; + padding: 3px; + + cursor: pointer; + border-radius: 6px; + + background-color: rgb(211, 211, 211); + color: white; + transition: background-color 0.3s ease; +} + +.SubreqButton.Selected { + background-color: rgb(100, 178, 238); +} \ No newline at end of file diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index 238d988..da62ea5 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -2,12 +2,13 @@ import { useState } from "react"; import Style from "./Requirements.module.css"; +import { useAuth } from "@/app/providers"; + import { User, Course } from "@/types/type-user"; import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; import { CourseIcon } from "@/components/course-icon/CourseIcon"; - function RenderSubrequirementCourse(props: { course: Course | null, subreq: DegreeSubrequirement; user: User }){ // TODO @@ -29,85 +30,142 @@ function RenderSubrequirementCourse(props: { course: Course | null, subreq: Degr ) } -function RenderSubrequirement(props: { subreq: DegreeSubrequirement, user: User }) -{ +function RenderSubrequirement(props: { subreq: DegreeSubrequirement; user: User }) { const [showAll, setShowAll] = useState(false); - // Extract non-null courses + // Separate null and non-null courses + const nullCourses = props.subreq.courses_options.filter((course) => course === null); const nonNullCourses = props.subreq.courses_options.filter((course) => course !== null) as Course[]; const satisfiedCourses = props.subreq.student_courses_satisfying.map((studentCourse) => studentCourse.course); - // Determine which courses to show based on satisfaction condition + // Determine which courses to show const isSatisfied = props.subreq.student_courses_satisfying.length === props.subreq.courses_required; - const displayedCourses = showAll + const displayedNonNullCourses = showAll ? nonNullCourses : isSatisfied ? satisfiedCourses : nonNullCourses.slice(0, 4); - const extraCoursesCount = showAll ? 0 : nonNullCourses.length - displayedCourses.length; + // Ensure null courses are always displayed + const displayedCourses = [...nullCourses, ...displayedNonNullCourses]; - return( -
-
- {props.subreq.student_courses_satisfying.length}|{props.subreq.courses_required} {props.subreq.subreq_name} -
-
+ return ( +
+
+ {props.subreq.student_courses_satisfying.length}|{props.subreq.courses_required} {props.subreq.subreq_name} +
+
{props.subreq.subreq_desc}
-
- {displayedCourses.map((course, index) => ( -
- -
- ))} - {/* Show More / Show Less Button */} - {nonNullCourses.length > 4 && ( -
setShowAll(!showAll)}> - {showAll ? "<<" : ">>"} -
) - } -
-
- ) +
+ {displayedCourses.map((course, index) => ( +
+ +
+ ))} + {/* Toggle Button to Expand / Collapse */} + {nonNullCourses.length > 4 && ( +
setShowAll(!showAll)}> + {showAll ? "<<" : ">>"} +
+ )} +
+
+ ); } -function RenderRequirement(props: { req: DegreeRequirement, user: User }) -{ - return( -
- -
-
- {props.req.req_name} -
-
- {props.req.courses_satisfied_count}|{props.req.courses_required_count} -
-
- -
- {props.req.subreqs_list.map((subreq, index) => ( - - ))} -
-
- ) +function RenderRequirement(props: { programIndex: number, req: DegreeRequirement; user: User }){ + + const { user, setUser } = useAuth(); + const { req, programIndex } = props; + const { subreqs_list, subreqs_required_count } = req; + + // Get the correct degree configuration (assumes only one degree per program) + const degreeConfig = user.FYP.degreeConfigurations[programIndex][0]; + + // Find the corresponding requirement in `degreeConfig` + const requirement = degreeConfig.reqs_list.find((r: DegreeRequirement) => r.req_name === req.req_name); + + if (!requirement) return null; // Fail-safe, shouldn't happen + + // Move clicked subreq to the front if it's beyond the first `subreqs_required_count` + const handleSubreqClick = (subreq: DegreeSubrequirement) => { + if (!subreqs_required_count) return; // Ignore clicks if not applicable + + setUser((prevUser: User) => { + const newUser = { ...prevUser }; + + // Get the degree and requirement again inside state update + const updatedDegree = newUser.FYP.degreeConfigurations[programIndex][0]; + const updatedRequirement = updatedDegree.reqs_list.find((r) => r.req_name === req.req_name); + + if (!updatedRequirement) return prevUser; // Failsafe + + const updatedSubreqs = [...updatedRequirement.subreqs_list]; + const index = updatedSubreqs.findIndex((s) => s.subreq_name === subreq.subreq_name); + + if (index >= subreqs_required_count) { + // Move it to the front + updatedSubreqs.splice(index, 1); + updatedSubreqs.unshift(subreq); + } + + // Update the requirement's subreqs_list in user state + updatedRequirement.subreqs_list = updatedSubreqs; + + return newUser; + }); + }; + + + return ( +
+
+
{props.req.req_name}
+
+ {props.req.checkbox !== undefined ? props.req.courses_satisfied_count === props.req.courses_required_count ? "✅" : "❌" : `${props.req.courses_satisfied_count}|${props.req.courses_required_count}`} +
+
+ + {/* Subreq Toggle Buttons - Only show if subreqs_required_count exists and < total subreqs */} + {subreqs_required_count && subreqs_list.length > subreqs_required_count && ( +
+ {subreqs_list.map((subreq, index) => ( +
handleSubreqClick(subreq)}> + {subreq.subreq_name} +
+ ))} +
+ )} + + {/* Display Selected Subreqs - Enforce subreqs_required_count if present */} +
+ {subreqs_required_count + ? subreqs_list.slice(0, subreqs_required_count).map((subreq, index) => ( + + )) + : subreqs_list.map((subreq, index) => ( + + ))} +
+
+ ); } -function RequirementsContent(props: { edit: boolean, degreeConfiguration: DegreeConfiguration, user: User, setUser: Function }) + +function RequirementsContent(props: { edit: boolean, programIndex: number, degreeConfiguration: DegreeConfiguration, user: User, setUser: Function }) { return(
{props.degreeConfiguration.reqs_list.map((req, index) => ( - + ))}
); } -function Requirements(props: { user: User, setUser: Function, degreeConfiguration: DegreeConfiguration }) +function Requirements(props: { user: User, setUser: Function, programIndex: number, degreeConfiguration: DegreeConfiguration }) { const [edit, setEdit] = useState(false); @@ -128,7 +186,7 @@ function Requirements(props: { user: User, setUser: Function, degreeConfiguratio
- +
); diff --git a/frontend/src/components/course-icon/CourseIcon.module.css b/frontend/src/components/course-icon/CourseIcon.module.css index 7efaaa5..a4dac89 100644 --- a/frontend/src/components/course-icon/CourseIcon.module.css +++ b/frontend/src/components/course-icon/CourseIcon.module.css @@ -9,6 +9,7 @@ width: max-content; padding: 2px 4px; + min-height: 18px; font-size: 14px; font-weight: bold; @@ -21,33 +22,3 @@ cursor: pointer; filter: brightness(95%); } - -.Mark { - padding-left: 1px; - padding-right: 1px; -} - - - - -.OrIconContainer { - display: flex; - align-items: center; - padding: 2px 4px; - border-radius: 15px; - background-color: #e3e3e3; - transition: filter 0.4s ease; - margin-right: 2px; -} - -.OrIconContainer:hover { - cursor: pointer; - filter: brightness(95%); -} - -.OrSeparator { - margin-bottom: 3px; - margin-right: 2px; - font-size: 14px; - font-weight: bold; -} \ No newline at end of file diff --git a/frontend/src/database/data-cpsc.ts b/frontend/src/database/configs/data-cpsc.ts similarity index 97% rename from frontend/src/database/data-cpsc.ts rename to frontend/src/database/configs/data-cpsc.ts index 6ddd52e..afdbc40 100644 --- a/frontend/src/database/data-cpsc.ts +++ b/frontend/src/database/configs/data-cpsc.ts @@ -1,8 +1,8 @@ import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; -import { CPSC_201, CPSC_202, MATH_244, CPSC_223, CPSC_323, CPSC_365, CPSC_366, CPSC_490 } from "./data-courses"; -import { SC_CPSC_201, SC_CPSC_202, SC_CPSC_223, SC_CPSC_323 } from "./data-studentcourses"; +import { CPSC_201, CPSC_202, MATH_244, CPSC_223, CPSC_323, CPSC_365, CPSC_366, CPSC_490 } from "./../data-courses"; +import { SC_CPSC_201, SC_CPSC_202, SC_CPSC_223, SC_CPSC_323 } from "./../data-studentcourses"; const CPSC_INTRO: DegreeSubrequirement = { subreq_type_id: 1, diff --git a/frontend/src/database/configs/data-econ.ts b/frontend/src/database/configs/data-econ.ts new file mode 100644 index 0000000..68adfb1 --- /dev/null +++ b/frontend/src/database/configs/data-econ.ts @@ -0,0 +1,162 @@ + +import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; + +import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151 } from "../data-courses"; +import { SC_ECON_110 } from "../data-studentcourses"; + +// INTRO + +const ECON_MATH: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "MATH", + subreq_desc: "118 or 120 recommended. Any MATH 200+ satisfies.", + courses_required: 1, + courses_options: [null, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const ECON_INTRO_MICRO: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "INTRO MICRO", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_108, ECON_110, ECON_115], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [SC_ECON_110], +} + +const ECON_INTRO_MACRO: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "INTRO MACRO", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_111, ECON_116], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const ECON_INTRO: DegreeRequirement = { + req_type_id: 1, + req_name: "INTRO", + req_desc: "", + + courses_required_count: 3, + courses_satisfied_count: 1, + + subreqs_list: [ECON_MATH, ECON_INTRO_MICRO, ECON_INTRO_MACRO] +} + +// CORE + +const ECON_CORE_MICRO: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "INTERMEDIATE MICRO", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_121, ECON_125], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const ECON_CORE_MACRO: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "INTERMEDIATE MACRO", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_122, ECON_126], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const ECON_CORE_METRICS: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "ECONOMETRICS", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_117, ECON_123, ECON_136], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const ECON_CORE: DegreeRequirement = { + req_type_id: 1, + req_name: "CORE", + req_desc: "", + + courses_required_count: 3, + courses_satisfied_count: 0, + + subreqs_list: [ECON_CORE_MICRO, ECON_CORE_MACRO, ECON_CORE_METRICS] +} + +// ELECTIVE + +const ECON_RANGE_ELECS: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "", + subreq_desc: "Standard elective or DUS approved extra-department substitution.", + courses_required: 1, + courses_options: [null], + courses_elective_range: { dept: "CPSC", min_code: 123, max_code: 999 }, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const ECON_SUB_ELEC: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "", + subreq_desc: "Intermediate or advanced ECON courses, traditionally numbered 123+.", + courses_required: 3, + courses_options: [null, null, null], + courses_elective_range: { dept: "ECON", min_code: 123, max_code: 999 }, + courses_any_bool: true, + student_courses_satisfying: [] +} + +const ECON_ELECTIVES: DegreeRequirement = { + req_type_id: 1, + req_name: "ELECTIVE", + req_desc: "", + + courses_required_count: 4, + courses_satisfied_count: 0, + + subreqs_list: [ECON_SUB_ELEC, ECON_RANGE_ELECS] +} + +// SENIOR + +const ECON_SEN_SUB: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "SENIOR REQUIREMENT", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: { dept: "ECON", min_code: 400, max_code: 491 }, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const ECON_SENIOR: DegreeRequirement = { + req_type_id: 1, + req_name: "SENIOR", + req_desc: "", + + courses_required_count: 2, + courses_satisfied_count: 0, + + subreqs_list: [ECON_SEN_SUB] +} + +// FINAL + +export const ECON_CONFIG: DegreeConfiguration = { + reqs_list: [ECON_INTRO, ECON_CORE, ECON_ELECTIVES, ECON_SENIOR] +} diff --git a/frontend/src/database/configs/data-hist.ts b/frontend/src/database/configs/data-hist.ts new file mode 100644 index 0000000..f551242 --- /dev/null +++ b/frontend/src/database/configs/data-hist.ts @@ -0,0 +1,202 @@ + +import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; + +// PRE + +const HIST_PRE: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "PRE 1800", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const HIST_PREINDUSTRIAL: DegreeRequirement = { + req_type_id: 1, + req_name: "PREINDUSTRIAL", + req_desc: "", + + courses_required_count: 2, + courses_satisfied_count: 0, + + checkbox: true, + subreqs_list: [HIST_PRE] +} + +// GLOBAL + +const HIST_CORE_GLOB_AFRICA: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "AFRICA", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const HIST_CORE_GLOB_ASIA: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "ASIA", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const HIST_CORE_GLOB_EUROPE: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "EUROPE", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const HIST_CORE_GLOB_LA: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "LATIN AMERICA", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const HIST_CORE_GLOB_ME: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "MIDDLE EAST", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const HIST_CORE_GLOB_US: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "U.S.", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const HIST_GLOB_CORE: DegreeRequirement = { + req_type_id: 1, + req_name: "GLOBAL", + req_desc: "", + + courses_required_count: 5, + courses_satisfied_count: 0, + + subreqs_required_count: 5, + subreqs_satisfied_count: 0, + + subreqs_list: [HIST_CORE_GLOB_AFRICA, HIST_CORE_GLOB_ASIA, HIST_CORE_GLOB_EUROPE, HIST_CORE_GLOB_LA, HIST_CORE_GLOB_ME, HIST_CORE_GLOB_US] +} + +// SEMINAR + +const HIST_DEPT_SEMINAR: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "DEPARTMENTAL", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const HIST_SEMINAR: DegreeRequirement = { + req_type_id: 1, + req_name: "SEMINAR", + req_desc: "", + + courses_required_count: 2, + courses_satisfied_count: 0, + + checkbox: true, + subreqs_list: [HIST_DEPT_SEMINAR] +} + +// ELECTIVE + +const HIST_ELEC_INDIV: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "", + subreq_desc: "", + courses_required: 5, + courses_options: [null, null, null, null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const HIST_ELECTIVE: DegreeRequirement = { + req_type_id: 1, + req_name: "ELECTIVE", + req_desc: "", + + courses_required_count: 5, + courses_satisfied_count: 0, + + subreqs_list: [HIST_ELEC_INDIV] +} + +// SENIOR + +const ECON_SEN_ONE: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "ONE TERM", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const ECON_SEN_TWO: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "TWO TERM", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const ECON_SENIOR: DegreeRequirement = { + req_type_id: 1, + req_name: "SENIOR", + req_desc: "", + + courses_required_count: 0, + courses_satisfied_count: 0, + + subreqs_required_count: 1, + subreqs_satisfied_count: 0, + + subreqs_list: [ECON_SEN_ONE, ECON_SEN_TWO] +} + +// FINAL + +export const HIST_CONFIG: DegreeConfiguration = { + reqs_list: [HIST_PREINDUSTRIAL, HIST_SEMINAR, HIST_GLOB_CORE, HIST_ELECTIVE, ECON_SENIOR] +} diff --git a/frontend/src/database/configs/data-plsc.ts b/frontend/src/database/configs/data-plsc.ts new file mode 100644 index 0000000..55b7cc8 --- /dev/null +++ b/frontend/src/database/configs/data-plsc.ts @@ -0,0 +1,162 @@ + +import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; + +import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151 } from "../data-courses"; +import { SC_ECON_110 } from "../data-studentcourses"; + +// INTRO + +const ECON_MATH: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "MATH", + subreq_desc: "118 or 120 recommended. Any MATH 200+ satisfies.", + courses_required: 1, + courses_options: [null, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const ECON_INTRO_MICRO: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "INTRO MICRO", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_108, ECON_110, ECON_115], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [SC_ECON_110], +} + +const ECON_INTRO_MACRO: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "INTRO MACRO", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_111, ECON_116], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const ECON_INTRO: DegreeRequirement = { + req_type_id: 1, + req_name: "INTRO", + req_desc: "", + + courses_required_count: 3, + courses_satisfied_count: 1, + + subreqs_list: [ECON_MATH, ECON_INTRO_MICRO, ECON_INTRO_MACRO] +} + +// CORE + +const ECON_CORE_MICRO: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "INTERMEDIATE MICRO", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_121, ECON_125], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const ECON_CORE_MACRO: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "INTERMEDIATE MACRO", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_122, ECON_126], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const ECON_CORE_METRICS: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "ECONOMETRICS", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_117, ECON_123, ECON_136], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const ECON_CORE: DegreeRequirement = { + req_type_id: 1, + req_name: "CORE", + req_desc: "", + + courses_required_count: 3, + courses_satisfied_count: 0, + + subreqs_list: [ECON_CORE_MICRO, ECON_CORE_MACRO, ECON_CORE_METRICS] +} + +// ELECTIVE + +const ECON_RANGE_ELECS: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "", + subreq_desc: "Standard elective or DUS approved extra-department substitution.", + courses_required: 1, + courses_options: [null], + courses_elective_range: { dept: "CPSC", min_code: 123, max_code: 999 }, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const ECON_SUB_ELEC: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "", + subreq_desc: "Intermediate or advanced ECON courses, traditionally numbered 123+.", + courses_required: 3, + courses_options: [null, null, null], + courses_elective_range: { dept: "ECON", min_code: 123, max_code: 999 }, + courses_any_bool: true, + student_courses_satisfying: [] +} + +const ECON_ELECTIVES: DegreeRequirement = { + req_type_id: 1, + req_name: "ELECTIVE", + req_desc: "", + + courses_required_count: 4, + courses_satisfied_count: 0, + + subreqs_list: [ECON_SUB_ELEC, ECON_RANGE_ELECS] +} + +// SENIOR + +const ECON_SEN_SUB: DegreeSubrequirement = { + subreq_type_id: 1, + subreq_name: "SENIOR REQUIREMENT", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: { dept: "ECON", min_code: 400, max_code: 491 }, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const ECON_SENIOR: DegreeRequirement = { + req_type_id: 1, + req_name: "SENIOR", + req_desc: "", + + courses_required_count: 2, + courses_satisfied_count: 0, + + subreqs_list: [ECON_SEN_SUB] +} + +// FINAL + +export const PLSC_CONFIG: DegreeConfiguration = { + reqs_list: [ECON_INTRO, ECON_CORE, ECON_ELECTIVES, ECON_SENIOR] +} diff --git a/frontend/src/database/data-courses.ts b/frontend/src/database/data-courses.ts index c2c42f8..71e83a5 100644 --- a/frontend/src/database/data-courses.ts +++ b/frontend/src/database/data-courses.ts @@ -13,3 +13,30 @@ export const CPSC_323: Course = { codes: ["CPSC 323"], title: "Introduction To S export const CPSC_365: Course = { codes: ["CPSC 365"], title: "Algorithms", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } export const CPSC_366: Course = { codes: ["CPSC 366"], title: "Intensive Algorithms", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } export const CPSC_490: Course = { codes: ["CPSC 490"], title: "Senior Project", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } + +// ECON PROGRAM +export const MATH_110: Course = { codes: ["MATH 110"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const MATH_111: Course = { codes: ["MATH 111"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const MATH_112: Course = { codes: ["MATH 112"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const MATH_115: Course = { codes: ["MATH 115"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const MATH_116: Course = { codes: ["MATH 116"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ENAS_151: Course = { codes: ["ENAS 151"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const MATH_118: Course = { codes: ["MATH 118"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const MATH_120: Course = { codes: ["MATH 120"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } + +export const ECON_108: Course = { codes: ["ECON 108"], title: "Introductory Microeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_110: Course = { codes: ["ECON 110"], title: "Introductory Microeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_115: Course = { codes: ["ECON 115"], title: "Introductory Microeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } + +export const ECON_111: Course = { codes: ["ECON 111"], title: "Introductory Macroeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_116: Course = { codes: ["ECON 116"], title: "Introductory Macroeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } + +export const ECON_121: Course = { codes: ["ECON 121"], title: "Intermediate Microeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_125: Course = { codes: ["ECON 125"], title: "Intermediate Microeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } + +export const ECON_122: Course = { codes: ["ECON 122"], title: "Intermediate Macroeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_126: Course = { codes: ["ECON 126"], title: "Intermediate Macroeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } + +export const ECON_117: Course = { codes: ["ECON 117"], title: "Econometrics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_123: Course = { codes: ["ECON 123"], title: "Econometrics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_136: Course = { codes: ["ECON 136"], title: "Econometrics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } diff --git a/frontend/src/database/data-degree.ts b/frontend/src/database/data-degree.ts index 1536dd2..ca1b720 100644 --- a/frontend/src/database/data-degree.ts +++ b/frontend/src/database/data-degree.ts @@ -2,48 +2,48 @@ import { DegreeMetadata } from "@/types/type-program"; export const ALL_PROGRAM_METADATAS: DegreeMetadata[][] = [ - [{ - name: "Computer Science", - abbr: "CPSC", - degreeType: "BACH_ART", - dus: { address: "AKW 208 432-6400", email: "cpsc.yale.edu", name: "Y. Richard Yang" }, - about: "The Department of Computer Science offers a B.A. degree program, as well as four combined major programs in cooperation with other departments: Electrical Engineering and Computer Science, Computer Science and Economics, Computer Science and Mathematics, and Computer Science and Psychology. Each program not only provides a solid technical education but also allows students either to take a broad range of courses in other disciplines or to complete the requirements of a second major.", - stats: { courses: 10, rating: 0, type: "QR", workload: 0 }, - students: 0, - wesbiteLink: "http://cpsc.yale.edu", - catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/computer-science/", - }], - [{ - name: "History", - abbr: "HIST", - degreeType: "BACH_ART", - dus: { address: "AKW 208 432-6400", email: "cpsc.yale.edu", name: "Y. Richard Yang" }, - about: "The History major is for students who understand that shaping the future requires knowing the past. History courses explore many centuries of human experimentation and ingenuity, from the global to the individual scale. History majors learn to be effective storytellers and analysts, and to craft arguments that speak to broad audiences. They make extensive use of Yale’s vast library resources to create pioneering original research projects. Students of history learn to think about politics and government, sexuality, the economy, cultural and intellectual life, war and society, and other themes in broadly humanistic—rather than narrowly technocratic—ways.", - stats: { courses: 10, rating: 0, type: "QR", workload: 0 }, - students: 0, - wesbiteLink: "http://cpsc.yale.edu", - catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/computer-science/", - }], - [{ - name: "Political Science", - abbr: "PLSC", - degreeType: "BACH_ART", - dus: { address: "AKW 208 432-6400", email: "cpsc.yale.edu", name: "Y. Richard Yang" }, - about: "Political science addresses how individuals and groups organize, allocate, and challenge the power to make collective decisions involving public issues. The goal of the major is to enable students to think critically and analytically about the agents, incentives, and institutions that shape political phenomena within human society. The subfields of political philosophy and analytical political theory (which includes the study of both qualitative and quantitative methodology) support the acquisition of the lenses through which such thought skills can be enriched.", - stats: { courses: 10, rating: 0, type: "QR", workload: 0 }, - students: 0, - wesbiteLink: "http://cpsc.yale.edu", - catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/computer-science/", - }], [{ - name: "Environmental Studies", - abbr: "EVST", - degreeType: "BACH_ART", - dus: { address: "AKW 208 432-6400", email: "cpsc.yale.edu", name: "Y. Richard Yang" }, - about: "Environmental Studies offers the opportunity to examine human relations with their environments from diverse perspectives. The major encourages interdisciplinary study in (1) social sciences, including anthropology, political science, law, economics, and ethics; (2) humanities, to include history, literature, religion, and the arts; and (3) natural sciences, such as biology, ecology, human health, geology, and chemistry. Students work with faculty advisers and the directors of undergraduate studies (DUS) to concentrate on some of the most pressing environmental and sustainability problems of our time.", - stats: { courses: 10, rating: 0, type: "QR", workload: 0 }, - students: 0, - wesbiteLink: "http://cpsc.yale.edu", - catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/computer-science/", - }] + name: "History", + abbr: "HIST", + degreeType: "BACH_ART", + dus: { address: "HQ 211, (203) 432-1366", email: "history@yale.edu", name: "Alan Mikhail" }, + about: "The History major offers a broad exploration of human experience across time, cultures, and societies. Students gain expertise in archival research, critical analysis, and historical interpretation. The major prepares students to engage with contemporary issues by understanding their historical contexts, whether in politics, economics, or cultural transformations. History students learn to construct arguments based on evidence and develop a strong capacity for analytical reasoning and effective communication.", + stats: { courses: 0, rating: 0, type: "Hu", workload: 0 }, + students: 0, + wesbiteLink: "https://history.yale.edu", + catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/history/", + }], + [{ + name: "Computer Science", + abbr: "CPSC", + degreeType: "BACH_ART", + dus: { address: "AKW 208, (203) 432-6400", email: "cpsc@yale.edu", name: "Y. Richard Yang" }, + about: "The Department of Computer Science offers a B.A. degree program, as well as four combined major programs in cooperation with other departments: Electrical Engineering and Computer Science, Computer Science and Economics, Computer Science and Mathematics, and Computer Science and Psychology. Each program not only provides a solid technical education but also allows students either to take a broad range of courses in other disciplines or to complete the requirements of a second major.", + stats: { courses: 0, rating: 0, type: "QR", workload: 0 }, + students: 0, + wesbiteLink: "http://cpsc.yale.edu", + catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/computer-science/", + }], + [{ + name: "Political Science", + abbr: "PLSC", + degreeType: "BACH_ART", + dus: { address: "Rosenkranz Hall 130, (203) 432-5248", email: "politicalscience@yale.edu", name: "Isabela Mares" }, + about: "The Political Science major examines governments, political behavior, and institutional structures at local, national, and global levels. Students engage with the theoretical foundations of politics, quantitative and qualitative methodologies, and policy analysis. The program prepares students for careers in law, public policy, international relations, and academia while offering flexibility to explore subfields such as comparative politics, American politics, and political philosophy.", + stats: { courses: 0, rating: 0, type: "So", workload: 0 }, + students: 0, + wesbiteLink: "https://politicalscience.yale.edu", + catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/political-science/", + }], + [{ + name: "Economics", + abbr: "ECON", + degreeType: "BACH_ART", + dus: { address: "28 Hillhouse Ave, (203) 432-3576", email: "economics@yale.edu", name: "Dirk Bergemann" }, + about: "The Economics major provides a rigorous framework for understanding financial markets, public policy, and decision-making. Students learn analytical techniques in microeconomics, macroeconomics, and econometrics, developing critical skills in data interpretation and policy evaluation. The major offers pathways in finance, economic development, and quantitative analysis, preparing students for careers in government, consulting, business, and research.", + stats: { courses: 0, rating: 0, type: "QR", workload: 0 }, + students: 0, + wesbiteLink: "https://economics.yale.edu", + catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/economics/", + }] ]; diff --git a/frontend/src/database/data-studentcourses.ts b/frontend/src/database/data-studentcourses.ts index 1fedb89..54120ca 100644 --- a/frontend/src/database/data-studentcourses.ts +++ b/frontend/src/database/data-studentcourses.ts @@ -1,11 +1,14 @@ import { StudentCourse } from "@/types/type-user" -import { CPSC_201, CPSC_202, CPSC_223, CPSC_323 } from "./data-courses" +import { CPSC_201, CPSC_202, CPSC_223, CPSC_323, ECON_110 } from "./data-courses" +// CPSC COURSES export const SC_CPSC_201: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_201 } export const SC_CPSC_202: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_202 } export const SC_CPSC_223: StudentCourse = { term: 202501, status: "DA", result: "GRADE_PASS", course: CPSC_223 } export const SC_CPSC_323: StudentCourse = { term: 202503, status: "MA", result: "IP", course: CPSC_323 } +// ECON COURSES +export const SC_ECON_110: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: ECON_110 } diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index ea0c37b..e60e861 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -1,31 +1,30 @@ import { User } from "./../types/type-user"; -import { SC_CPSC_201, SC_CPSC_223, SC_CPSC_323 } from "./data-studentcourses"; -import { CPSC_CONFIG } from "./data-cpsc"; +import { CPSC_CONFIG } from "./configs/data-cpsc"; +import { ECON_CONFIG } from "./configs/data-econ"; +import { HIST_CONFIG } from "./configs/data-hist"; +import { PLSC_CONFIG } from "./configs/data-plsc"; export const Ryan: User = { name: "Ryan", netID: "rgg32", onboard: false, FYP: { - studentCourses: [SC_CPSC_201, SC_CPSC_223, SC_CPSC_323], + studentCourses: [], studentTermArrangement: { first_year: [0, 202403, 202501], sophomore: [0, 202503, 202601], junior: [0, 202603, 202701], senior: [0, 202703, 202801], }, - languagePlacement: { - language: "Spanish", - level: 5, - }, + languagePlacement: { language: "Spanish", level: 5 }, degreeDeclarations: [], degreeConfigurations: [ + [HIST_CONFIG], [CPSC_CONFIG], - [], - [], - [] + [PLSC_CONFIG], + [ECON_CONFIG] ], } } @@ -42,20 +41,13 @@ export const NullUser: User = { junior: [], senior: [], }, - languagePlacement: { - language: "", - level: 0, - }, + languagePlacement: { language: "", level: 0 }, degreeDeclarations: [], degreeConfigurations: [ - [ - ], - [ - ], - [ - ], - [ - ] + [], + [], + [], + [] ], } } diff --git a/frontend/src/types/type-program.ts b/frontend/src/types/type-program.ts index 2eda0b0..59c1bf4 100644 --- a/frontend/src/types/type-program.ts +++ b/frontend/src/types/type-program.ts @@ -63,7 +63,11 @@ export interface DegreeRequirement { courses_required_count: number; courses_satisfied_count: number; - + + subreqs_required_count?: number; + subreqs_satisfied_count?: number; + + checkbox?: boolean; subreqs_list: DegreeSubrequirement[]; } From 63eb98c8852dba844f985f3fd3d75b9baf468574 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Mon, 10 Mar 2025 14:33:27 -0700 Subject: [PATCH 16/38] bloody trainwreck --- .../app/majors/metadata/Metadata.module.css | 34 +++ frontend/src/app/majors/metadata/Metadata.tsx | 140 +++++---- .../overhead/major-search/MajorSearch.tsx | 2 +- .../src/app/majors/overhead/pinned/Pinned.tsx | 2 +- frontend/src/app/majors/page.tsx | 35 +-- .../app/majors/requirements/Requirements.tsx | 99 ++++--- frontend/src/database/configs/data-econ.ts | 162 ----------- frontend/src/database/configs/data-hist.ts | 202 ------------- frontend/src/database/configs/data-plsc.ts | 162 ----------- frontend/src/database/data-degree.ts | 49 ---- frontend/src/database/data-user.ts | 59 ++-- .../data-cpsc.ts => programs/concs-cpsc.ts} | 93 +++--- .../database/programs/configs/config-econ.ts | 162 +++++++++++ .../database/programs/configs/config-hist.ts | 273 ++++++++++++++++++ .../database/programs/configs/config-plsc.ts | 201 +++++++++++++ .../src/database/programs/data-program.ts | 73 +++++ .../src/database/programs/metas/meta-econ.ts | 17 ++ .../src/database/programs/metas/meta-hist.ts | 23 ++ .../src/database/programs/metas/meta-plsc.ts | 17 ++ frontend/src/types/type-program.ts | 75 ++--- frontend/src/types/type-user.ts | 6 +- 21 files changed, 1068 insertions(+), 818 deletions(-) delete mode 100644 frontend/src/database/configs/data-econ.ts delete mode 100644 frontend/src/database/configs/data-hist.ts delete mode 100644 frontend/src/database/configs/data-plsc.ts delete mode 100644 frontend/src/database/data-degree.ts rename frontend/src/database/{configs/data-cpsc.ts => programs/concs-cpsc.ts} (53%) create mode 100644 frontend/src/database/programs/configs/config-econ.ts create mode 100644 frontend/src/database/programs/configs/config-hist.ts create mode 100644 frontend/src/database/programs/configs/config-plsc.ts create mode 100644 frontend/src/database/programs/data-program.ts create mode 100644 frontend/src/database/programs/metas/meta-econ.ts create mode 100644 frontend/src/database/programs/metas/meta-hist.ts create mode 100644 frontend/src/database/programs/metas/meta-plsc.ts diff --git a/frontend/src/app/majors/metadata/Metadata.module.css b/frontend/src/app/majors/metadata/Metadata.module.css index eefa8bd..5293146 100644 --- a/frontend/src/app/majors/metadata/Metadata.module.css +++ b/frontend/src/app/majors/metadata/Metadata.module.css @@ -92,4 +92,38 @@ .thumbtack:active { opacity: 0.5; /* Even lighter on click */ +} + + +.ScrollButton { + background-color: white; + border: none; + cursor: pointer; + padding: 10px; + display: inline-block; +} + +.ToggleContainer { + display: flex; + flex-direction: row; + + margin-left: 80px; + margin-bottom: 8px; +} + +.ToggleOption { + padding: 4px 10px; + cursor: pointer; + background-color: white; + color: black; + transition: background-color 0.3s, color 0.3s; + border-radius: 5px; + + border: 1px solid rgb(196, 196, 196); + +} + +.ToggleOption.active { + background-color: #598ff4; + color: white; } \ No newline at end of file diff --git a/frontend/src/app/majors/metadata/Metadata.tsx b/frontend/src/app/majors/metadata/Metadata.tsx index 87316bc..33f0395 100644 --- a/frontend/src/app/majors/metadata/Metadata.tsx +++ b/frontend/src/app/majors/metadata/Metadata.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from "react"; import Link from 'next/link'; import { User } from "@/types/type-user"; -import { DegreeMetadata } from "@/types/type-program"; +import { Program } from "@/types/type-program"; import { pinProgram, addProgram } from "./MetadataUtils"; import Style from "./Metadata.module.css"; @@ -15,7 +15,7 @@ function MetadataTopshelf(props: { degreeMetadata: DegreeMetadata }) { return ( -
+
pinProgram(props.programIndex, 0, props.user, props.setUser)}>
@@ -27,7 +27,7 @@ function MetadataTopshelf(props: {
{props.degreeMetadata.name}
- {props.degreeMetadata.students} + {props.degreeMetadata.info.students}
MAJOR @@ -41,7 +41,7 @@ function MetadataTopshelf(props: { ); } -function MetadataStats(props: { degreeMetadata: DegreeMetadata }){ +function MetadataStats(props: { concMetadata: ConcMetadata }){ return(
@@ -53,81 +53,118 @@ function MetadataStats(props: { degreeMetadata: DegreeMetadata }){ COURSES
- {props.degreeMetadata.stats.courses} + {props.concMetadata.stats.courses}
RATING
-
{props.degreeMetadata.stats.rating}
+
{props.concMetadata.stats.rating}
WORKLOAD
-
{props.degreeMetadata.stats.workload}
+
{props.concMetadata.stats.workload}
TYPE
-
{props.degreeMetadata.stats.type}
+
{props.concMetadata.stats.type}
); } +function MetadataBody(props: { concIndex: number, degreeMetadata: DegreeMetadata }){ + + const { concIndex, degreeMetadata } = props; + + return( +
+ +
+ ABOUT +
+
+ {degreeMetadata.concs[concIndex].about} +
+
+ DUS +
+
+ {degreeMetadata.info.dus.name}; {degreeMetadata.info.dus.address} +
+
+
MAJOR CATALOG
+
MAJOR WEBSITE
+
+
+ ); +} + +function MetadataToggle(props: { degreeMetadatas: DegreeMetadata[], degreeIndex: number, setDegreeIndex: Function, concIndex: number, setConcIndex: Function }){ + // TODO + // If the currently selected degreeMetadata's concs attribute has length greater than 1, then + // display an additional set of identical toggle buttons below the current set where + // each divs content is now conc.conc_name and clicking on one calls setConcIndex for the newly clicked one. + + return ( +
+
+ {props.degreeMetadatas.map((metadata, index) => ( +
props.setDegreeIndex(index)}> + {metadata.degreeType} +
+ ))} +
+ + {props.degreeMetadatas[props.degreeIndex].concs.length > 1 && ( +
+ {props.degreeMetadatas[props.degreeIndex].concs.map((conc, index) => ( +
props.setConcIndex(index)}> + {conc.conc_name} +
+ ))} +
+ )} +
+ + ); +} + function MetadataContent(props: { user: User, setUser: Function, - degreeMetadata: DegreeMetadata, programIndex: number, + degreeMetadatas: DegreeMetadata[], + degreeIndex: number, + setDegreeIndex: Function + concIndex: number, + setConcIndex: Function }){ return (
- -
- -
- ABOUT -
-
- {props.degreeMetadata.about} -
-
- DUS -
-
- {props.degreeMetadata.dus.name}; {props.degreeMetadata.dus.address} -
- -
-
MAJOR CATALOG
-
MAJOR WEBSITE
-
-
+ + +
); } -function MetadataScrollButton(props: { - shiftProgramIndex: Function; - peekProgram: Function; - dir: number; -}) { +function MetadataScrollButton(props: { shiftProgramIndex: Function; peekProgram: Function; dir: number }) +{ return ( -
props.shiftProgramIndex(props.dir)} - > +
props.shiftProgramIndex(props.dir)}>
@@ -144,17 +181,18 @@ function MetadataScrollButton(props: { function Metadata(props: { - user: User, - setUser: Function, - programMetadatas: DegreeMetadata[], - programIndex: number, - shiftProgramIndex: Function, + program: Program, + index: { conc: number, deg: number, prog: number } peekProgram: Function }) { return (
- +
); diff --git a/frontend/src/app/majors/overhead/major-search/MajorSearch.tsx b/frontend/src/app/majors/overhead/major-search/MajorSearch.tsx index ff42cc7..24b6803 100644 --- a/frontend/src/app/majors/overhead/major-search/MajorSearch.tsx +++ b/frontend/src/app/majors/overhead/major-search/MajorSearch.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useRef } from "react"; import Style from "./MajorSearch.module.css"; import { User } from "@/types/type-user"; -import { ALL_PROGRAM_METADATAS } from "@/database/data-degree"; +import { ALL_PROGRAM_METADATAS } from "@/database/programs/metas/meta-econ"; function MajorSearchBar(props: { user: User; setProgramIndex: Function }) { const [searchTerm, setSearchTerm] = useState(""); diff --git a/frontend/src/app/majors/overhead/pinned/Pinned.tsx b/frontend/src/app/majors/overhead/pinned/Pinned.tsx index d5f7d1b..5ee46c5 100644 --- a/frontend/src/app/majors/overhead/pinned/Pinned.tsx +++ b/frontend/src/app/majors/overhead/pinned/Pinned.tsx @@ -3,7 +3,7 @@ import Style from "./Pinned.module.css"; import { User } from "@/types/type-user"; import { StudentDegree } from "@/types/type-program"; -import { ALL_PROGRAM_METADATAS } from "@/database/data-degree"; +import { ALL_PROGRAM_METADATAS } from "@/database/programs/metas/meta-econ"; function DegreeIcon(props: { studentDegree: StudentDegree, setProgramIndex: Function }) { const mark = (status: string) => { diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index 38552a1..ff77375 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -3,9 +3,6 @@ import { useState } from "react"; import { useAuth } from "../providers"; -import { DegreeMetadata } from "@/types/type-program"; -import { ALL_PROGRAM_METADATAS } from "@/database/data-degree"; - import Style from "./Majors.module.css"; import NavBar from "@/components/navbar/NavBar"; import Overhead from "./overhead/Overhead"; @@ -15,37 +12,31 @@ import Requirements from "./requirements/Requirements"; function Majors() { const { user, setUser } = useAuth(); + const { programs } = user.FYP.programs; - const [programIndex, setProgramIndex] = useState(0); - - const allProgramMetadatas: DegreeMetadata[][] = ALL_PROGRAM_METADATAS; - - const shiftProgramIndex: Function = (dir: number) => { - setProgramIndex((programIndex + dir + allProgramMetadatas.length) % allProgramMetadatas.length); - }; + const [index, setIndex] = useState({ conc: 0, deg: 0, prog: 0}); + const updateIndex: Function = (index: { conc: number, deg: number, prog: number}) => { + setIndex(index) + }; - const peekProgram = (dir: number) => { - return allProgramMetadatas[(programIndex + dir + allProgramMetadatas.length) % allProgramMetadatas.length][0]; + const peekProgram = (dir: number) => { + return programs[(index.prog + dir + programs.length) % programs.length]; }; return(
- }/> + {/* utility={} */} +
diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index da62ea5..82fce59 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -5,11 +5,11 @@ import Style from "./Requirements.module.css"; import { useAuth } from "@/app/providers"; import { User, Course } from "@/types/type-user"; -import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; +import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; import { CourseIcon } from "@/components/course-icon/CourseIcon"; -function RenderSubrequirementCourse(props: { course: Course | null, subreq: DegreeSubrequirement; user: User }){ +function RenderSubrequirementCourse(props: { course: Course | null, subreq: ConcentrationSubrequirement; user: User }){ // TODO @@ -30,7 +30,7 @@ function RenderSubrequirementCourse(props: { course: Course | null, subreq: Degr ) } -function RenderSubrequirement(props: { subreq: DegreeSubrequirement; user: User }) { +function RenderSubrequirement(props: { subreq: ConcentrationSubrequirement; user: User }) { const [showAll, setShowAll] = useState(false); // Separate null and non-null courses @@ -74,61 +74,67 @@ function RenderSubrequirement(props: { subreq: DegreeSubrequirement; user: User ); } -function RenderRequirement(props: { programIndex: number, req: DegreeRequirement; user: User }){ +function RenderRequirement(props: { req: ConcentrationRequirement }){ - const { user, setUser } = useAuth(); - const { req, programIndex } = props; - const { subreqs_list, subreqs_required_count } = req; + // const { user, setUser } = useAuth(); + // const { req, programIndex, degreeIndex } = props; + const { subreqs_list, subreqs_required_count } = props.req; - // Get the correct degree configuration (assumes only one degree per program) - const degreeConfig = user.FYP.degreeConfigurations[programIndex][0]; + // // Get the correct degree configuration (assumes only one degree per program) + // const degreeConfig = user.FYP.degreeConfigurations[programIndex][0]; // Find the corresponding requirement in `degreeConfig` - const requirement = degreeConfig.reqs_list.find((r: DegreeRequirement) => r.req_name === req.req_name); + // const requirement = degreeConfig[0].reqs_list.find((r: DegreeRequirement) => r.req_name === req.req_name); - if (!requirement) return null; // Fail-safe, shouldn't happen + // if (!requirement) return null; // Fail-safe, shouldn't happen // Move clicked subreq to the front if it's beyond the first `subreqs_required_count` - const handleSubreqClick = (subreq: DegreeSubrequirement) => { - if (!subreqs_required_count) return; // Ignore clicks if not applicable + // const handleSubreqClick = (subreq: DegreeSubrequirement) => { + // if (!subreqs_required_count) return; // Ignore clicks if not applicable - setUser((prevUser: User) => { - const newUser = { ...prevUser }; + // setUser((prevUser: User) => { + // const newUser = { ...prevUser }; - // Get the degree and requirement again inside state update - const updatedDegree = newUser.FYP.degreeConfigurations[programIndex][0]; - const updatedRequirement = updatedDegree.reqs_list.find((r) => r.req_name === req.req_name); + // // Get the degree and requirement again inside state update + // const updatedDegree = newUser.FYP.degreeConfigurations[programIndex][degreeIndex]; + // const updatedRequirement = updatedDegree[0].reqs_list.find((r) => r.req_name === req.req_name); // FIXXX - if (!updatedRequirement) return prevUser; // Failsafe + // if (!updatedRequirement) return prevUser; // Failsafe - const updatedSubreqs = [...updatedRequirement.subreqs_list]; - const index = updatedSubreqs.findIndex((s) => s.subreq_name === subreq.subreq_name); + // const updatedSubreqs = [...updatedRequirement.subreqs_list]; + // const index = updatedSubreqs.findIndex((s) => s.subreq_name === subreq.subreq_name); - if (index >= subreqs_required_count) { - // Move it to the front - updatedSubreqs.splice(index, 1); - updatedSubreqs.unshift(subreq); - } + // if (index >= subreqs_required_count) { + // // Move it to the front + // updatedSubreqs.splice(index, 1); + // updatedSubreqs.unshift(subreq); + // } - // Update the requirement's subreqs_list in user state - updatedRequirement.subreqs_list = updatedSubreqs; + // // Update the requirement's subreqs_list in user state + // updatedRequirement.subreqs_list = updatedSubreqs; - return newUser; - }); - }; + // return newUser; + // }); + // }; return (
-
{props.req.req_name}
+
+ {props.req.req_name} +
{props.req.checkbox !== undefined ? props.req.courses_satisfied_count === props.req.courses_required_count ? "✅" : "❌" : `${props.req.courses_satisfied_count}|${props.req.courses_required_count}`}
+
+ {props.req.req_desc} +
+ {/* Subreq Toggle Buttons - Only show if subreqs_required_count exists and < total subreqs */} - {subreqs_required_count && subreqs_list.length > subreqs_required_count && ( + {/* {subreqs_required_count && subreqs_list.length > subreqs_required_count && (
{subreqs_list.map((subreq, index) => (
handleSubreqClick(subreq)}> @@ -136,7 +142,7 @@ function RenderRequirement(props: { programIndex: number, req: DegreeRequirement
))}
- )} + )} */} {/* Display Selected Subreqs - Enforce subreqs_required_count if present */}
@@ -153,25 +159,26 @@ function RenderRequirement(props: { programIndex: number, req: DegreeRequirement } -function RequirementsContent(props: { edit: boolean, programIndex: number, degreeConfiguration: DegreeConfiguration, user: User, setUser: Function }) +// function RequirementsContent(props: { edit: boolean, programIndex: number, degreeIndex: number, degreeConfiguration: DegreeConfiguration, user: User, setUser: Function }) +function RequirementsContent(props: { conc: DegreeConcentration }) { return(
- {props.degreeConfiguration.reqs_list.map((req, index) => ( - + {props.conc.conc_reqs.map((req, index) => ( + ))}
); } -function Requirements(props: { user: User, setUser: Function, programIndex: number, degreeConfiguration: DegreeConfiguration }) +// function Requirements(props: { user: User, setUser: Function, programIndex: number, degreeIndex: number, degreeConfiguration: DegreeConfiguration }) +function Requirements(props: { conc: DegreeConcentration }) { - - const [edit, setEdit] = useState(false); - const updateEdit = () => { - setEdit(!edit); - }; + // const [edit, setEdit] = useState(false); + // const updateEdit = () => { + // setEdit(!edit); + // }; return(
@@ -180,13 +187,15 @@ function Requirements(props: { user: User, setUser: Function, programIndex: numb Requirements
-
+ {/*
*/} +
- + {/* */} +
); diff --git a/frontend/src/database/configs/data-econ.ts b/frontend/src/database/configs/data-econ.ts deleted file mode 100644 index 68adfb1..0000000 --- a/frontend/src/database/configs/data-econ.ts +++ /dev/null @@ -1,162 +0,0 @@ - -import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; - -import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151 } from "../data-courses"; -import { SC_ECON_110 } from "../data-studentcourses"; - -// INTRO - -const ECON_MATH: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "MATH", - subreq_desc: "118 or 120 recommended. Any MATH 200+ satisfies.", - courses_required: 1, - courses_options: [null, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const ECON_INTRO_MICRO: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "INTRO MICRO", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_108, ECON_110, ECON_115], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [SC_ECON_110], -} - -const ECON_INTRO_MACRO: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "INTRO MACRO", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_111, ECON_116], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const ECON_INTRO: DegreeRequirement = { - req_type_id: 1, - req_name: "INTRO", - req_desc: "", - - courses_required_count: 3, - courses_satisfied_count: 1, - - subreqs_list: [ECON_MATH, ECON_INTRO_MICRO, ECON_INTRO_MACRO] -} - -// CORE - -const ECON_CORE_MICRO: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "INTERMEDIATE MICRO", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_121, ECON_125], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const ECON_CORE_MACRO: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "INTERMEDIATE MACRO", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_122, ECON_126], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const ECON_CORE_METRICS: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "ECONOMETRICS", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_117, ECON_123, ECON_136], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const ECON_CORE: DegreeRequirement = { - req_type_id: 1, - req_name: "CORE", - req_desc: "", - - courses_required_count: 3, - courses_satisfied_count: 0, - - subreqs_list: [ECON_CORE_MICRO, ECON_CORE_MACRO, ECON_CORE_METRICS] -} - -// ELECTIVE - -const ECON_RANGE_ELECS: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "", - subreq_desc: "Standard elective or DUS approved extra-department substitution.", - courses_required: 1, - courses_options: [null], - courses_elective_range: { dept: "CPSC", min_code: 123, max_code: 999 }, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const ECON_SUB_ELEC: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "", - subreq_desc: "Intermediate or advanced ECON courses, traditionally numbered 123+.", - courses_required: 3, - courses_options: [null, null, null], - courses_elective_range: { dept: "ECON", min_code: 123, max_code: 999 }, - courses_any_bool: true, - student_courses_satisfying: [] -} - -const ECON_ELECTIVES: DegreeRequirement = { - req_type_id: 1, - req_name: "ELECTIVE", - req_desc: "", - - courses_required_count: 4, - courses_satisfied_count: 0, - - subreqs_list: [ECON_SUB_ELEC, ECON_RANGE_ELECS] -} - -// SENIOR - -const ECON_SEN_SUB: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "SENIOR REQUIREMENT", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: { dept: "ECON", min_code: 400, max_code: 491 }, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const ECON_SENIOR: DegreeRequirement = { - req_type_id: 1, - req_name: "SENIOR", - req_desc: "", - - courses_required_count: 2, - courses_satisfied_count: 0, - - subreqs_list: [ECON_SEN_SUB] -} - -// FINAL - -export const ECON_CONFIG: DegreeConfiguration = { - reqs_list: [ECON_INTRO, ECON_CORE, ECON_ELECTIVES, ECON_SENIOR] -} diff --git a/frontend/src/database/configs/data-hist.ts b/frontend/src/database/configs/data-hist.ts deleted file mode 100644 index f551242..0000000 --- a/frontend/src/database/configs/data-hist.ts +++ /dev/null @@ -1,202 +0,0 @@ - -import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; - -// PRE - -const HIST_PRE: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "PRE 1800", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const HIST_PREINDUSTRIAL: DegreeRequirement = { - req_type_id: 1, - req_name: "PREINDUSTRIAL", - req_desc: "", - - courses_required_count: 2, - courses_satisfied_count: 0, - - checkbox: true, - subreqs_list: [HIST_PRE] -} - -// GLOBAL - -const HIST_CORE_GLOB_AFRICA: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "AFRICA", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const HIST_CORE_GLOB_ASIA: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "ASIA", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const HIST_CORE_GLOB_EUROPE: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "EUROPE", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const HIST_CORE_GLOB_LA: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "LATIN AMERICA", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const HIST_CORE_GLOB_ME: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "MIDDLE EAST", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const HIST_CORE_GLOB_US: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "U.S.", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const HIST_GLOB_CORE: DegreeRequirement = { - req_type_id: 1, - req_name: "GLOBAL", - req_desc: "", - - courses_required_count: 5, - courses_satisfied_count: 0, - - subreqs_required_count: 5, - subreqs_satisfied_count: 0, - - subreqs_list: [HIST_CORE_GLOB_AFRICA, HIST_CORE_GLOB_ASIA, HIST_CORE_GLOB_EUROPE, HIST_CORE_GLOB_LA, HIST_CORE_GLOB_ME, HIST_CORE_GLOB_US] -} - -// SEMINAR - -const HIST_DEPT_SEMINAR: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "DEPARTMENTAL", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const HIST_SEMINAR: DegreeRequirement = { - req_type_id: 1, - req_name: "SEMINAR", - req_desc: "", - - courses_required_count: 2, - courses_satisfied_count: 0, - - checkbox: true, - subreqs_list: [HIST_DEPT_SEMINAR] -} - -// ELECTIVE - -const HIST_ELEC_INDIV: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "", - subreq_desc: "", - courses_required: 5, - courses_options: [null, null, null, null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const HIST_ELECTIVE: DegreeRequirement = { - req_type_id: 1, - req_name: "ELECTIVE", - req_desc: "", - - courses_required_count: 5, - courses_satisfied_count: 0, - - subreqs_list: [HIST_ELEC_INDIV] -} - -// SENIOR - -const ECON_SEN_ONE: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "ONE TERM", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const ECON_SEN_TWO: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "TWO TERM", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const ECON_SENIOR: DegreeRequirement = { - req_type_id: 1, - req_name: "SENIOR", - req_desc: "", - - courses_required_count: 0, - courses_satisfied_count: 0, - - subreqs_required_count: 1, - subreqs_satisfied_count: 0, - - subreqs_list: [ECON_SEN_ONE, ECON_SEN_TWO] -} - -// FINAL - -export const HIST_CONFIG: DegreeConfiguration = { - reqs_list: [HIST_PREINDUSTRIAL, HIST_SEMINAR, HIST_GLOB_CORE, HIST_ELECTIVE, ECON_SENIOR] -} diff --git a/frontend/src/database/configs/data-plsc.ts b/frontend/src/database/configs/data-plsc.ts deleted file mode 100644 index 55b7cc8..0000000 --- a/frontend/src/database/configs/data-plsc.ts +++ /dev/null @@ -1,162 +0,0 @@ - -import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; - -import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151 } from "../data-courses"; -import { SC_ECON_110 } from "../data-studentcourses"; - -// INTRO - -const ECON_MATH: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "MATH", - subreq_desc: "118 or 120 recommended. Any MATH 200+ satisfies.", - courses_required: 1, - courses_options: [null, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const ECON_INTRO_MICRO: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "INTRO MICRO", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_108, ECON_110, ECON_115], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [SC_ECON_110], -} - -const ECON_INTRO_MACRO: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "INTRO MACRO", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_111, ECON_116], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const ECON_INTRO: DegreeRequirement = { - req_type_id: 1, - req_name: "INTRO", - req_desc: "", - - courses_required_count: 3, - courses_satisfied_count: 1, - - subreqs_list: [ECON_MATH, ECON_INTRO_MICRO, ECON_INTRO_MACRO] -} - -// CORE - -const ECON_CORE_MICRO: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "INTERMEDIATE MICRO", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_121, ECON_125], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const ECON_CORE_MACRO: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "INTERMEDIATE MACRO", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_122, ECON_126], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const ECON_CORE_METRICS: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "ECONOMETRICS", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_117, ECON_123, ECON_136], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const ECON_CORE: DegreeRequirement = { - req_type_id: 1, - req_name: "CORE", - req_desc: "", - - courses_required_count: 3, - courses_satisfied_count: 0, - - subreqs_list: [ECON_CORE_MICRO, ECON_CORE_MACRO, ECON_CORE_METRICS] -} - -// ELECTIVE - -const ECON_RANGE_ELECS: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "", - subreq_desc: "Standard elective or DUS approved extra-department substitution.", - courses_required: 1, - courses_options: [null], - courses_elective_range: { dept: "CPSC", min_code: 123, max_code: 999 }, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const ECON_SUB_ELEC: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "", - subreq_desc: "Intermediate or advanced ECON courses, traditionally numbered 123+.", - courses_required: 3, - courses_options: [null, null, null], - courses_elective_range: { dept: "ECON", min_code: 123, max_code: 999 }, - courses_any_bool: true, - student_courses_satisfying: [] -} - -const ECON_ELECTIVES: DegreeRequirement = { - req_type_id: 1, - req_name: "ELECTIVE", - req_desc: "", - - courses_required_count: 4, - courses_satisfied_count: 0, - - subreqs_list: [ECON_SUB_ELEC, ECON_RANGE_ELECS] -} - -// SENIOR - -const ECON_SEN_SUB: DegreeSubrequirement = { - subreq_type_id: 1, - subreq_name: "SENIOR REQUIREMENT", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: { dept: "ECON", min_code: 400, max_code: 491 }, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const ECON_SENIOR: DegreeRequirement = { - req_type_id: 1, - req_name: "SENIOR", - req_desc: "", - - courses_required_count: 2, - courses_satisfied_count: 0, - - subreqs_list: [ECON_SEN_SUB] -} - -// FINAL - -export const PLSC_CONFIG: DegreeConfiguration = { - reqs_list: [ECON_INTRO, ECON_CORE, ECON_ELECTIVES, ECON_SENIOR] -} diff --git a/frontend/src/database/data-degree.ts b/frontend/src/database/data-degree.ts deleted file mode 100644 index ca1b720..0000000 --- a/frontend/src/database/data-degree.ts +++ /dev/null @@ -1,49 +0,0 @@ - -import { DegreeMetadata } from "@/types/type-program"; - -export const ALL_PROGRAM_METADATAS: DegreeMetadata[][] = [ - [{ - name: "History", - abbr: "HIST", - degreeType: "BACH_ART", - dus: { address: "HQ 211, (203) 432-1366", email: "history@yale.edu", name: "Alan Mikhail" }, - about: "The History major offers a broad exploration of human experience across time, cultures, and societies. Students gain expertise in archival research, critical analysis, and historical interpretation. The major prepares students to engage with contemporary issues by understanding their historical contexts, whether in politics, economics, or cultural transformations. History students learn to construct arguments based on evidence and develop a strong capacity for analytical reasoning and effective communication.", - stats: { courses: 0, rating: 0, type: "Hu", workload: 0 }, - students: 0, - wesbiteLink: "https://history.yale.edu", - catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/history/", - }], - [{ - name: "Computer Science", - abbr: "CPSC", - degreeType: "BACH_ART", - dus: { address: "AKW 208, (203) 432-6400", email: "cpsc@yale.edu", name: "Y. Richard Yang" }, - about: "The Department of Computer Science offers a B.A. degree program, as well as four combined major programs in cooperation with other departments: Electrical Engineering and Computer Science, Computer Science and Economics, Computer Science and Mathematics, and Computer Science and Psychology. Each program not only provides a solid technical education but also allows students either to take a broad range of courses in other disciplines or to complete the requirements of a second major.", - stats: { courses: 0, rating: 0, type: "QR", workload: 0 }, - students: 0, - wesbiteLink: "http://cpsc.yale.edu", - catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/computer-science/", - }], - [{ - name: "Political Science", - abbr: "PLSC", - degreeType: "BACH_ART", - dus: { address: "Rosenkranz Hall 130, (203) 432-5248", email: "politicalscience@yale.edu", name: "Isabela Mares" }, - about: "The Political Science major examines governments, political behavior, and institutional structures at local, national, and global levels. Students engage with the theoretical foundations of politics, quantitative and qualitative methodologies, and policy analysis. The program prepares students for careers in law, public policy, international relations, and academia while offering flexibility to explore subfields such as comparative politics, American politics, and political philosophy.", - stats: { courses: 0, rating: 0, type: "So", workload: 0 }, - students: 0, - wesbiteLink: "https://politicalscience.yale.edu", - catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/political-science/", - }], - [{ - name: "Economics", - abbr: "ECON", - degreeType: "BACH_ART", - dus: { address: "28 Hillhouse Ave, (203) 432-3576", email: "economics@yale.edu", name: "Dirk Bergemann" }, - about: "The Economics major provides a rigorous framework for understanding financial markets, public policy, and decision-making. Students learn analytical techniques in microeconomics, macroeconomics, and econometrics, developing critical skills in data interpretation and policy evaluation. The major offers pathways in finance, economic development, and quantitative analysis, preparing students for careers in government, consulting, business, and research.", - stats: { courses: 0, rating: 0, type: "QR", workload: 0 }, - students: 0, - wesbiteLink: "https://economics.yale.edu", - catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/economics/", - }] -]; diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index e60e861..e2d5c5e 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -1,10 +1,6 @@ import { User } from "./../types/type-user"; - -import { CPSC_CONFIG } from "./configs/data-cpsc"; -import { ECON_CONFIG } from "./configs/data-econ"; -import { HIST_CONFIG } from "./configs/data-hist"; -import { PLSC_CONFIG } from "./configs/data-plsc"; +import { PROG_CPSC } from "./programs/data-program"; export const Ryan: User = { name: "Ryan", @@ -19,38 +15,33 @@ export const Ryan: User = { senior: [0, 202703, 202801], }, languagePlacement: { language: "Spanish", level: 5 }, - degreeDeclarations: [], - degreeConfigurations: [ - [HIST_CONFIG], - [CPSC_CONFIG], - [PLSC_CONFIG], - [ECON_CONFIG] - ], + programs: [PROG_CPSC], + declarations: [], } } -export const NullUser: User = { - name: "", - netID: "", - onboard: false, - FYP: { - studentCourses: [], - studentTermArrangement: { - first_year: [], - sophomore: [], - junior: [], - senior: [], - }, - languagePlacement: { language: "", level: 0 }, - degreeDeclarations: [], - degreeConfigurations: [ - [], - [], - [], - [] - ], - } -} +// export const NullUser: User = { +// name: "", +// netID: "", +// onboard: false, +// FYP: { +// studentCourses: [], +// studentTermArrangement: { +// first_year: [], +// sophomore: [], +// junior: [], +// senior: [], +// }, +// languagePlacement: { language: "", level: 0 }, +// degreeDeclarations: [], +// degreeConfigurations: [ +// [], +// [], +// [], +// [] +// ], +// } +// } diff --git a/frontend/src/database/configs/data-cpsc.ts b/frontend/src/database/programs/concs-cpsc.ts similarity index 53% rename from frontend/src/database/configs/data-cpsc.ts rename to frontend/src/database/programs/concs-cpsc.ts index afdbc40..601e08c 100644 --- a/frontend/src/database/configs/data-cpsc.ts +++ b/frontend/src/database/programs/concs-cpsc.ts @@ -1,11 +1,12 @@ -import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; +import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; import { CPSC_201, CPSC_202, MATH_244, CPSC_223, CPSC_323, CPSC_365, CPSC_366, CPSC_490 } from "./../data-courses"; -import { SC_CPSC_201, SC_CPSC_202, SC_CPSC_223, SC_CPSC_323 } from "./../data-studentcourses"; +import { SC_CPSC_201, SC_CPSC_223, SC_CPSC_323 } from "./../data-studentcourses"; -const CPSC_INTRO: DegreeSubrequirement = { - subreq_type_id: 1, +// CORE + +const CORE_SUB_INTRO: ConcentrationSubrequirement = { subreq_name: "INTRO", subreq_desc: "", courses_required: 1, @@ -15,8 +16,7 @@ const CPSC_INTRO: DegreeSubrequirement = { student_courses_satisfying: [SC_CPSC_201], } -const CPSC_MATH: DegreeSubrequirement = { - subreq_type_id: 1, +const CORE_SUB_MATH: ConcentrationSubrequirement = { subreq_name: "DISCRETE MATH", subreq_desc: "", courses_required: 1, @@ -26,8 +26,7 @@ const CPSC_MATH: DegreeSubrequirement = { student_courses_satisfying: [], } -const CPSC_DATA: DegreeSubrequirement = { - subreq_type_id: 1, +const CORE_SUB_DATA: ConcentrationSubrequirement = { subreq_name: "DATA STRUCTURES", subreq_desc: "", courses_required: 1, @@ -37,8 +36,7 @@ const CPSC_DATA: DegreeSubrequirement = { student_courses_satisfying: [SC_CPSC_223], } -const CPSC_SYSTEMS: DegreeSubrequirement = { - subreq_type_id: 1, +const CORE_SUB_SYSTEMS: ConcentrationSubrequirement = { subreq_name: "SYSTEMS", subreq_desc: "", courses_required: 1, @@ -48,8 +46,7 @@ const CPSC_SYSTEMS: DegreeSubrequirement = { student_courses_satisfying: [SC_CPSC_323], } -const CPSC_ALGOS: DegreeSubrequirement = { - subreq_type_id: 1, +const CORE_SUB_ALGOS: ConcentrationSubrequirement = { subreq_name: "ALGORITHMS", subreq_desc: "", courses_required: 1, @@ -59,52 +56,65 @@ const CPSC_ALGOS: DegreeSubrequirement = { student_courses_satisfying: [], } -const CPSC_CORE: DegreeRequirement = { - req_type_id: 1, +const CPSC_CORE: ConcentrationRequirement = { req_name: "CORE", req_desc: "", - courses_required_count: 5, courses_satisfied_count: 3, - - subreqs_list: [CPSC_INTRO, CPSC_MATH, CPSC_DATA, CPSC_SYSTEMS, CPSC_ALGOS] + subreqs_list: [CORE_SUB_INTRO, CORE_SUB_MATH, CORE_SUB_DATA, CORE_SUB_SYSTEMS, CORE_SUB_ALGOS] } -const CPSC_RANGE_ELECS: DegreeSubrequirement = { - subreq_type_id: 1, +// ELECTIVE + +const ELEC_MULT_BA: ConcentrationSubrequirement = { subreq_name: "", - subreq_desc: "Standard elective or DUS approved extra-department substitution.", - courses_required: 1, - courses_options: [null], + subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", + courses_required: 4, + courses_options: [null, null, null, null], courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, courses_any_bool: false, student_courses_satisfying: [] } -const CPSC_SUB_ELEC: DegreeSubrequirement = { - subreq_type_id: 1, +const ELEC_MULT_BS: ConcentrationSubrequirement = { subreq_name: "", subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", - courses_required: 3, - courses_options: [null, null, null], + courses_required: 6, + courses_options: [null, null, null, null, null, null], + courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const ELEC_SUB: ConcentrationSubrequirement = { + subreq_name: "", + subreq_desc: "Standard elective or DUS approved extra-department substitution.", + courses_required: 1, + courses_options: [null], courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, courses_any_bool: true, student_courses_satisfying: [] } -const CPSC_ELECTIVES: DegreeRequirement = { - req_type_id: 1, +const CPSC_BA_ELEC: ConcentrationRequirement = { req_name: "ELECTIVE", req_desc: "", - courses_required_count: 4, courses_satisfied_count: 0, + subreqs_list: [ELEC_SUB, ELEC_MULT_BA] +} - subreqs_list: [CPSC_SUB_ELEC, CPSC_RANGE_ELECS] +const CPSC_BS_ELEC: ConcentrationRequirement = { + req_name: "ELECTIVE", + req_desc: "", + courses_required_count: 6, + courses_satisfied_count: 0, + subreqs_list: [ELEC_SUB, ELEC_MULT_BS] } -const CPSC_SENPROJ: DegreeSubrequirement = { - subreq_type_id: 1, +// SENIOR + +const SEN_PROJ: ConcentrationSubrequirement = { subreq_name: "SENIOR PROJECT", subreq_desc: "", courses_required: 1, @@ -114,17 +124,24 @@ const CPSC_SENPROJ: DegreeSubrequirement = { student_courses_satisfying: [] } -const CPSC_SENIOR: DegreeRequirement = { - req_type_id: 1, +const CPSC_SENIOR: ConcentrationRequirement = { req_name: "SENIOR", req_desc: "", - courses_required_count: 1, courses_satisfied_count: 0, + subreqs_list: [SEN_PROJ] +} + +// EXPORT - subreqs_list: [CPSC_SENPROJ] +export const CONC_CPSC_BA_I: DegreeConcentration = { + conc_name: "", + conc_desc: "", + conc_reqs: [CPSC_CORE, CPSC_BA_ELEC, CPSC_SENIOR] } -export const CPSC_CONFIG: DegreeConfiguration = { - reqs_list: [CPSC_CORE, CPSC_ELECTIVES, CPSC_SENIOR] +export const CONC_CPSC_BS_I: DegreeConcentration = { + conc_name: "", + conc_desc: "", + conc_reqs: [CPSC_CORE, CPSC_BS_ELEC, CPSC_SENIOR] } \ No newline at end of file diff --git a/frontend/src/database/programs/configs/config-econ.ts b/frontend/src/database/programs/configs/config-econ.ts new file mode 100644 index 0000000..30b1553 --- /dev/null +++ b/frontend/src/database/programs/configs/config-econ.ts @@ -0,0 +1,162 @@ + +// import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; + +// import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151 } from "../data-courses"; +// import { SC_ECON_110 } from "../data-studentcourses"; + +// // INTRO + +// const ECON_MATH: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "MATH", +// subreq_desc: "118 or 120 recommended. Any MATH 200+ satisfies.", +// courses_required: 1, +// courses_options: [null, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const ECON_INTRO_MICRO: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "INTRO MICRO", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [ECON_108, ECON_110, ECON_115], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [SC_ECON_110], +// } + +// const ECON_INTRO_MACRO: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "INTRO MACRO", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [ECON_111, ECON_116], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const ECON_INTRO: DegreeRequirement = { +// req_type_id: 1, +// req_name: "INTRO", +// req_desc: "", + +// courses_required_count: 3, +// courses_satisfied_count: 1, + +// subreqs_list: [ECON_MATH, ECON_INTRO_MICRO, ECON_INTRO_MACRO] +// } + +// // CORE + +// const ECON_CORE_MICRO: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "INTERMEDIATE MICRO", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [ECON_121, ECON_125], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const ECON_CORE_MACRO: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "INTERMEDIATE MACRO", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [ECON_122, ECON_126], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const ECON_CORE_METRICS: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "ECONOMETRICS", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [ECON_117, ECON_123, ECON_136], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const ECON_CORE: DegreeRequirement = { +// req_type_id: 1, +// req_name: "CORE", +// req_desc: "", + +// courses_required_count: 3, +// courses_satisfied_count: 0, + +// subreqs_list: [ECON_CORE_MICRO, ECON_CORE_MACRO, ECON_CORE_METRICS] +// } + +// // ELECTIVE + +// const ECON_RANGE_ELECS: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "", +// subreq_desc: "Standard elective or DUS approved extra-department substitution.", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: { dept: "CPSC", min_code: 123, max_code: 999 }, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const ECON_SUB_ELEC: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "", +// subreq_desc: "Intermediate or advanced ECON courses, traditionally numbered 123+.", +// courses_required: 3, +// courses_options: [null, null, null], +// courses_elective_range: { dept: "ECON", min_code: 123, max_code: 999 }, +// courses_any_bool: true, +// student_courses_satisfying: [] +// } + +// const ECON_ELECTIVES: DegreeRequirement = { +// req_type_id: 1, +// req_name: "ELECTIVE", +// req_desc: "", + +// courses_required_count: 4, +// courses_satisfied_count: 0, + +// subreqs_list: [ECON_SUB_ELEC, ECON_RANGE_ELECS] +// } + +// // SENIOR + +// const ECON_SEN_SUB: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "SENIOR REQUIREMENT", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: { dept: "ECON", min_code: 400, max_code: 491 }, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const ECON_SENIOR: DegreeRequirement = { +// req_type_id: 1, +// req_name: "SENIOR", +// req_desc: "", + +// courses_required_count: 2, +// courses_satisfied_count: 0, + +// subreqs_list: [ECON_SEN_SUB] +// } + +// // FINAL + +// export const ECON_CONFIG: DegreeConfiguration = { +// reqs_list: [ECON_INTRO, ECON_CORE, ECON_ELECTIVES, ECON_SENIOR] +// } diff --git a/frontend/src/database/programs/configs/config-hist.ts b/frontend/src/database/programs/configs/config-hist.ts new file mode 100644 index 0000000..5786583 --- /dev/null +++ b/frontend/src/database/programs/configs/config-hist.ts @@ -0,0 +1,273 @@ + +// import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; + +// // PRE + +// const HIST_PRE: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "PRE 1800", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const HIST_PREINDUSTRIAL: DegreeRequirement = { +// req_type_id: 1, +// req_name: "PREINDUSTRIAL", +// req_desc: "", + +// courses_required_count: 2, +// courses_satisfied_count: 0, + +// checkbox: true, +// subreqs_list: [HIST_PRE] +// } + +// // GLOBAL + +// const HIST_CORE_GLOB_AFRICA: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "AFRICA", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const HIST_CORE_GLOB_ASIA: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "ASIA", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const HIST_CORE_GLOB_EUROPE: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "EUROPE", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const HIST_CORE_GLOB_LA: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "LATIN AMERICA", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const HIST_CORE_GLOB_ME: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "MIDDLE EAST", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const HIST_CORE_GLOB_US: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "U.S.", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const HIST_GLOB_CORE: DegreeRequirement = { +// req_type_id: 1, +// req_name: "GLOBAL", +// req_desc: "", + +// courses_required_count: 5, +// courses_satisfied_count: 0, + +// subreqs_required_count: 5, +// subreqs_satisfied_count: 0, + +// subreqs_list: [HIST_CORE_GLOB_AFRICA, HIST_CORE_GLOB_ASIA, HIST_CORE_GLOB_EUROPE, HIST_CORE_GLOB_LA, HIST_CORE_GLOB_ME, HIST_CORE_GLOB_US] +// } + +// // SEMINAR + +// const HIST_DEPT_SEMINAR: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "DEPARTMENTAL", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const HIST_SEMINAR: DegreeRequirement = { +// req_type_id: 1, +// req_name: "SEMINAR", +// req_desc: "", + +// courses_required_count: 2, +// courses_satisfied_count: 0, + +// checkbox: true, +// subreqs_list: [HIST_DEPT_SEMINAR] +// } + +// // ELECTIVE + +// const HIST_ELEC_SPEC_INDIV: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "", +// subreq_desc: "", +// courses_required: 3, +// courses_options: [null, null, null, null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const HIST_ELEC_GLOB_INDIV: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "", +// subreq_desc: "", +// courses_required: 5, +// courses_options: [null, null, null, null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const HIST_ELECTIVE_SPEC: DegreeRequirement = { +// req_type_id: 1, +// req_name: "ELECTIVE", +// req_desc: "", + +// courses_required_count: 3, +// courses_satisfied_count: 0, + +// subreqs_list: [HIST_ELEC_SPEC_INDIV] +// } + +// const HIST_ELECTIVE_GLOB: DegreeRequirement = { +// req_type_id: 1, +// req_name: "ELECTIVE", +// req_desc: "", + +// courses_required_count: 5, +// courses_satisfied_count: 0, + +// subreqs_list: [HIST_ELEC_GLOB_INDIV] +// } + +// // SENIOR + +// const ECON_SEN_ONE: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "ONE TERM", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const ECON_SEN_TWO: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "TWO TERM", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const ECON_SENIOR: DegreeRequirement = { +// req_type_id: 1, +// req_name: "SENIOR", +// req_desc: "", + +// courses_required_count: 0, +// courses_satisfied_count: 0, + +// subreqs_required_count: 1, +// subreqs_satisfied_count: 0, + +// subreqs_list: [ECON_SEN_ONE, ECON_SEN_TWO] +// } + +// // FINAL + +// const HIST_SPEC_CORE_IN: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "REGION OR PATHWAY", +// subreq_desc: "", +// courses_required: 5, +// courses_options: [null, null, null, null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const HIST_SPEC_CORE_OUT: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "OUTSIDE", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const HIST_SPEC_CORE: DegreeRequirement = { +// req_type_id: 1, +// req_name: "SPECIALIST", +// req_desc: "", + +// courses_required_count: 7, +// courses_satisfied_count: 0, + +// subreqs_required_count: 2, +// subreqs_satisfied_count: 0, + +// subreqs_list: [HIST_SPEC_CORE_IN, HIST_SPEC_CORE_OUT] +// } + +// // LAST + +// export const HIST_CONFIG_BA_SPEC: DegreeConfiguration = { +// config_name: "SPECIALIST", +// reqs_list: [HIST_PREINDUSTRIAL, HIST_SEMINAR, HIST_SPEC_CORE, HIST_ELECTIVE_SPEC, ECON_SENIOR] +// } + +// export const HIST_CONFIG_BA_GLOB: DegreeConfiguration = { +// config_name: "GLOBALIST", +// reqs_list: [HIST_PREINDUSTRIAL, HIST_SEMINAR, HIST_GLOB_CORE, HIST_ELECTIVE_GLOB, ECON_SENIOR] +// } + +// export const HIST_CONFIG_BA: DegreeConfiguration = { +// config_name: "GLOBALIST", +// reqs_list: [HIST_PREINDUSTRIAL, HIST_SEMINAR, HIST_GLOB_CORE, HIST_ELECTIVE_GLOB, ECON_SENIOR] +// } \ No newline at end of file diff --git a/frontend/src/database/programs/configs/config-plsc.ts b/frontend/src/database/programs/configs/config-plsc.ts new file mode 100644 index 0000000..1649d77 --- /dev/null +++ b/frontend/src/database/programs/configs/config-plsc.ts @@ -0,0 +1,201 @@ + +// import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; + +// // INTRO + +// const PLSC_INTRO_TWO: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "INTRO COURSES", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const PLSC_INTRO: DegreeRequirement = { +// req_type_id: 1, +// req_name: "INTRO", +// req_desc: "", + +// courses_required_count: 3, +// courses_satisfied_count: 1, + +// subreqs_list: [PLSC_INTRO_TWO] +// } + +// // CORE + +// const PLSC_CORE_LECTURES: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "CORE LECTURES", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const PLSC_CORE_METHODS: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "METHODS AND FORMAL THEORY", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const PLSC_CORE: DegreeRequirement = { +// req_type_id: 1, +// req_name: "CORE", +// req_desc: "", + +// courses_required_count: 3, +// courses_satisfied_count: 0, + +// subreqs_list: [PLSC_CORE_LECTURES, PLSC_CORE_METHODS] +// } + +// // ELECTIVE + +// const PLSC_SUB_INTL: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "INTERNATIONAL RELATIONS", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const PLSC_SUB_US: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "AMERICAN GOVERNMENT", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const PLSC_SUB_PHIL: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "POLITICAL PHILOSOPHY", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const PLSC_SUB_COMP: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "COMPARATIVE POLITICS", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const PLSC_SUBFIELDS: DegreeRequirement = { +// req_type_id: 1, +// req_name: "SUBFIELDS", +// req_desc: "", + +// courses_required_count: 4, +// courses_satisfied_count: 0, + +// subreqs_required_count: 2, +// subreqs_satisfied_count: 0, + +// subreqs_list: [PLSC_SUB_INTL, PLSC_SUB_US, PLSC_SUB_PHIL, PLSC_SUB_COMP] +// } + +// // SEMINAR + +// const PLSC_SEM_ANY: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "YEAR ANY", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const PLSC_SEM_SEN: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "YEAR SENIOR", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const PLSC_SEMINAR: DegreeRequirement = { +// req_type_id: 1, +// req_name: "SEMINAR", +// req_desc: "Seminar courses taught by PLSC faculty satisfy.", + +// courses_required_count: 2, +// courses_satisfied_count: 0, + +// checkbox: true, +// subreqs_list: [PLSC_SEM_ANY, PLSC_SEM_SEN] +// } + +// // SENIOR + +// const PLSC_SEN_ONE: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "ONE TERM", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const PLSC_SEN_TWO: DegreeSubrequirement = { +// subreq_type_id: 1, +// subreq_name: "TWO TERM", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const PLSC_SENIOR: DegreeRequirement = { +// req_type_id: 1, +// req_name: "SENIOR", +// req_desc: "", + +// courses_required_count: 0, +// courses_satisfied_count: 0, + +// subreqs_required_count: 1, +// subreqs_satisfied_count: 0, + +// subreqs_list: [PLSC_SEN_ONE, PLSC_SEN_TWO] +// } + +// // FINAL + +// export const PLSC_CONFIG: DegreeConfiguration = { +// reqs_list: [PLSC_INTRO, PLSC_CORE, PLSC_SUBFIELDS, PLSC_SEMINAR, PLSC_SENIOR] +// } diff --git a/frontend/src/database/programs/data-program.ts b/frontend/src/database/programs/data-program.ts new file mode 100644 index 0000000..3cf4a92 --- /dev/null +++ b/frontend/src/database/programs/data-program.ts @@ -0,0 +1,73 @@ + +import { Program } from "@/types/type-program"; +import { CONC_CPSC_BA_I, CONC_CPSC_BS_I } from "./concs-cpsc"; + +export const PROG_CPSC: Program = { + prog_data: { + prog_name: "Computer Science", + prog_abbr: "CPSC", + prog_stud_count: 0, + prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, + prog_catolog: "", + prog_website: "" + }, + prog_degs: [ + { deg_type: "B.A.", deg_concs: [CONC_CPSC_BA_I] }, + { deg_type: "B.S.", deg_concs: [CONC_CPSC_BS_I] } + ] +} + +export const PROG_ECON: Program = { + prog_data: { + prog_name: "Economics", + prog_abbr: "ECON", + prog_stud_count: 0, + prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, + prog_catolog: "", + prog_website: "" + }, + prog_degs: [ + { + deg_type: "B.A.", + deg_concs: [] + }, + { + deg_type: "B.S.", + deg_concs: [] + } + ] +} + +export const PROG_PLSC: Program = { + prog_data: { + prog_name: "Political Science", + prog_abbr: "PLSC", + prog_stud_count: 0, + prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, + prog_catolog: "", + prog_website: "" + }, + prog_degs: [ + { + deg_type: "B.A.", + deg_concs: [] + } + ] +} + +export const PROG_HIST: Program = { + prog_data: { + prog_name: "History", + prog_abbr: "HIST", + prog_stud_count: 0, + prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, + prog_catolog: "", + prog_website: "" + }, + prog_degs: [ + { + deg_type: "B.A.", + deg_concs: [] + } + ] +} diff --git a/frontend/src/database/programs/metas/meta-econ.ts b/frontend/src/database/programs/metas/meta-econ.ts new file mode 100644 index 0000000..2518fc1 --- /dev/null +++ b/frontend/src/database/programs/metas/meta-econ.ts @@ -0,0 +1,17 @@ + +// import { ConcMetadata, DegreeMetadata } from "@/types/type-program"; + +// const CONC_ECON_GEN: ConcMetadata = { +// conc_name: "", +// stats: { courses: 0, rating: 0, type: "So", workload: 0 }, +// about: "The Economics major provides a rigorous framework for understanding financial markets, public policy, and decision-making. Students learn analytical techniques in microeconomics, macroeconomics, and econometrics, developing critical skills in data interpretation and policy evaluation. The major offers pathways in finance, economic development, and quantitative analysis, preparing students for careers in government, consulting, business, and research.", +// } + +// export const META_ECON_BA: DegreeMetadata = { +// name: "Economics", +// abbr: "ECON", +// degreeType: "B.A.", + +// concs: [CONC_ECON_GEN], +// info: { students: 0, dus: { address: "28 Hillhouse Ave, (203) 432-3576", email: "economics@yale.edu", name: "Dirk Bergemann" }, wesbiteLink: "https://economics.yale.edu", catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/economics/" } +// } diff --git a/frontend/src/database/programs/metas/meta-hist.ts b/frontend/src/database/programs/metas/meta-hist.ts new file mode 100644 index 0000000..8b492e1 --- /dev/null +++ b/frontend/src/database/programs/metas/meta-hist.ts @@ -0,0 +1,23 @@ + +// import { ConcMetadata, DegreeMetadata } from "@/types/type-program"; + +// const META_HIST_GLOB: ConcMetadata = { +// conc_name: "Globalist", +// stats: { courses: 0, rating: 0, type: "Hu", workload: 0 }, +// about: "The History major offers a broad exploration of human experience across time, cultures, and societies. Students gain expertise in archival research, critical analysis, and historical interpretation. The major prepares students to engage with contemporary issues by understanding their historical contexts, whether in politics, economics, or cultural transformations. History students learn to construct arguments based on evidence and develop a strong capacity for analytical reasoning and effective communication.", +// } + +// const META_HIST_SPEC: ConcMetadata = { +// conc_name: "Specialist", +// stats: { courses: 0, rating: 0, type: "Hu", workload: 0 }, +// about: "The History major offers a broad exploration of human experience across time, cultures, and societies. Students gain expertise in archival research, critical analysis, and historical interpretation. The major prepares students to engage with contemporary issues by understanding their historical contexts, whether in politics, economics, or cultural transformations. History students learn to construct arguments based on evidence and develop a strong capacity for analytical reasoning and effective communication.", +// } + +// export const META_HIST_BA: DegreeMetadata = { +// name: "History", +// abbr: "HIST", +// degreeType: "B.A.", + +// concs: [META_HIST_GLOB, META_HIST_SPEC], +// info: { students: 0, dus: { address: "HQ 211, (203) 432-1366", email: "history@yale.edu", name: "Alan Mikhail" }, wesbiteLink: "https://history.yale.edu", catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/history/" } +// } \ No newline at end of file diff --git a/frontend/src/database/programs/metas/meta-plsc.ts b/frontend/src/database/programs/metas/meta-plsc.ts new file mode 100644 index 0000000..9ea7064 --- /dev/null +++ b/frontend/src/database/programs/metas/meta-plsc.ts @@ -0,0 +1,17 @@ + +// import { ConcMetadata, DegreeMetadata } from "@/types/type-program"; + +// const META_PLSC: ConcMetadata = { +// conc_name: "", +// stats: { courses: 0, rating: 0, type: "Hu", workload: 0 }, +// about: "The Political Science major examines governments, political behavior, and institutional structures at local, national, and global levels. Students engage with the theoretical foundations of politics, quantitative and qualitative methodologies, and policy analysis. The program prepares students for careers in law, public policy, international relations, and academia while offering flexibility to explore subfields such as comparative politics, American politics, and political philosophy.", +// } + +// export const META_PLSC_BA: DegreeMetadata = { +// name: "Political Science", +// abbr: "PLSC", +// degreeType: "B.A.", + +// concs: [META_PLSC], +// info: { students: 0, dus: { address: "Rosenkranz Hall 130, (203) 432-5248", email: "politicalscience@yale.edu", name: "Isabela Mares" }, wesbiteLink: "https://politicalscience.yale.edu", catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/history/" } +// } diff --git a/frontend/src/types/type-program.ts b/frontend/src/types/type-program.ts index 59c1bf4..a18486a 100644 --- a/frontend/src/types/type-program.ts +++ b/frontend/src/types/type-program.ts @@ -7,33 +7,6 @@ export interface StudentDegree { degreeIndex: number; } -interface DUS { - name: string; - address: string; - email: string; -} - -interface DegreeMetadataStats { - courses: number; - rating: number; - workload: number; - type: string; -} - -export interface DegreeMetadata { - name: string; - abbr: string; - degreeType: string; - stats: DegreeMetadataStats; - students: number; - about: string; - dus: DUS; - catologLink: string; - wesbiteLink: string; -} - -// \BEGIN{MAJOR MAGIC} - export interface ElectiveRange { dept: string; min_code: number; @@ -42,9 +15,7 @@ export interface ElectiveRange { export type SubreqElectiveRange = ElectiveRange| null; -// subreq: 1 | set options -export interface DegreeSubrequirement { - subreq_type_id: number; +export interface ConcentrationSubrequirement { subreq_name: string; subreq_desc: string; @@ -56,8 +27,7 @@ export interface DegreeSubrequirement { student_courses_satisfying: StudentCourse[]; } -export interface DegreeRequirement { - req_type_id: number; +export interface ConcentrationRequirement { req_name: string; req_desc: string; @@ -68,27 +38,36 @@ export interface DegreeRequirement { subreqs_satisfied_count?: number; checkbox?: boolean; - subreqs_list: DegreeSubrequirement[]; + subreqs_list: ConcentrationSubrequirement[]; } -// export interface DegreeRequirementTypeTwo { -// req_type_id: number; -// req_name: string; -// req_desc: string; - -// checkbox_bool: boolean; -// user_courses_satisfying: Course[]; -// } +export interface DegreeConcentration { + conc_name: string; + conc_desc: string; + conc_reqs: ConcentrationRequirement[]; +} -// export type DegreeRequirement = DegreeRequirementTypeOne | DegreeRequirementTypeTwo; +export interface ProgramDegree { + deg_type: string; + deg_concs: DegreeConcentration[]; +} -export interface DegreeConfiguration { - reqs_list: DegreeRequirement[]; +interface ProgDUS { + dus_name: string; + dus_email: string; + dus_address: string; } -export interface Degree { - metadata: DegreeMetadata; - configuration: DegreeConfiguration; +interface ProgramMetadata { + prog_name: string; + prog_abbr: string; + prog_stud_count: number; + prog_dus: ProgDUS; + prog_catolog: string; + prog_website: string; } -// \END{MAJOR MAGIC} +export interface Program { + prog_data: ProgramMetadata; + prog_degs: ProgramDegree[]; +} diff --git a/frontend/src/types/type-user.ts b/frontend/src/types/type-user.ts index ce21d91..85f4a23 100644 --- a/frontend/src/types/type-user.ts +++ b/frontend/src/types/type-user.ts @@ -1,5 +1,5 @@ -import { DegreeConfiguration, StudentDegree } from "./type-program"; +import { Program, StudentDegree } from "./type-program"; export interface LanguagePlacement { language: string; @@ -42,8 +42,8 @@ export interface FYP { languagePlacement: LanguagePlacement; studentCourses: StudentCourse[]; studentTermArrangement: StudentTermArrangement; - degreeConfigurations: DegreeConfiguration[][]; - degreeDeclarations: StudentDegree[]; + programs: Program[]; + declarations: StudentDegree[]; } export interface User { From 117400f28a9a9ba25af30fe58464206595b0c2db Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Mon, 10 Mar 2025 18:26:55 -0700 Subject: [PATCH 17/38] programs restored --- frontend/src/app/majors/metadata/Metadata.tsx | 189 ++++++------ frontend/src/app/majors/page.tsx | 19 +- frontend/src/database/data-courses.ts | 8 +- frontend/src/database/data-user.ts | 4 +- .../programs/{ => concs}/concs-cpsc.ts | 12 +- .../src/database/programs/concs/concs-econ.ts | 143 +++++++++ .../src/database/programs/concs/concs-hist.ts | 233 +++++++++++++++ .../src/database/programs/concs/concs-plsc.ts | 220 ++++++++++++++ .../database/programs/configs/config-econ.ts | 162 ----------- .../database/programs/configs/config-hist.ts | 273 ------------------ .../database/programs/configs/config-plsc.ts | 201 ------------- .../src/database/programs/data-program.ts | 21 +- .../src/database/programs/metas/meta-econ.ts | 17 -- .../src/database/programs/metas/meta-hist.ts | 23 -- .../src/database/programs/metas/meta-plsc.ts | 17 -- frontend/src/types/type-program.ts | 6 + 16 files changed, 717 insertions(+), 831 deletions(-) rename frontend/src/database/programs/{ => concs}/concs-cpsc.ts (93%) create mode 100644 frontend/src/database/programs/concs/concs-econ.ts create mode 100644 frontend/src/database/programs/concs/concs-hist.ts create mode 100644 frontend/src/database/programs/concs/concs-plsc.ts delete mode 100644 frontend/src/database/programs/configs/config-econ.ts delete mode 100644 frontend/src/database/programs/configs/config-hist.ts delete mode 100644 frontend/src/database/programs/configs/config-plsc.ts delete mode 100644 frontend/src/database/programs/metas/meta-econ.ts delete mode 100644 frontend/src/database/programs/metas/meta-hist.ts delete mode 100644 frontend/src/database/programs/metas/meta-plsc.ts diff --git a/frontend/src/app/majors/metadata/Metadata.tsx b/frontend/src/app/majors/metadata/Metadata.tsx index 33f0395..2c2904e 100644 --- a/frontend/src/app/majors/metadata/Metadata.tsx +++ b/frontend/src/app/majors/metadata/Metadata.tsx @@ -3,129 +3,129 @@ import { useState, useEffect } from "react"; import Link from 'next/link'; import { User } from "@/types/type-user"; -import { Program } from "@/types/type-program"; +import { MajorsIndex, Program } from "@/types/type-program"; import { pinProgram, addProgram } from "./MetadataUtils"; import Style from "./Metadata.module.css"; -function MetadataTopshelf(props: { - user: User, - setUser: Function, - programIndex: number, - degreeMetadata: DegreeMetadata -}) { +function MetadataTopshelf(props: { program: Program }) +{ return (
-
pinProgram(props.programIndex, 0, props.user, props.setUser)}> + {/*
pinProgram(props.programIndex, 0, props.user, props.setUser)}>
addProgram(props.programIndex, 0, props.user, props.setUser)}> -
+
*/}
-
{props.degreeMetadata.name}
+
+ {props.program.prog_data.prog_name} +
- {props.degreeMetadata.info.students} + {props.program.prog_data.prog_stud_count}
MAJOR
- {props.degreeMetadata.abbr} + {props.program.prog_data.prog_abbr}
); } -function MetadataStats(props: { concMetadata: ConcMetadata }){ - return( -
-
- STATS -
-
-
-
- COURSES -
-
- {props.concMetadata.stats.courses} -
-
-
-
- RATING -
-
{props.concMetadata.stats.rating}
-
-
-
- WORKLOAD -
-
{props.concMetadata.stats.workload}
-
-
-
- TYPE -
-
{props.concMetadata.stats.type}
-
-
-
- ); -} - -function MetadataBody(props: { concIndex: number, degreeMetadata: DegreeMetadata }){ - - const { concIndex, degreeMetadata } = props; +// function MetadataStats(props: { concMetadata: ConcMetadata }){ +// return( +//
+//
+// STATS +//
+//
+//
+//
+// COURSES +//
+//
+// {props.concMetadata.stats.courses} +//
+//
+//
+//
+// RATING +//
+//
{props.concMetadata.stats.rating}
+//
+//
+//
+// WORKLOAD +//
+//
{props.concMetadata.stats.workload}
+//
+//
+//
+// TYPE +//
+//
{props.concMetadata.stats.type}
+//
+//
+//
+// ); +// } + +function MetadataBody(props: { program: Program, index: MajorsIndex }){ return(
- + {/* */}
ABOUT
- {degreeMetadata.concs[concIndex].about} + {props.program.prog_degs[props.index.deg].deg_concs[props.index.conc].conc_desc}
DUS
- {degreeMetadata.info.dus.name}; {degreeMetadata.info.dus.address} + {props.program.prog_data.prog_dus.dus_name}; {props.program.prog_data.prog_dus.dus_email}
-
MAJOR CATALOG
-
MAJOR WEBSITE
+
MAJOR CATALOG
+
MAJOR WEBSITE
); } -function MetadataToggle(props: { degreeMetadatas: DegreeMetadata[], degreeIndex: number, setDegreeIndex: Function, concIndex: number, setConcIndex: Function }){ - // TODO - // If the currently selected degreeMetadata's concs attribute has length greater than 1, then - // display an additional set of identical toggle buttons below the current set where - // each divs content is now conc.conc_name and clicking on one calls setConcIndex for the newly clicked one. - +function MetadataToggle(props: { program: Program, index: MajorsIndex, setIndex: Function }) +{ return (
- {props.degreeMetadatas.map((metadata, index) => ( -
props.setDegreeIndex(index)}> - {metadata.degreeType} + {props.program.prog_degs.map((deg, index) => ( +
props.setIndex({ prog: props.index.prog, deg: index, conc: props.index.conc })} + > + {deg.deg_type}
))}
- {props.degreeMetadatas[props.degreeIndex].concs.length > 1 && ( + {props.program.prog_degs[props.index.deg].deg_concs.length > 1 && (
- {props.degreeMetadatas[props.degreeIndex].concs.map((conc, index) => ( -
props.setConcIndex(index)}> + {props.program.prog_degs[props.index.deg].deg_concs.map((conc, index) => ( +
props.setIndex({ prog: props.index.prog, deg: props.index.deg, conc: index })} + > {conc.conc_name}
))} @@ -136,42 +136,28 @@ function MetadataToggle(props: { degreeMetadatas: DegreeMetadata[], degreeIndex: ); } -function MetadataContent(props: { - user: User, - setUser: Function, - programIndex: number, - degreeMetadatas: DegreeMetadata[], - degreeIndex: number, - setDegreeIndex: Function - concIndex: number, - setConcIndex: Function -}){ +function MetadataContent(props: { program: Program, index: MajorsIndex, setIndex: Function }) +{ return (
- - - + + +
); } -function MetadataScrollButton(props: { shiftProgramIndex: Function; peekProgram: Function; dir: number }) +function MetadataScrollButton(props: { index: MajorsIndex, setIndex: Function; peekProgram: Function; dir: number }) { return ( -
props.shiftProgramIndex(props.dir)}> +
props.setIndex({ conc: 0, deg: 0, prog: props.index.prog + props.dir })}>
- {props.peekProgram(props.dir).name} + {props.peekProgram(props.dir).prog_name}
- {props.peekProgram(props.dir).abbr} + {props.peekProgram(props.dir).prog_abbr}
@@ -180,20 +166,13 @@ function MetadataScrollButton(props: { shiftProgramIndex: Function; peekProgram: } -function Metadata(props: { - program: Program, - index: { conc: number, deg: number, prog: number } - peekProgram: Function -}) { +function Metadata(props: { program: Program, index: MajorsIndex, setIndex: Function, peekProgram: Function}) +{ return (
- - - + + +
); } diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index ff77375..35e197e 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -2,6 +2,7 @@ "use client"; import { useState } from "react"; import { useAuth } from "../providers"; +import { MajorsIndex } from "@/types/type-program"; import Style from "./Majors.module.css"; import NavBar from "@/components/navbar/NavBar"; @@ -11,16 +12,16 @@ import Requirements from "./requirements/Requirements"; function Majors() { - const { user, setUser } = useAuth(); - const { programs } = user.FYP.programs; + const { user } = useAuth(); - const [index, setIndex] = useState({ conc: 0, deg: 0, prog: 0}); - const updateIndex: Function = (index: { conc: number, deg: number, prog: number}) => { - setIndex(index) + const [index, setIndex] = useState({ conc: 0, deg: 0, prog: 0}); + const updateIndex: Function = (index: MajorsIndex) => { + index.prog = index.prog % user.FYP.programs.length; + setIndex(index); }; const peekProgram = (dir: number) => { - return programs[(index.prog + dir + programs.length) % programs.length]; + return user.FYP.programs[(index.prog + dir + user.FYP.programs.length) % user.FYP.programs.length].prog_data; }; return( @@ -29,15 +30,13 @@ function Majors()
- +
); diff --git a/frontend/src/database/data-courses.ts b/frontend/src/database/data-courses.ts index 71e83a5..5939794 100644 --- a/frontend/src/database/data-courses.ts +++ b/frontend/src/database/data-courses.ts @@ -1,9 +1,15 @@ import { Course } from "@/types/type-user" -// HSAR ONE +// HSAR PROGRAM export const HSAR_401: Course = { codes: ["HSAR 401"], title: "Critical Approaches To Art History", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// PLSC PROGRAM +export const PLSC_474: Course = { codes: ["PSLC 474"], title: "", credit: 1, dist: ["So"], seasons: ["Spring"]} +export const PLSC_490: Course = { codes: ["PSLC 490"], title: "", credit: 1, dist: ["So"], seasons: ["Fall", "Spring"]} +export const PLSC_493: Course = { codes: ["PSLC 493"], title: "", credit: 1, dist: ["So"], seasons: ["Fall", "Spring"]} + + // CPSC PROGRAM export const CPSC_201: Course = { codes: ["CPSC 201"], title: "Introduction To Computer Science", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } export const CPSC_202: Course = { codes: ["CPSC 202"], title: "Math Tools For Computer Scientists", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index e2d5c5e..bf2f7f3 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -1,6 +1,6 @@ import { User } from "./../types/type-user"; -import { PROG_CPSC } from "./programs/data-program"; +import { PROG_CPSC, PROG_ECON, PROG_HIST, PROG_PLSC } from "./programs/data-program"; export const Ryan: User = { name: "Ryan", @@ -15,7 +15,7 @@ export const Ryan: User = { senior: [0, 202703, 202801], }, languagePlacement: { language: "Spanish", level: 5 }, - programs: [PROG_CPSC], + programs: [PROG_CPSC, PROG_ECON, PROG_HIST, PROG_PLSC], declarations: [], } } diff --git a/frontend/src/database/programs/concs-cpsc.ts b/frontend/src/database/programs/concs/concs-cpsc.ts similarity index 93% rename from frontend/src/database/programs/concs-cpsc.ts rename to frontend/src/database/programs/concs/concs-cpsc.ts index 601e08c..45651e9 100644 --- a/frontend/src/database/programs/concs-cpsc.ts +++ b/frontend/src/database/programs/concs/concs-cpsc.ts @@ -1,8 +1,8 @@ import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; -import { CPSC_201, CPSC_202, MATH_244, CPSC_223, CPSC_323, CPSC_365, CPSC_366, CPSC_490 } from "./../data-courses"; -import { SC_CPSC_201, SC_CPSC_223, SC_CPSC_323 } from "./../data-studentcourses"; +import { CPSC_201, CPSC_202, MATH_244, CPSC_223, CPSC_323, CPSC_365, CPSC_366, CPSC_490 } from "../../data-courses"; +import { SC_CPSC_201, SC_CPSC_223, SC_CPSC_323 } from "../../data-studentcourses"; // CORE @@ -69,8 +69,8 @@ const CPSC_CORE: ConcentrationRequirement = { const ELEC_MULT_BA: ConcentrationSubrequirement = { subreq_name: "", subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", - courses_required: 4, - courses_options: [null, null, null, null], + courses_required: 3, + courses_options: [null, null, null], courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, courses_any_bool: false, student_courses_satisfying: [] @@ -79,8 +79,8 @@ const ELEC_MULT_BA: ConcentrationSubrequirement = { const ELEC_MULT_BS: ConcentrationSubrequirement = { subreq_name: "", subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", - courses_required: 6, - courses_options: [null, null, null, null, null, null], + courses_required: 5, + courses_options: [null, null, null, null, null], courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, courses_any_bool: false, student_courses_satisfying: [] diff --git a/frontend/src/database/programs/concs/concs-econ.ts b/frontend/src/database/programs/concs/concs-econ.ts new file mode 100644 index 0000000..7f5df20 --- /dev/null +++ b/frontend/src/database/programs/concs/concs-econ.ts @@ -0,0 +1,143 @@ + +import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; + +import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151 } from "./../../data-courses"; +import { SC_ECON_110 } from "./../../data-studentcourses"; + +// INTRO + +const INTRO_MATH: ConcentrationSubrequirement = { + subreq_name: "MATH", + subreq_desc: "118 or 120 recommended. Any MATH 200+ satisfies.", + courses_required: 1, + courses_options: [MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const INTRO_MICRO: ConcentrationSubrequirement = { + subreq_name: "INTRO MICRO", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_108, ECON_110, ECON_115], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [SC_ECON_110], +} + +const INTRO_MACRO: ConcentrationSubrequirement = { + subreq_name: "INTRO MACRO", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_111, ECON_116], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const ECON_INTRO: ConcentrationRequirement = { + req_name: "INTRO", + req_desc: "", + courses_required_count: 3, + courses_satisfied_count: 1, + subreqs_list: [INTRO_MATH, INTRO_MICRO, INTRO_MACRO] +} + +// CORE + +const CORE_MICRO: ConcentrationSubrequirement = { + subreq_name: "INTERMEDIATE MICRO", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_121, ECON_125], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const CORE_MACRO: ConcentrationSubrequirement = { + subreq_name: "INTERMEDIATE MACRO", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_122, ECON_126], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const CORE_METRICS: ConcentrationSubrequirement = { + subreq_name: "ECONOMETRICS", + subreq_desc: "", + courses_required: 1, + courses_options: [ECON_117, ECON_123, ECON_136], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const ECON_CORE: ConcentrationRequirement = { + req_name: "CORE", + req_desc: "", + courses_required_count: 3, + courses_satisfied_count: 0, + subreqs_list: [CORE_MICRO, CORE_MACRO, CORE_METRICS] +} + +// ELECTIVE + +const ELEC_STAN: ConcentrationSubrequirement = { + subreq_name: "", + subreq_desc: "Standard elective or DUS approved extra-department substitution.", + courses_required: 1, + courses_options: [null], + courses_elective_range: { dept: "CPSC", min_code: 123, max_code: 999 }, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const ELEC_SUB: ConcentrationSubrequirement = { + subreq_name: "", + subreq_desc: "Intermediate or advanced ECON courses, traditionally numbered 123+.", + courses_required: 3, + courses_options: [null, null, null], + courses_elective_range: { dept: "ECON", min_code: 123, max_code: 999 }, + courses_any_bool: true, + student_courses_satisfying: [] +} + +const ECON_ELECTIVE: ConcentrationRequirement = { + req_name: "ELECTIVE", + req_desc: "", + courses_required_count: 4, + courses_satisfied_count: 0, + subreqs_list: [ELEC_STAN, ELEC_SUB] +} + +// SENIOR + +const SEN_REQ: ConcentrationSubrequirement = { + subreq_name: "SENIOR REQUIREMENT", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: { dept: "ECON", min_code: 400, max_code: 491 }, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const ECON_SEN: ConcentrationRequirement = { + req_name: "SENIOR", + req_desc: "", + courses_required_count: 2, + courses_satisfied_count: 0, + subreqs_list: [SEN_REQ] +} + +// // FINAL + +export const CONC_ECON_BA_I: DegreeConcentration = { + conc_name: "", + conc_desc: "", + conc_reqs: [ECON_INTRO, ECON_CORE, ECON_ELECTIVE, ECON_SEN] +} diff --git a/frontend/src/database/programs/concs/concs-hist.ts b/frontend/src/database/programs/concs/concs-hist.ts new file mode 100644 index 0000000..1cfa69a --- /dev/null +++ b/frontend/src/database/programs/concs/concs-hist.ts @@ -0,0 +1,233 @@ +import { ConcentrationRequirement, ConcentrationSubrequirement, DegreeConcentration } from "@/types/type-program"; + +// PRE + +const PRE_REQ: ConcentrationSubrequirement = { + subreq_name: "PRE 1800", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const HIST_PRE: ConcentrationRequirement = { + req_name: "PREINDUSTRIAL", + req_desc: "", + courses_required_count: 2, + courses_satisfied_count: 0, + checkbox: true, + subreqs_list: [PRE_REQ] +} + +// GLOB CORE + +const GLOB_CORE_AFRICA: ConcentrationSubrequirement = { + subreq_name: "AFRICA", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const GLOB_CORE_ASIA: ConcentrationSubrequirement = { + subreq_name: "ASIA", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const GLOB_CORE_EURO: ConcentrationSubrequirement = { + subreq_name: "EUROPE", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const GLOB_CORE_LA: ConcentrationSubrequirement = { + subreq_name: "LATIN AMERICA", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const GLOB_CORE_ME: ConcentrationSubrequirement = { + subreq_name: "MIDDLE EAST", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const GLOB_CORE_US: ConcentrationSubrequirement = { + subreq_name: "U.S.", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const HIST_GLOB_CORE: ConcentrationRequirement = { + req_name: "GLOBAL", + req_desc: "", + courses_required_count: 5, + courses_satisfied_count: 0, + subreqs_required_count: 5, + subreqs_satisfied_count: 0, + subreqs_list: [GLOB_CORE_AFRICA, GLOB_CORE_AFRICA, GLOB_CORE_EURO, GLOB_CORE_LA, GLOB_CORE_ME, GLOB_CORE_US] +} + +// SPEC CORE + +const SPEC_CORE_IN: ConcentrationSubrequirement = { + subreq_name: "REGION OR PATHWAY", + subreq_desc: "", + courses_required: 5, + courses_options: [null, null, null, null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const SPEC_CORE_OUT: ConcentrationSubrequirement = { + subreq_name: "OUTSIDE", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const HIST_SPEC_CORE: ConcentrationRequirement = { + req_name: "SPECIALIST", + req_desc: "", + courses_required_count: 7, + courses_satisfied_count: 0, + subreqs_required_count: 2, + subreqs_satisfied_count: 0, + subreqs_list: [SPEC_CORE_IN, SPEC_CORE_OUT] +} + +// SEMINAR + +const SEM_REQ: ConcentrationSubrequirement = { + subreq_name: "DEPARTMENTAL", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const HIST_SEM: ConcentrationRequirement = { + req_name: "SEMINAR", + req_desc: "", + courses_required_count: 2, + courses_satisfied_count: 0, + checkbox: true, + subreqs_list: [SEM_REQ] +} + +// GLOB ELEC + +const GLOB_ELEC_REQ: ConcentrationSubrequirement = { + subreq_name: "", + subreq_desc: "", + courses_required: 5, + courses_options: [null, null, null, null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const HIST_GLOB_ELEC: ConcentrationRequirement = { + req_name: "ELECTIVE", + req_desc: "", + courses_required_count: 5, + courses_satisfied_count: 0, + subreqs_list: [GLOB_ELEC_REQ] +} + +// SPEC ELEC + +const SPEC_ELEC_REQ: ConcentrationSubrequirement = { + subreq_name: "", + subreq_desc: "", + courses_required: 3, + courses_options: [null, null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const HIST_SPEC_ELEC: ConcentrationRequirement = { + req_name: "ELECTIVE", + req_desc: "", + courses_required_count: 3, + courses_satisfied_count: 0, + subreqs_list: [SPEC_ELEC_REQ] +} + +// SENIOR + +const SEN_ONE: ConcentrationSubrequirement = { + subreq_name: "ONE TERM", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const SEN_TWO: ConcentrationSubrequirement = { + subreq_name: "TWO TERM", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const HIST_SEN: ConcentrationRequirement = { + req_name: "SENIOR", + req_desc: "", + courses_required_count: 0, + courses_satisfied_count: 0, + subreqs_required_count: 1, + subreqs_satisfied_count: 0, + subreqs_list: [SEN_ONE, SEN_TWO] +} + +// EXPORT + +export const CONC_HIST_BA_GLOB: DegreeConcentration = { + conc_name: "GLOBALIST", + conc_desc: "", + conc_reqs: [HIST_PRE, HIST_GLOB_CORE, HIST_SEM, HIST_GLOB_ELEC, HIST_SEN] +} + +export const CONC_HIST_BA_SPEC: DegreeConcentration = { + conc_name: "SPECIALIST", + conc_desc: "", + conc_reqs: [HIST_PRE, HIST_SPEC_CORE, HIST_SEM, HIST_SPEC_ELEC, HIST_SEN] +} diff --git a/frontend/src/database/programs/concs/concs-plsc.ts b/frontend/src/database/programs/concs/concs-plsc.ts new file mode 100644 index 0000000..422947f --- /dev/null +++ b/frontend/src/database/programs/concs/concs-plsc.ts @@ -0,0 +1,220 @@ + +import { DegreeConcentration, ConcentrationRequirement, ConcentrationSubrequirement } from "@/types/type-program"; +import { PLSC_474, PLSC_490, PLSC_493 } from "@/database/data-courses"; + +// INTRO + +const INTRO_REQ: ConcentrationSubrequirement = { + subreq_name: "INTRO COURSES", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const PLSC_INTRO: ConcentrationRequirement = { + req_name: "INTRO", + req_desc: "", + courses_required_count: 3, + courses_satisfied_count: 1, + subreqs_list: [INTRO_REQ] +} + +// CORE + +const CORE_LECT: ConcentrationSubrequirement = { + subreq_name: "CORE LECTURES", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const CORE_METH: ConcentrationSubrequirement = { + subreq_name: "METHODS AND FORMAL THEORY", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const PLSC_CORE_STAN: ConcentrationRequirement = { + req_name: "CORE", + req_desc: "", + courses_required_count: 3, + courses_satisfied_count: 0, + subreqs_list: [CORE_LECT, CORE_METH] +} + +const CORE_RESE: ConcentrationSubrequirement = { + subreq_name: "RESEARCH", + subreq_desc: "", + courses_required: 1, + courses_options: [PLSC_474], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [], +} + +const PLSC_CORE_INTE: ConcentrationRequirement = { + req_name: "CORE", + req_desc: "", + courses_required_count: 4, + courses_satisfied_count: 0, + subreqs_list: [CORE_LECT, CORE_METH, CORE_RESE] +} + +// ELECTIVE + +const SUB_INTL: ConcentrationSubrequirement = { + subreq_name: "INTERNATIONAL RELATIONS", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const SUB_US: ConcentrationSubrequirement = { + subreq_name: "AMERICAN GOVERNMENT", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const SUB_PHIL: ConcentrationSubrequirement = { + subreq_name: "POLITICAL PHILOSOPHY", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const SUB_COMP: ConcentrationSubrequirement = { + subreq_name: "COMPARATIVE POLITICS", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const PLSC_SUB: ConcentrationRequirement = { + req_name: "SUBFIELDS", + req_desc: "", + courses_required_count: 4, + courses_satisfied_count: 0, + subreqs_required_count: 2, + subreqs_satisfied_count: 0, + subreqs_list: [SUB_INTL, SUB_US, SUB_PHIL, SUB_COMP] +} + +// SEMINAR + +const SEM_ANY: ConcentrationSubrequirement = { + subreq_name: "YEAR ANY", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const SEM_SEN: ConcentrationSubrequirement = { + subreq_name: "YEAR SENIOR", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const PLSC_SEMINAR: ConcentrationRequirement = { + req_name: "SEMINAR", + req_desc: "Seminar courses taught by PLSC faculty satisfy.", + courses_required_count: 2, + courses_satisfied_count: 0, + checkbox: true, + subreqs_list: [SEM_ANY, SEM_SEN] +} + +// SEN STANDARD + +const SEN_STAN_ONE: ConcentrationSubrequirement = { + subreq_name: "ONE TERM", + subreq_desc: "", + courses_required: 1, + courses_options: [null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const SEN_STAN_TWO: ConcentrationSubrequirement = { + subreq_name: "TWO TERM", + subreq_desc: "", + courses_required: 2, + courses_options: [null, null], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const PLSC_SEN_STAN: ConcentrationRequirement = { + req_name: "SENIOR", + req_desc: "", + courses_required_count: 0, + courses_satisfied_count: 0, + subreqs_required_count: 1, + subreqs_satisfied_count: 0, + subreqs_list: [SEN_STAN_ONE, SEN_STAN_TWO] +} + +// SEN INTENSIVE + +const SEN_INTE_REQ: ConcentrationSubrequirement = { + subreq_name: "TWO TERM", + subreq_desc: "", + courses_required: 2, + courses_options: [PLSC_490, PLSC_493], + courses_elective_range: null, + courses_any_bool: false, + student_courses_satisfying: [] +} + +const PLSC_SEN_INTE: ConcentrationRequirement = { + req_name: "SENIOR", + req_desc: "", + courses_required_count: 2, + courses_satisfied_count: 0, + subreqs_list: [SEN_INTE_REQ] +} + +// EXPORT + +export const CONC_PLSC_BA_STAN: DegreeConcentration = { + conc_name: "STANDARD", + conc_desc: "", + conc_reqs: [PLSC_INTRO, PLSC_CORE_STAN, PLSC_SUB, PLSC_SEMINAR, PLSC_SEN_STAN] +} + +export const CONC_PLSC_BA_INTE: DegreeConcentration = { + conc_name: "INTENSIVE", + conc_desc: "", + conc_reqs: [PLSC_INTRO, PLSC_CORE_INTE, PLSC_SUB, PLSC_SEMINAR, PLSC_SEN_INTE] +} diff --git a/frontend/src/database/programs/configs/config-econ.ts b/frontend/src/database/programs/configs/config-econ.ts deleted file mode 100644 index 30b1553..0000000 --- a/frontend/src/database/programs/configs/config-econ.ts +++ /dev/null @@ -1,162 +0,0 @@ - -// import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; - -// import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151 } from "../data-courses"; -// import { SC_ECON_110 } from "../data-studentcourses"; - -// // INTRO - -// const ECON_MATH: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "MATH", -// subreq_desc: "118 or 120 recommended. Any MATH 200+ satisfies.", -// courses_required: 1, -// courses_options: [null, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const ECON_INTRO_MICRO: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "INTRO MICRO", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [ECON_108, ECON_110, ECON_115], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [SC_ECON_110], -// } - -// const ECON_INTRO_MACRO: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "INTRO MACRO", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [ECON_111, ECON_116], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const ECON_INTRO: DegreeRequirement = { -// req_type_id: 1, -// req_name: "INTRO", -// req_desc: "", - -// courses_required_count: 3, -// courses_satisfied_count: 1, - -// subreqs_list: [ECON_MATH, ECON_INTRO_MICRO, ECON_INTRO_MACRO] -// } - -// // CORE - -// const ECON_CORE_MICRO: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "INTERMEDIATE MICRO", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [ECON_121, ECON_125], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const ECON_CORE_MACRO: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "INTERMEDIATE MACRO", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [ECON_122, ECON_126], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const ECON_CORE_METRICS: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "ECONOMETRICS", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [ECON_117, ECON_123, ECON_136], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const ECON_CORE: DegreeRequirement = { -// req_type_id: 1, -// req_name: "CORE", -// req_desc: "", - -// courses_required_count: 3, -// courses_satisfied_count: 0, - -// subreqs_list: [ECON_CORE_MICRO, ECON_CORE_MACRO, ECON_CORE_METRICS] -// } - -// // ELECTIVE - -// const ECON_RANGE_ELECS: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "", -// subreq_desc: "Standard elective or DUS approved extra-department substitution.", -// courses_required: 1, -// courses_options: [null], -// courses_elective_range: { dept: "CPSC", min_code: 123, max_code: 999 }, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const ECON_SUB_ELEC: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "", -// subreq_desc: "Intermediate or advanced ECON courses, traditionally numbered 123+.", -// courses_required: 3, -// courses_options: [null, null, null], -// courses_elective_range: { dept: "ECON", min_code: 123, max_code: 999 }, -// courses_any_bool: true, -// student_courses_satisfying: [] -// } - -// const ECON_ELECTIVES: DegreeRequirement = { -// req_type_id: 1, -// req_name: "ELECTIVE", -// req_desc: "", - -// courses_required_count: 4, -// courses_satisfied_count: 0, - -// subreqs_list: [ECON_SUB_ELEC, ECON_RANGE_ELECS] -// } - -// // SENIOR - -// const ECON_SEN_SUB: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "SENIOR REQUIREMENT", -// subreq_desc: "", -// courses_required: 2, -// courses_options: [null, null], -// courses_elective_range: { dept: "ECON", min_code: 400, max_code: 491 }, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const ECON_SENIOR: DegreeRequirement = { -// req_type_id: 1, -// req_name: "SENIOR", -// req_desc: "", - -// courses_required_count: 2, -// courses_satisfied_count: 0, - -// subreqs_list: [ECON_SEN_SUB] -// } - -// // FINAL - -// export const ECON_CONFIG: DegreeConfiguration = { -// reqs_list: [ECON_INTRO, ECON_CORE, ECON_ELECTIVES, ECON_SENIOR] -// } diff --git a/frontend/src/database/programs/configs/config-hist.ts b/frontend/src/database/programs/configs/config-hist.ts deleted file mode 100644 index 5786583..0000000 --- a/frontend/src/database/programs/configs/config-hist.ts +++ /dev/null @@ -1,273 +0,0 @@ - -// import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; - -// // PRE - -// const HIST_PRE: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "PRE 1800", -// subreq_desc: "", -// courses_required: 2, -// courses_options: [null, null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const HIST_PREINDUSTRIAL: DegreeRequirement = { -// req_type_id: 1, -// req_name: "PREINDUSTRIAL", -// req_desc: "", - -// courses_required_count: 2, -// courses_satisfied_count: 0, - -// checkbox: true, -// subreqs_list: [HIST_PRE] -// } - -// // GLOBAL - -// const HIST_CORE_GLOB_AFRICA: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "AFRICA", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const HIST_CORE_GLOB_ASIA: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "ASIA", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const HIST_CORE_GLOB_EUROPE: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "EUROPE", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const HIST_CORE_GLOB_LA: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "LATIN AMERICA", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const HIST_CORE_GLOB_ME: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "MIDDLE EAST", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const HIST_CORE_GLOB_US: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "U.S.", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const HIST_GLOB_CORE: DegreeRequirement = { -// req_type_id: 1, -// req_name: "GLOBAL", -// req_desc: "", - -// courses_required_count: 5, -// courses_satisfied_count: 0, - -// subreqs_required_count: 5, -// subreqs_satisfied_count: 0, - -// subreqs_list: [HIST_CORE_GLOB_AFRICA, HIST_CORE_GLOB_ASIA, HIST_CORE_GLOB_EUROPE, HIST_CORE_GLOB_LA, HIST_CORE_GLOB_ME, HIST_CORE_GLOB_US] -// } - -// // SEMINAR - -// const HIST_DEPT_SEMINAR: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "DEPARTMENTAL", -// subreq_desc: "", -// courses_required: 2, -// courses_options: [null, null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const HIST_SEMINAR: DegreeRequirement = { -// req_type_id: 1, -// req_name: "SEMINAR", -// req_desc: "", - -// courses_required_count: 2, -// courses_satisfied_count: 0, - -// checkbox: true, -// subreqs_list: [HIST_DEPT_SEMINAR] -// } - -// // ELECTIVE - -// const HIST_ELEC_SPEC_INDIV: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "", -// subreq_desc: "", -// courses_required: 3, -// courses_options: [null, null, null, null, null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const HIST_ELEC_GLOB_INDIV: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "", -// subreq_desc: "", -// courses_required: 5, -// courses_options: [null, null, null, null, null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const HIST_ELECTIVE_SPEC: DegreeRequirement = { -// req_type_id: 1, -// req_name: "ELECTIVE", -// req_desc: "", - -// courses_required_count: 3, -// courses_satisfied_count: 0, - -// subreqs_list: [HIST_ELEC_SPEC_INDIV] -// } - -// const HIST_ELECTIVE_GLOB: DegreeRequirement = { -// req_type_id: 1, -// req_name: "ELECTIVE", -// req_desc: "", - -// courses_required_count: 5, -// courses_satisfied_count: 0, - -// subreqs_list: [HIST_ELEC_GLOB_INDIV] -// } - -// // SENIOR - -// const ECON_SEN_ONE: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "ONE TERM", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const ECON_SEN_TWO: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "TWO TERM", -// subreq_desc: "", -// courses_required: 2, -// courses_options: [null, null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const ECON_SENIOR: DegreeRequirement = { -// req_type_id: 1, -// req_name: "SENIOR", -// req_desc: "", - -// courses_required_count: 0, -// courses_satisfied_count: 0, - -// subreqs_required_count: 1, -// subreqs_satisfied_count: 0, - -// subreqs_list: [ECON_SEN_ONE, ECON_SEN_TWO] -// } - -// // FINAL - -// const HIST_SPEC_CORE_IN: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "REGION OR PATHWAY", -// subreq_desc: "", -// courses_required: 5, -// courses_options: [null, null, null, null, null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const HIST_SPEC_CORE_OUT: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "OUTSIDE", -// subreq_desc: "", -// courses_required: 2, -// courses_options: [null, null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const HIST_SPEC_CORE: DegreeRequirement = { -// req_type_id: 1, -// req_name: "SPECIALIST", -// req_desc: "", - -// courses_required_count: 7, -// courses_satisfied_count: 0, - -// subreqs_required_count: 2, -// subreqs_satisfied_count: 0, - -// subreqs_list: [HIST_SPEC_CORE_IN, HIST_SPEC_CORE_OUT] -// } - -// // LAST - -// export const HIST_CONFIG_BA_SPEC: DegreeConfiguration = { -// config_name: "SPECIALIST", -// reqs_list: [HIST_PREINDUSTRIAL, HIST_SEMINAR, HIST_SPEC_CORE, HIST_ELECTIVE_SPEC, ECON_SENIOR] -// } - -// export const HIST_CONFIG_BA_GLOB: DegreeConfiguration = { -// config_name: "GLOBALIST", -// reqs_list: [HIST_PREINDUSTRIAL, HIST_SEMINAR, HIST_GLOB_CORE, HIST_ELECTIVE_GLOB, ECON_SENIOR] -// } - -// export const HIST_CONFIG_BA: DegreeConfiguration = { -// config_name: "GLOBALIST", -// reqs_list: [HIST_PREINDUSTRIAL, HIST_SEMINAR, HIST_GLOB_CORE, HIST_ELECTIVE_GLOB, ECON_SENIOR] -// } \ No newline at end of file diff --git a/frontend/src/database/programs/configs/config-plsc.ts b/frontend/src/database/programs/configs/config-plsc.ts deleted file mode 100644 index 1649d77..0000000 --- a/frontend/src/database/programs/configs/config-plsc.ts +++ /dev/null @@ -1,201 +0,0 @@ - -// import { DegreeConfiguration, DegreeRequirement, DegreeSubrequirement } from "@/types/type-program"; - -// // INTRO - -// const PLSC_INTRO_TWO: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "INTRO COURSES", -// subreq_desc: "", -// courses_required: 2, -// courses_options: [null, null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const PLSC_INTRO: DegreeRequirement = { -// req_type_id: 1, -// req_name: "INTRO", -// req_desc: "", - -// courses_required_count: 3, -// courses_satisfied_count: 1, - -// subreqs_list: [PLSC_INTRO_TWO] -// } - -// // CORE - -// const PLSC_CORE_LECTURES: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "CORE LECTURES", -// subreq_desc: "", -// courses_required: 2, -// courses_options: [null, null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const PLSC_CORE_METHODS: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "METHODS AND FORMAL THEORY", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const PLSC_CORE: DegreeRequirement = { -// req_type_id: 1, -// req_name: "CORE", -// req_desc: "", - -// courses_required_count: 3, -// courses_satisfied_count: 0, - -// subreqs_list: [PLSC_CORE_LECTURES, PLSC_CORE_METHODS] -// } - -// // ELECTIVE - -// const PLSC_SUB_INTL: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "INTERNATIONAL RELATIONS", -// subreq_desc: "", -// courses_required: 2, -// courses_options: [null, null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const PLSC_SUB_US: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "AMERICAN GOVERNMENT", -// subreq_desc: "", -// courses_required: 2, -// courses_options: [null, null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const PLSC_SUB_PHIL: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "POLITICAL PHILOSOPHY", -// subreq_desc: "", -// courses_required: 2, -// courses_options: [null, null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const PLSC_SUB_COMP: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "COMPARATIVE POLITICS", -// subreq_desc: "", -// courses_required: 2, -// courses_options: [null, null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const PLSC_SUBFIELDS: DegreeRequirement = { -// req_type_id: 1, -// req_name: "SUBFIELDS", -// req_desc: "", - -// courses_required_count: 4, -// courses_satisfied_count: 0, - -// subreqs_required_count: 2, -// subreqs_satisfied_count: 0, - -// subreqs_list: [PLSC_SUB_INTL, PLSC_SUB_US, PLSC_SUB_PHIL, PLSC_SUB_COMP] -// } - -// // SEMINAR - -// const PLSC_SEM_ANY: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "YEAR ANY", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const PLSC_SEM_SEN: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "YEAR SENIOR", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const PLSC_SEMINAR: DegreeRequirement = { -// req_type_id: 1, -// req_name: "SEMINAR", -// req_desc: "Seminar courses taught by PLSC faculty satisfy.", - -// courses_required_count: 2, -// courses_satisfied_count: 0, - -// checkbox: true, -// subreqs_list: [PLSC_SEM_ANY, PLSC_SEM_SEN] -// } - -// // SENIOR - -// const PLSC_SEN_ONE: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "ONE TERM", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const PLSC_SEN_TWO: DegreeSubrequirement = { -// subreq_type_id: 1, -// subreq_name: "TWO TERM", -// subreq_desc: "", -// courses_required: 2, -// courses_options: [null, null], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const PLSC_SENIOR: DegreeRequirement = { -// req_type_id: 1, -// req_name: "SENIOR", -// req_desc: "", - -// courses_required_count: 0, -// courses_satisfied_count: 0, - -// subreqs_required_count: 1, -// subreqs_satisfied_count: 0, - -// subreqs_list: [PLSC_SEN_ONE, PLSC_SEN_TWO] -// } - -// // FINAL - -// export const PLSC_CONFIG: DegreeConfiguration = { -// reqs_list: [PLSC_INTRO, PLSC_CORE, PLSC_SUBFIELDS, PLSC_SEMINAR, PLSC_SENIOR] -// } diff --git a/frontend/src/database/programs/data-program.ts b/frontend/src/database/programs/data-program.ts index 3cf4a92..d56d96b 100644 --- a/frontend/src/database/programs/data-program.ts +++ b/frontend/src/database/programs/data-program.ts @@ -1,6 +1,9 @@ import { Program } from "@/types/type-program"; -import { CONC_CPSC_BA_I, CONC_CPSC_BS_I } from "./concs-cpsc"; +import { CONC_CPSC_BA_I, CONC_CPSC_BS_I } from "./concs/concs-cpsc"; +import { CONC_ECON_BA_I } from "./concs/concs-econ"; +import { CONC_HIST_BA_GLOB, CONC_HIST_BA_SPEC } from "./concs/concs-hist"; +import { CONC_PLSC_BA_INTE, CONC_PLSC_BA_STAN } from "./concs/concs-plsc"; export const PROG_CPSC: Program = { prog_data: { @@ -27,14 +30,7 @@ export const PROG_ECON: Program = { prog_website: "" }, prog_degs: [ - { - deg_type: "B.A.", - deg_concs: [] - }, - { - deg_type: "B.S.", - deg_concs: [] - } + { deg_type: "B.A.", deg_concs: [CONC_ECON_BA_I] } ] } @@ -48,10 +44,7 @@ export const PROG_PLSC: Program = { prog_website: "" }, prog_degs: [ - { - deg_type: "B.A.", - deg_concs: [] - } + { deg_type: "B.A.", deg_concs: [CONC_PLSC_BA_STAN, CONC_PLSC_BA_INTE] } ] } @@ -67,7 +60,7 @@ export const PROG_HIST: Program = { prog_degs: [ { deg_type: "B.A.", - deg_concs: [] + deg_concs: [CONC_HIST_BA_GLOB, CONC_HIST_BA_SPEC] } ] } diff --git a/frontend/src/database/programs/metas/meta-econ.ts b/frontend/src/database/programs/metas/meta-econ.ts deleted file mode 100644 index 2518fc1..0000000 --- a/frontend/src/database/programs/metas/meta-econ.ts +++ /dev/null @@ -1,17 +0,0 @@ - -// import { ConcMetadata, DegreeMetadata } from "@/types/type-program"; - -// const CONC_ECON_GEN: ConcMetadata = { -// conc_name: "", -// stats: { courses: 0, rating: 0, type: "So", workload: 0 }, -// about: "The Economics major provides a rigorous framework for understanding financial markets, public policy, and decision-making. Students learn analytical techniques in microeconomics, macroeconomics, and econometrics, developing critical skills in data interpretation and policy evaluation. The major offers pathways in finance, economic development, and quantitative analysis, preparing students for careers in government, consulting, business, and research.", -// } - -// export const META_ECON_BA: DegreeMetadata = { -// name: "Economics", -// abbr: "ECON", -// degreeType: "B.A.", - -// concs: [CONC_ECON_GEN], -// info: { students: 0, dus: { address: "28 Hillhouse Ave, (203) 432-3576", email: "economics@yale.edu", name: "Dirk Bergemann" }, wesbiteLink: "https://economics.yale.edu", catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/economics/" } -// } diff --git a/frontend/src/database/programs/metas/meta-hist.ts b/frontend/src/database/programs/metas/meta-hist.ts deleted file mode 100644 index 8b492e1..0000000 --- a/frontend/src/database/programs/metas/meta-hist.ts +++ /dev/null @@ -1,23 +0,0 @@ - -// import { ConcMetadata, DegreeMetadata } from "@/types/type-program"; - -// const META_HIST_GLOB: ConcMetadata = { -// conc_name: "Globalist", -// stats: { courses: 0, rating: 0, type: "Hu", workload: 0 }, -// about: "The History major offers a broad exploration of human experience across time, cultures, and societies. Students gain expertise in archival research, critical analysis, and historical interpretation. The major prepares students to engage with contemporary issues by understanding their historical contexts, whether in politics, economics, or cultural transformations. History students learn to construct arguments based on evidence and develop a strong capacity for analytical reasoning and effective communication.", -// } - -// const META_HIST_SPEC: ConcMetadata = { -// conc_name: "Specialist", -// stats: { courses: 0, rating: 0, type: "Hu", workload: 0 }, -// about: "The History major offers a broad exploration of human experience across time, cultures, and societies. Students gain expertise in archival research, critical analysis, and historical interpretation. The major prepares students to engage with contemporary issues by understanding their historical contexts, whether in politics, economics, or cultural transformations. History students learn to construct arguments based on evidence and develop a strong capacity for analytical reasoning and effective communication.", -// } - -// export const META_HIST_BA: DegreeMetadata = { -// name: "History", -// abbr: "HIST", -// degreeType: "B.A.", - -// concs: [META_HIST_GLOB, META_HIST_SPEC], -// info: { students: 0, dus: { address: "HQ 211, (203) 432-1366", email: "history@yale.edu", name: "Alan Mikhail" }, wesbiteLink: "https://history.yale.edu", catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/history/" } -// } \ No newline at end of file diff --git a/frontend/src/database/programs/metas/meta-plsc.ts b/frontend/src/database/programs/metas/meta-plsc.ts deleted file mode 100644 index 9ea7064..0000000 --- a/frontend/src/database/programs/metas/meta-plsc.ts +++ /dev/null @@ -1,17 +0,0 @@ - -// import { ConcMetadata, DegreeMetadata } from "@/types/type-program"; - -// const META_PLSC: ConcMetadata = { -// conc_name: "", -// stats: { courses: 0, rating: 0, type: "Hu", workload: 0 }, -// about: "The Political Science major examines governments, political behavior, and institutional structures at local, national, and global levels. Students engage with the theoretical foundations of politics, quantitative and qualitative methodologies, and policy analysis. The program prepares students for careers in law, public policy, international relations, and academia while offering flexibility to explore subfields such as comparative politics, American politics, and political philosophy.", -// } - -// export const META_PLSC_BA: DegreeMetadata = { -// name: "Political Science", -// abbr: "PLSC", -// degreeType: "B.A.", - -// concs: [META_PLSC], -// info: { students: 0, dus: { address: "Rosenkranz Hall 130, (203) 432-5248", email: "politicalscience@yale.edu", name: "Isabela Mares" }, wesbiteLink: "https://politicalscience.yale.edu", catologLink: "https://catalog.yale.edu/ycps/subjects-of-instruction/history/" } -// } diff --git a/frontend/src/types/type-program.ts b/frontend/src/types/type-program.ts index a18486a..e930036 100644 --- a/frontend/src/types/type-program.ts +++ b/frontend/src/types/type-program.ts @@ -71,3 +71,9 @@ export interface Program { prog_data: ProgramMetadata; prog_degs: ProgramDegree[]; } + +export interface MajorsIndex { + prog: number; + deg: number; + conc: number; +} From 9b9f61b1d8b45d8065fcf13c7b1c6ebd3a98881b Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Tue, 11 Mar 2025 11:51:49 -0700 Subject: [PATCH 18/38] program list --- frontend/src/app/majors/Majors.module.css | 26 ++++- .../app/majors/metadata/Metadata.module.css | 16 ++- frontend/src/app/majors/metadata/Metadata.tsx | 52 ++++++--- .../src/app/majors/metadata/MetadataUtils.tsx | 107 ------------------ frontend/src/app/majors/page.tsx | 48 ++++---- .../app/majors/requirements/Requirements.tsx | 35 +++--- 6 files changed, 115 insertions(+), 169 deletions(-) delete mode 100644 frontend/src/app/majors/metadata/MetadataUtils.tsx diff --git a/frontend/src/app/majors/Majors.module.css b/frontend/src/app/majors/Majors.module.css index 0243411..24bbae8 100644 --- a/frontend/src/app/majors/Majors.module.css +++ b/frontend/src/app/majors/Majors.module.css @@ -1,19 +1,15 @@ .MajorsPage { /* border: 1px solid blue; */ - position: absolute; top: 75px; display: flex; flex-direction: row; - justify-content: center; width: 100%; height: calc(100vh - 75px); - - /* padding-top: 50px; */ } .Divider { @@ -26,3 +22,25 @@ width: 1px; height: calc(100% - 100px); } + +.EditButton { + position: fixed; + + top: 95px; + left: 20px; + + width: 30px; + height: 30px; + + color: white; + text-align: center; + line-height: 30px; + font-size: 20px; + + background-color: #61ADFE; + border: none; + border-radius: 50%; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.25); + + z-index: 2; +} \ No newline at end of file diff --git a/frontend/src/app/majors/metadata/Metadata.module.css b/frontend/src/app/majors/metadata/Metadata.module.css index 5293146..56af915 100644 --- a/frontend/src/app/majors/metadata/Metadata.module.css +++ b/frontend/src/app/majors/metadata/Metadata.module.css @@ -11,7 +11,6 @@ .MetadataContainer{ /* border: 1px solid red; */ - margin-top: 75px; width: 550px; @@ -99,7 +98,7 @@ background-color: white; border: none; cursor: pointer; - padding: 10px; + /* padding: 10px; */ display: inline-block; } @@ -107,11 +106,12 @@ display: flex; flex-direction: row; - margin-left: 80px; + /* margin-left: 80px; */ margin-bottom: 8px; } .ToggleOption { + font-size: 14px; padding: 4px 10px; cursor: pointer; background-color: white; @@ -126,4 +126,12 @@ .ToggleOption.active { background-color: #598ff4; color: white; -} \ No newline at end of file +} + +.ProgramOption { + color: gray; + font-size: 20px; + font-weight: bold; + margin-bottom: 4px; + cursor: pointer; +} diff --git a/frontend/src/app/majors/metadata/Metadata.tsx b/frontend/src/app/majors/metadata/Metadata.tsx index 2c2904e..d9212c1 100644 --- a/frontend/src/app/majors/metadata/Metadata.tsx +++ b/frontend/src/app/majors/metadata/Metadata.tsx @@ -1,12 +1,9 @@ import { useState, useEffect } from "react"; -import Link from 'next/link'; +import Style from "./Metadata.module.css"; -import { User } from "@/types/type-user"; +import Link from 'next/link'; import { MajorsIndex, Program } from "@/types/type-program"; -import { pinProgram, addProgram } from "./MetadataUtils"; - -import Style from "./Metadata.module.css"; function MetadataTopshelf(props: { program: Program }) { @@ -80,7 +77,8 @@ function MetadataTopshelf(props: { program: Program }) function MetadataBody(props: { program: Program, index: MajorsIndex }){ return( -
+ // style={{ marginLeft: "80px" }} +
{/* */}
ABOUT @@ -147,17 +145,17 @@ function MetadataContent(props: { program: Program, index: MajorsIndex, setIndex ); } -function MetadataScrollButton(props: { index: MajorsIndex, setIndex: Function; peekProgram: Function; dir: number }) +function MetadataScrollButton(props: { programs: Program[], index: MajorsIndex, setIndex: Function; dir: number }) { - return ( + return(
props.setIndex({ conc: 0, deg: 0, prog: props.index.prog + props.dir })}>
- {props.peekProgram(props.dir).prog_name} + {props.programs[(props.index.prog + props.dir + props.programs.length) % props.programs.length].prog_data.prog_name}
- {props.peekProgram(props.dir).prog_abbr} + {props.programs[(props.index.prog + props.dir + props.programs.length) % props.programs.length].prog_data.prog_abbr}
@@ -165,14 +163,38 @@ function MetadataScrollButton(props: { index: MajorsIndex, setIndex: Function; p ); } +function ProgramContent(props: { programs: Program[], index: MajorsIndex, setIndex: Function }){ + return( +
+ + + +
+ ) +} + +function ProgramList(props: { programs: Program[], setIndex: Function }){ + return( +
+ {props.programs.map((program: Program, prog_index: number) => ( +
props.setIndex({ conc: 0, deg: 0, prog: prog_index })}> + {program.prog_data.prog_name} {program.prog_data.prog_abbr} +
+ ))} +
+ ) +} -function Metadata(props: { program: Program, index: MajorsIndex, setIndex: Function, peekProgram: Function}) +function Metadata(props: { programs: Program[], index: MajorsIndex, setIndex: Function }) { - return ( + return(
- - - + {props.index.conc == -1 ? ( + + ) : ( + + ) + }
); } diff --git a/frontend/src/app/majors/metadata/MetadataUtils.tsx b/frontend/src/app/majors/metadata/MetadataUtils.tsx deleted file mode 100644 index f0ca59c..0000000 --- a/frontend/src/app/majors/metadata/MetadataUtils.tsx +++ /dev/null @@ -1,107 +0,0 @@ - -import { User } from "@/types/type-user"; -import { StudentDegree } from "@/types/type-program"; - -export function pinProgram( - currProgram: number, - currDegree: number, - user: User, - setUser: Function, -) { - // Find if the program is already in degreeDeclarations - const existingDegree = user.FYP.degreeDeclarations.find( - (degree) => degree.programIndex === currProgram - ); - - if (existingDegree) { - if (existingDegree.status === "PIN") { - // Unpin the program (remove it from degreeDeclarations) - const updatedUser: User = { - ...user, - FYP: { - ...user.FYP, - degreeDeclarations: user.FYP.degreeDeclarations.filter( - (degree) => degree.programIndex !== currProgram - ), - }, - }; - setUser(updatedUser); - } - } else { - // Pin the program if it's not already pinned or added - const newStudentDegree: StudentDegree = { - status: "PIN", - programIndex: currProgram, - degreeIndex: currDegree, - }; - - const updatedUser: User = { - ...user, - FYP: { - ...user.FYP, - degreeDeclarations: [...user.FYP.degreeDeclarations, newStudentDegree], - }, - }; - setUser(updatedUser); - } -} - - -// Utility function to add a program -export function addProgram( - currProgram: number, - currDegree: number, - user: User, - setUser: Function -) { - // Find if the program already exists in degreeDeclarations - const existingDegree = user.FYP.degreeDeclarations.find( - (degree) => degree.programIndex === currProgram - ); - - if (existingDegree) { - if (existingDegree.status === "ADD") { - // Unadd the program (remove it from degreeDeclarations) - const updatedUser: User = { - ...user, - FYP: { - ...user.FYP, - degreeDeclarations: user.FYP.degreeDeclarations.filter( - (degree) => degree.programIndex !== currProgram - ), - }, - }; - setUser(updatedUser); - } else if (existingDegree.status === "PIN") { - // Change status from "PIN" to "ADD" - const updatedUser: User = { - ...user, - FYP: { - ...user.FYP, - degreeDeclarations: user.FYP.degreeDeclarations.map((degree) => - degree.programIndex === currProgram - ? { ...degree, status: "ADD" } - : degree - ), - }, - }; - setUser(updatedUser); - } - } else { - // Add the program if it's not already pinned or added - const newStudentDegree: StudentDegree = { - status: "ADD", - programIndex: currProgram, - degreeIndex: currDegree, - }; - - const updatedUser: User = { - ...user, - FYP: { - ...user.FYP, - degreeDeclarations: [...user.FYP.degreeDeclarations, newStudentDegree], - }, - }; - setUser(updatedUser); - } -} diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index 35e197e..cbd9f16 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -1,42 +1,50 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useAuth } from "../providers"; import { MajorsIndex } from "@/types/type-program"; import Style from "./Majors.module.css"; import NavBar from "@/components/navbar/NavBar"; -import Overhead from "./overhead/Overhead"; +// import Overhead from "./overhead/Overhead"; import Metadata from "./metadata/Metadata"; import Requirements from "./requirements/Requirements"; function Majors() { const { user } = useAuth(); + const [index, setIndex] = useState({ conc: 0, deg: 0, prog: 0 }); + + useEffect(() => { + if(typeof window !== "undefined"){ + const storedIndex = sessionStorage.getItem("majorsIndex"); + if(storedIndex){ + setIndex(JSON.parse(storedIndex)); + } + } + }, []); - const [index, setIndex] = useState({ conc: 0, deg: 0, prog: 0}); - const updateIndex: Function = (index: MajorsIndex) => { - index.prog = index.prog % user.FYP.programs.length; - setIndex(index); - }; + useEffect(() => { + if(typeof window !== "undefined"){ + sessionStorage.setItem("majorsIndex", JSON.stringify(index)); + } + }, [index]); - const peekProgram = (dir: number) => { - return user.FYP.programs[(index.prog + dir + user.FYP.programs.length) % user.FYP.programs.length].prog_data; - }; + const updateIndex = (newIndex: MajorsIndex) => { + if(newIndex.conc === -1){ + setIndex({ ...newIndex, deg: 0, conc: index.conc === -1 ? 0 : -1 }); + return; + } + setIndex({ ...newIndex, prog: (newIndex.prog + user.FYP.programs.length) % user.FYP.programs.length }); + }; - return( + return(
- {/* utility={} */} - + updateIndex({ ...index, conc: -1 })}/>}/>
- +
- +
); diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index 82fce59..9337a83 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -2,17 +2,13 @@ import { useState } from "react"; import Style from "./Requirements.module.css"; -import { useAuth } from "@/app/providers"; - -import { User, Course } from "@/types/type-user"; +import { Course } from "@/types/type-user"; import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; import { CourseIcon } from "@/components/course-icon/CourseIcon"; -function RenderSubrequirementCourse(props: { course: Course | null, subreq: ConcentrationSubrequirement; user: User }){ +function RenderSubrequirementCourse(props: { course: Course | null, subreq: ConcentrationSubrequirement }){ - // TODO - if(props.course === null){ return(
@@ -30,7 +26,7 @@ function RenderSubrequirementCourse(props: { course: Course | null, subreq: Conc ) } -function RenderSubrequirement(props: { subreq: ConcentrationSubrequirement; user: User }) { +function RenderSubrequirement(props: { subreq: ConcentrationSubrequirement }) { const [showAll, setShowAll] = useState(false); // Separate null and non-null courses @@ -60,7 +56,7 @@ function RenderSubrequirement(props: { subreq: ConcentrationSubrequirement; user
{displayedCourses.map((course, index) => (
- +
))} {/* Toggle Button to Expand / Collapse */} @@ -148,18 +144,16 @@ function RenderRequirement(props: { req: ConcentrationRequirement }){
{subreqs_required_count ? subreqs_list.slice(0, subreqs_required_count).map((subreq, index) => ( - + )) : subreqs_list.map((subreq, index) => ( - + ))}
); } - -// function RequirementsContent(props: { edit: boolean, programIndex: number, degreeIndex: number, degreeConfiguration: DegreeConfiguration, user: User, setUser: Function }) function RequirementsContent(props: { conc: DegreeConcentration }) { @@ -172,15 +166,14 @@ function RequirementsContent(props: { conc: DegreeConcentration }) ); } -// function Requirements(props: { user: User, setUser: Function, programIndex: number, degreeIndex: number, degreeConfiguration: DegreeConfiguration }) -function Requirements(props: { conc: DegreeConcentration }) +function Requirements(props: { conc: DegreeConcentration | null }) { // const [edit, setEdit] = useState(false); // const updateEdit = () => { // setEdit(!edit); // }; - return( + return(
@@ -193,10 +186,14 @@ function Requirements(props: { conc: DegreeConcentration })
-
- {/* */} - -
+ {props.conc == null ? ( +
+ ) : ( +
+ +
+ ) + }
); } From d2eb41e44e8c08d88146811851aee773c796aaff Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Tue, 11 Mar 2025 12:23:26 -0700 Subject: [PATCH 19/38] good start --- frontend/src/app/majors/page.tsx | 32 +++++----- frontend/src/database/data-courses.ts | 63 ++++++++++--------- frontend/src/database/data-studentcourses.ts | 14 ----- .../src/database/programs/concs/concs-cpsc.ts | 3 +- .../src/database/programs/concs/concs-econ.ts | 3 +- 5 files changed, 55 insertions(+), 60 deletions(-) delete mode 100644 frontend/src/database/data-studentcourses.ts diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index cbd9f16..9131031 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -13,30 +13,34 @@ import Requirements from "./requirements/Requirements"; function Majors() { const { user } = useAuth(); - const [index, setIndex] = useState({ conc: 0, deg: 0, prog: 0 }); - - useEffect(() => { + + const [index, setIndex] = useState(null); + + useEffect(() => { if(typeof window !== "undefined"){ const storedIndex = sessionStorage.getItem("majorsIndex"); - if(storedIndex){ - setIndex(JSON.parse(storedIndex)); - } + setIndex(storedIndex ? JSON.parse(storedIndex) : { conc: 0, deg: 0, prog: 0 }); } }, []); useEffect(() => { - if(typeof window !== "undefined"){ + if(typeof window !== "undefined" && index !== null){ sessionStorage.setItem("majorsIndex", JSON.stringify(index)); } }, [index]); - const updateIndex = (newIndex: MajorsIndex) => { - if(newIndex.conc === -1){ - setIndex({ ...newIndex, deg: 0, conc: index.conc === -1 ? 0 : -1 }); - return; - } - setIndex({ ...newIndex, prog: (newIndex.prog + user.FYP.programs.length) % user.FYP.programs.length }); - }; + const updateIndex = (newIndex: MajorsIndex) => { + setIndex((prev) => ({ + ...prev!, + ...newIndex, + prog: newIndex.prog !== undefined + ? (newIndex.prog + user.FYP.programs.length) % user.FYP.programs.length + : prev!.prog, + conc: newIndex.conc === -1 ? (prev!.conc === -1 ? 0 : -1) : newIndex.conc, + })); + }; + + if(index === null) return null; return(
diff --git a/frontend/src/database/data-courses.ts b/frontend/src/database/data-courses.ts index 5939794..d2deb8d 100644 --- a/frontend/src/database/data-courses.ts +++ b/frontend/src/database/data-courses.ts @@ -1,24 +1,25 @@ -import { Course } from "@/types/type-user" +import { Course, StudentCourse } from "@/types/type-user" + +// COURSES // HSAR PROGRAM -export const HSAR_401: Course = { codes: ["HSAR 401"], title: "Critical Approaches To Art History", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const HSAR_401: Course = { codes: ["HSAR 401"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } // PLSC PROGRAM export const PLSC_474: Course = { codes: ["PSLC 474"], title: "", credit: 1, dist: ["So"], seasons: ["Spring"]} export const PLSC_490: Course = { codes: ["PSLC 490"], title: "", credit: 1, dist: ["So"], seasons: ["Fall", "Spring"]} export const PLSC_493: Course = { codes: ["PSLC 493"], title: "", credit: 1, dist: ["So"], seasons: ["Fall", "Spring"]} - // CPSC PROGRAM -export const CPSC_201: Course = { codes: ["CPSC 201"], title: "Introduction To Computer Science", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_202: Course = { codes: ["CPSC 202"], title: "Math Tools For Computer Scientists", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const MATH_244: Course = { codes: ["MATH 244"], title: "Discrete Mathematics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_223: Course = { codes: ["CPSC 223"], title: "Data Structures And Programming Techniques", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_323: Course = { codes: ["CPSC 323"], title: "Introduction To Systems Programming", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_365: Course = { codes: ["CPSC 365"], title: "Algorithms", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_366: Course = { codes: ["CPSC 366"], title: "Intensive Algorithms", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_490: Course = { codes: ["CPSC 490"], title: "Senior Project", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_201: Course = { codes: ["CPSC 201"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_202: Course = { codes: ["CPSC 202"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const MATH_244: Course = { codes: ["MATH 244"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_223: Course = { codes: ["CPSC 223"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_323: Course = { codes: ["CPSC 323"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_365: Course = { codes: ["CPSC 365"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_366: Course = { codes: ["CPSC 366"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_490: Course = { codes: ["CPSC 490"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } // ECON PROGRAM export const MATH_110: Course = { codes: ["MATH 110"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } @@ -29,20 +30,26 @@ export const MATH_116: Course = { codes: ["MATH 116"], title: "", credit: 1, dis export const ENAS_151: Course = { codes: ["ENAS 151"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } export const MATH_118: Course = { codes: ["MATH 118"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } export const MATH_120: Course = { codes: ["MATH 120"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } - -export const ECON_108: Course = { codes: ["ECON 108"], title: "Introductory Microeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_110: Course = { codes: ["ECON 110"], title: "Introductory Microeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_115: Course = { codes: ["ECON 115"], title: "Introductory Microeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } - -export const ECON_111: Course = { codes: ["ECON 111"], title: "Introductory Macroeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_116: Course = { codes: ["ECON 116"], title: "Introductory Macroeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } - -export const ECON_121: Course = { codes: ["ECON 121"], title: "Intermediate Microeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_125: Course = { codes: ["ECON 125"], title: "Intermediate Microeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } - -export const ECON_122: Course = { codes: ["ECON 122"], title: "Intermediate Macroeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_126: Course = { codes: ["ECON 126"], title: "Intermediate Macroeconomics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } - -export const ECON_117: Course = { codes: ["ECON 117"], title: "Econometrics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_123: Course = { codes: ["ECON 123"], title: "Econometrics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_136: Course = { codes: ["ECON 136"], title: "Econometrics", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_108: Course = { codes: ["ECON 108"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_110: Course = { codes: ["ECON 110"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_115: Course = { codes: ["ECON 115"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_111: Course = { codes: ["ECON 111"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_116: Course = { codes: ["ECON 116"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_121: Course = { codes: ["ECON 121"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_125: Course = { codes: ["ECON 125"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_122: Course = { codes: ["ECON 122"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_126: Course = { codes: ["ECON 126"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_117: Course = { codes: ["ECON 117"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_123: Course = { codes: ["ECON 123"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const ECON_136: Course = { codes: ["ECON 136"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } + +// STUDENT COURSES + +// CPSC COURSES +export const SC_CPSC_201: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_201 } +export const SC_CPSC_202: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_202 } +export const SC_CPSC_223: StudentCourse = { term: 202501, status: "DA", result: "GRADE_PASS", course: CPSC_223 } +export const SC_CPSC_323: StudentCourse = { term: 202503, status: "MA", result: "IP", course: CPSC_323 } + +// ECON COURSES +export const SC_ECON_110: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: ECON_110 } diff --git a/frontend/src/database/data-studentcourses.ts b/frontend/src/database/data-studentcourses.ts deleted file mode 100644 index 54120ca..0000000 --- a/frontend/src/database/data-studentcourses.ts +++ /dev/null @@ -1,14 +0,0 @@ - -import { StudentCourse } from "@/types/type-user" -import { CPSC_201, CPSC_202, CPSC_223, CPSC_323, ECON_110 } from "./data-courses" - -// CPSC COURSES -export const SC_CPSC_201: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_201 } -export const SC_CPSC_202: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_202 } -export const SC_CPSC_223: StudentCourse = { term: 202501, status: "DA", result: "GRADE_PASS", course: CPSC_223 } -export const SC_CPSC_323: StudentCourse = { term: 202503, status: "MA", result: "IP", course: CPSC_323 } - -// ECON COURSES -export const SC_ECON_110: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: ECON_110 } - - diff --git a/frontend/src/database/programs/concs/concs-cpsc.ts b/frontend/src/database/programs/concs/concs-cpsc.ts index 45651e9..97f3144 100644 --- a/frontend/src/database/programs/concs/concs-cpsc.ts +++ b/frontend/src/database/programs/concs/concs-cpsc.ts @@ -1,8 +1,7 @@ import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; -import { CPSC_201, CPSC_202, MATH_244, CPSC_223, CPSC_323, CPSC_365, CPSC_366, CPSC_490 } from "../../data-courses"; -import { SC_CPSC_201, SC_CPSC_223, SC_CPSC_323 } from "../../data-studentcourses"; +import { CPSC_201, CPSC_202, MATH_244, CPSC_223, CPSC_323, CPSC_365, CPSC_366, CPSC_490, SC_CPSC_201, SC_CPSC_223, SC_CPSC_323 } from "../../data-courses"; // CORE diff --git a/frontend/src/database/programs/concs/concs-econ.ts b/frontend/src/database/programs/concs/concs-econ.ts index 7f5df20..f7e89fd 100644 --- a/frontend/src/database/programs/concs/concs-econ.ts +++ b/frontend/src/database/programs/concs/concs-econ.ts @@ -1,8 +1,7 @@ import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; -import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151 } from "./../../data-courses"; -import { SC_ECON_110 } from "./../../data-studentcourses"; +import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151, SC_ECON_110 } from "./../../data-courses"; // INTRO From 711a1100221a030d7c3be44c6267322947f529e1 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Tue, 11 Mar 2025 13:40:53 -0700 Subject: [PATCH 20/38] user status (meh) and editing --- frontend/src/app/majors/overhead/Overhead.tsx | 8 +-- .../src/app/majors/overhead/pinned/Pinned.tsx | 37 ++--------- frontend/src/app/majors/page.tsx | 11 ++-- .../requirements/Requirements.module.css | 54 +++++++--------- .../app/majors/requirements/Requirements.tsx | 64 +++++++++---------- frontend/src/database/data-courses.ts | 6 +- frontend/src/database/data-user.ts | 4 +- .../src/database/programs/concs/concs-cpsc.ts | 32 +++++----- .../src/database/programs/concs/concs-econ.ts | 1 + .../src/database/programs/concs/concs-hist.ts | 2 + .../src/database/programs/concs/concs-plsc.ts | 2 + frontend/src/types/type-program.ts | 7 +- frontend/src/types/type-user.ts | 11 +++- 13 files changed, 109 insertions(+), 130 deletions(-) diff --git a/frontend/src/app/majors/overhead/Overhead.tsx b/frontend/src/app/majors/overhead/Overhead.tsx index 8845215..2749dc5 100644 --- a/frontend/src/app/majors/overhead/Overhead.tsx +++ b/frontend/src/app/majors/overhead/Overhead.tsx @@ -2,17 +2,17 @@ import Style from "./Overhead.module.css"; import { User } from "@/types/type-user"; -import MajorSearchBar from "./major-search/MajorSearch"; +// import MajorSearchBar from "./major-search/MajorSearch"; import Pinned from "./pinned/Pinned"; -function Overhead(props: { user: User, setProgramIndex: Function }) { +function Overhead(props: { user: User, setIndex: Function }) { return (
- + {/* */}
PINNED
- +
); } diff --git a/frontend/src/app/majors/overhead/pinned/Pinned.tsx b/frontend/src/app/majors/overhead/pinned/Pinned.tsx index 5ee46c5..08c163c 100644 --- a/frontend/src/app/majors/overhead/pinned/Pinned.tsx +++ b/frontend/src/app/majors/overhead/pinned/Pinned.tsx @@ -1,45 +1,22 @@ import Style from "./Pinned.module.css"; -import { User } from "@/types/type-user"; -import { StudentDegree } from "@/types/type-program"; -import { ALL_PROGRAM_METADATAS } from "@/database/programs/metas/meta-econ"; +import { User, StudentConc } from "@/types/type-user"; -function DegreeIcon(props: { studentDegree: StudentDegree, setProgramIndex: Function }) { - const mark = (status: string) => { - let mark = ""; - switch (status) { - case "DA": - mark = "✓"; - break; - case "ADD": - mark = "⚠"; - break; - case "PIN": - mark = "📌"; - break; - default: - mark = ""; - } - return ( -
- {mark} -
- ); - }; +function ConcIcon(props: { user: User, setIndex: Function, studentConc: StudentConc }) { return( -
props.setProgramIndex(props.studentDegree.programIndex)}> - {mark(props.studentDegree.status)}{ALL_PROGRAM_METADATAS[props.studentDegree.programIndex][0].abbr} +
props.setIndex(props.studentConc.majors_index)}> + 📌{props.user.FYP.prog_list[props.studentConc.majors_index.prog].prog_data.prog_abbr}
); } -function Pinned(props: { user: User, setProgramIndex: Function }) { +function Pinned(props: { user: User, setIndex: Function }) { return (
- {props.user.FYP.degreeDeclarations.map((studentDegree, index) => ( - + {props.user.FYP.decl_list.map((studentConc, index) => ( + ))}
); diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index 9131031..415a612 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -6,7 +6,7 @@ import { MajorsIndex } from "@/types/type-program"; import Style from "./Majors.module.css"; import NavBar from "@/components/navbar/NavBar"; -// import Overhead from "./overhead/Overhead"; +import Overhead from "./overhead/Overhead"; import Metadata from "./metadata/Metadata"; import Requirements from "./requirements/Requirements"; @@ -34,7 +34,7 @@ function Majors() ...prev!, ...newIndex, prog: newIndex.prog !== undefined - ? (newIndex.prog + user.FYP.programs.length) % user.FYP.programs.length + ? (newIndex.prog + user.FYP.prog_list.length) % user.FYP.prog_list.length : prev!.prog, conc: newIndex.conc === -1 ? (prev!.conc === -1 ? 0 : -1) : newIndex.conc, })); @@ -44,11 +44,12 @@ function Majors() return(
- updateIndex({ ...index, conc: -1 })}/>}/> + }/>
- +
updateIndex({ ...index, conc: -1 })}/> +
- +
); diff --git a/frontend/src/app/majors/requirements/Requirements.module.css b/frontend/src/app/majors/requirements/Requirements.module.css index ab5aa0b..f7fcb74 100644 --- a/frontend/src/app/majors/requirements/Requirements.module.css +++ b/frontend/src/app/majors/requirements/Requirements.module.css @@ -11,18 +11,9 @@ .RequirementsContainer { /* border: 1px solid green; */ - margin-top: 37px; - width: 600px; height: 650px; - - /* min-width: 390px; */ - - /* background-color: white; */ - /* padding: 20px; */ - /* border-radius: 25px; */ - /* box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); */ } .ReqsList { @@ -33,7 +24,8 @@ scrollbar-width: thin; overflow-y: scroll; padding-right: 7px; - overflow-x: hidden; */ + overflow-x: hidden; +*/ } .ReqHeader { @@ -55,10 +47,8 @@ font-weight: 500; } - .EmptyCourse { border-radius: 15px; - /* padding: 2px 2px; e7e7e7c3 */ background-color: #F5F5F5; transition: filter 0.4s ease; width: 24px; /* Adjust width to match CourseIcon */ @@ -81,39 +71,21 @@ font-size: 14px; } - -.ButtonRow { - display: flex; - flex-direction: row; -} - .resetButton { margin-right: 10px; /* Add margin to the right of the reset button */ opacity: 0; /* Make it invisible */ pointer-events: none; /* Make it unclickable */ } -.editButton { - margin-right: 0; /* Remove margin for the last child */ -} - .resetButton.visible { opacity: 1; /* Make it visible */ pointer-events: auto; /* Make it clickable */ } -.resetButton:hover, .editButton:hover { - color: #666; /* Slightly lighter color on hover */ - cursor: pointer; /* Show pointer cursor on hover */ -} - .resetButton:active, .editButton:active { color: #333; /* Darker color on click */ } - - - .ButtonRow { display: flex; gap: 6px; @@ -134,4 +106,24 @@ .SubreqButton.Selected { background-color: rgb(100, 178, 238); -} \ No newline at end of file +} + + + +.RequirementsContainerHeader { + display: flex; + flex-direction: row; + justify-content: space-between; + margin-bottom: 10px; + font-size: 30px; + padding: 2px 8px; +} + +.GoldBackground { + background-color: rgba(255, 215, 0, 0.3); /* Gold with 40% opacity */ + border-radius: 2px; +} + +.EditButton:hover { + cursor: pointer; /* Show pointer cursor on hover */ +} diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index 9337a83..295c9ff 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -7,11 +7,13 @@ import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentrat import { CourseIcon } from "@/components/course-icon/CourseIcon"; -function RenderSubrequirementCourse(props: { course: Course | null, subreq: ConcentrationSubrequirement }){ +function RenderSubrequirementCourse(props: { edit?: boolean, course: Course | null, subreq: ConcentrationSubrequirement }){ if(props.course === null){ return( -
+
+ {props.edit ? (
e
) : (
)} +
); } @@ -26,7 +28,7 @@ function RenderSubrequirementCourse(props: { course: Course | null, subreq: Conc ) } -function RenderSubrequirement(props: { subreq: ConcentrationSubrequirement }) { +function RenderSubrequirement(props: { edit: boolean, subreq: ConcentrationSubrequirement }) { const [showAll, setShowAll] = useState(false); // Separate null and non-null courses @@ -56,7 +58,11 @@ function RenderSubrequirement(props: { subreq: ConcentrationSubrequirement }) {
{displayedCourses.map((course, index) => (
- +
))} {/* Toggle Button to Expand / Collapse */} @@ -70,7 +76,7 @@ function RenderSubrequirement(props: { subreq: ConcentrationSubrequirement }) { ); } -function RenderRequirement(props: { req: ConcentrationRequirement }){ +function RenderRequirement(props: { edit: boolean, req: ConcentrationRequirement }){ // const { user, setUser } = useAuth(); // const { req, programIndex, degreeIndex } = props; @@ -144,23 +150,22 @@ function RenderRequirement(props: { req: ConcentrationRequirement }){
{subreqs_required_count ? subreqs_list.slice(0, subreqs_required_count).map((subreq, index) => ( - + )) : subreqs_list.map((subreq, index) => ( - + ))}
); } -function RequirementsContent(props: { conc: DegreeConcentration }) +function RequirementsContent(props: { edit: boolean, conc: DegreeConcentration }) { - return(
{props.conc.conc_reqs.map((req, index) => ( - + ))}
); @@ -168,32 +173,27 @@ function RequirementsContent(props: { conc: DegreeConcentration }) function Requirements(props: { conc: DegreeConcentration | null }) { - // const [edit, setEdit] = useState(false); - // const updateEdit = () => { - // setEdit(!edit); - // }; + const [edit, setEdit] = useState(false); - return( -
-
-
+ if(props.conc == null){ + return( +
+
Requirements
-
- {/*
*/} -
- ⚙ -
-
+
+ ) + } + + return( +
+
+
Requirements
+ {props.conc.user_status == 1 ? (
setEdit(!edit)}>⚙
) : (
)}
- {props.conc == null ? ( -
- ) : ( -
- -
- ) - } +
+ +
); } diff --git a/frontend/src/database/data-courses.ts b/frontend/src/database/data-courses.ts index d2deb8d..49f9010 100644 --- a/frontend/src/database/data-courses.ts +++ b/frontend/src/database/data-courses.ts @@ -19,6 +19,7 @@ export const CPSC_223: Course = { codes: ["CPSC 223"], title: "", credit: 1, dis export const CPSC_323: Course = { codes: ["CPSC 323"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } export const CPSC_365: Course = { codes: ["CPSC 365"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } export const CPSC_366: Course = { codes: ["CPSC 366"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_381: Course = { codes: ["CPSC 381"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } export const CPSC_490: Course = { codes: ["CPSC 490"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } // ECON PROGRAM @@ -47,9 +48,10 @@ export const ECON_136: Course = { codes: ["ECON 136"], title: "", credit: 1, dis // CPSC COURSES export const SC_CPSC_201: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_201 } -export const SC_CPSC_202: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_202 } +// export const SC_CPSC_202: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_202 } export const SC_CPSC_223: StudentCourse = { term: 202501, status: "DA", result: "GRADE_PASS", course: CPSC_223 } -export const SC_CPSC_323: StudentCourse = { term: 202503, status: "MA", result: "IP", course: CPSC_323 } +export const SC_CPSC_323: StudentCourse = { term: 202503, status: "DA", result: "GRADE_PASS", course: CPSC_323 } +export const SC_CPSC_381: StudentCourse = { term: 202503, status: "MA", result: "IP", course: CPSC_381 } // ECON COURSES export const SC_ECON_110: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: ECON_110 } diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index bf2f7f3..6c6438a 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -15,8 +15,8 @@ export const Ryan: User = { senior: [0, 202703, 202801], }, languagePlacement: { language: "Spanish", level: 5 }, - programs: [PROG_CPSC, PROG_ECON, PROG_HIST, PROG_PLSC], - declarations: [], + prog_list: [PROG_CPSC, PROG_ECON, PROG_HIST, PROG_PLSC], + decl_list: [{ user_status: 1, majors_index: { conc: 0, deg: 0, prog: 0 } }], } } diff --git a/frontend/src/database/programs/concs/concs-cpsc.ts b/frontend/src/database/programs/concs/concs-cpsc.ts index 97f3144..6b025d6 100644 --- a/frontend/src/database/programs/concs/concs-cpsc.ts +++ b/frontend/src/database/programs/concs/concs-cpsc.ts @@ -1,21 +1,21 @@ import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; -import { CPSC_201, CPSC_202, MATH_244, CPSC_223, CPSC_323, CPSC_365, CPSC_366, CPSC_490, SC_CPSC_201, SC_CPSC_223, SC_CPSC_323 } from "../../data-courses"; +import { CPSC_201, CPSC_202, MATH_244, CPSC_223, CPSC_323, CPSC_365, CPSC_366, CPSC_381, CPSC_490, SC_CPSC_201, SC_CPSC_223, SC_CPSC_323, SC_CPSC_381 } from "../../data-courses"; // CORE -const CORE_SUB_INTRO: ConcentrationSubrequirement = { +const CORE_INTRO: ConcentrationSubrequirement = { subreq_name: "INTRO", subreq_desc: "", courses_required: 1, courses_options: [CPSC_201], courses_elective_range: null, courses_any_bool: false, - student_courses_satisfying: [SC_CPSC_201], + student_courses_satisfying: [], } -const CORE_SUB_MATH: ConcentrationSubrequirement = { +const CORE_MATH: ConcentrationSubrequirement = { subreq_name: "DISCRETE MATH", subreq_desc: "", courses_required: 1, @@ -25,7 +25,7 @@ const CORE_SUB_MATH: ConcentrationSubrequirement = { student_courses_satisfying: [], } -const CORE_SUB_DATA: ConcentrationSubrequirement = { +const CORE_DATA: ConcentrationSubrequirement = { subreq_name: "DATA STRUCTURES", subreq_desc: "", courses_required: 1, @@ -35,7 +35,7 @@ const CORE_SUB_DATA: ConcentrationSubrequirement = { student_courses_satisfying: [SC_CPSC_223], } -const CORE_SUB_SYSTEMS: ConcentrationSubrequirement = { +const CORE_SYS: ConcentrationSubrequirement = { subreq_name: "SYSTEMS", subreq_desc: "", courses_required: 1, @@ -45,7 +45,7 @@ const CORE_SUB_SYSTEMS: ConcentrationSubrequirement = { student_courses_satisfying: [SC_CPSC_323], } -const CORE_SUB_ALGOS: ConcentrationSubrequirement = { +const CORE_ALGO: ConcentrationSubrequirement = { subreq_name: "ALGORITHMS", subreq_desc: "", courses_required: 1, @@ -59,8 +59,8 @@ const CPSC_CORE: ConcentrationRequirement = { req_name: "CORE", req_desc: "", courses_required_count: 5, - courses_satisfied_count: 3, - subreqs_list: [CORE_SUB_INTRO, CORE_SUB_MATH, CORE_SUB_DATA, CORE_SUB_SYSTEMS, CORE_SUB_ALGOS] + courses_satisfied_count: 2, + subreqs_list: [CORE_INTRO, CORE_MATH, CORE_DATA, CORE_SYS, CORE_ALGO] } // ELECTIVE @@ -69,20 +69,20 @@ const ELEC_MULT_BA: ConcentrationSubrequirement = { subreq_name: "", subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", courses_required: 3, - courses_options: [null, null, null], + courses_options: [CPSC_381, null, null], courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, courses_any_bool: false, - student_courses_satisfying: [] + student_courses_satisfying: [SC_CPSC_381] } const ELEC_MULT_BS: ConcentrationSubrequirement = { subreq_name: "", subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", courses_required: 5, - courses_options: [null, null, null, null, null], + courses_options: [CPSC_381, null, null, null, null], courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, courses_any_bool: false, - student_courses_satisfying: [] + student_courses_satisfying: [SC_CPSC_381] } const ELEC_SUB: ConcentrationSubrequirement = { @@ -99,7 +99,7 @@ const CPSC_BA_ELEC: ConcentrationRequirement = { req_name: "ELECTIVE", req_desc: "", courses_required_count: 4, - courses_satisfied_count: 0, + courses_satisfied_count: 1, subreqs_list: [ELEC_SUB, ELEC_MULT_BA] } @@ -107,7 +107,7 @@ const CPSC_BS_ELEC: ConcentrationRequirement = { req_name: "ELECTIVE", req_desc: "", courses_required_count: 6, - courses_satisfied_count: 0, + courses_satisfied_count: 1, subreqs_list: [ELEC_SUB, ELEC_MULT_BS] } @@ -134,12 +134,14 @@ const CPSC_SENIOR: ConcentrationRequirement = { // EXPORT export const CONC_CPSC_BA_I: DegreeConcentration = { + user_status: 1, conc_name: "", conc_desc: "", conc_reqs: [CPSC_CORE, CPSC_BA_ELEC, CPSC_SENIOR] } export const CONC_CPSC_BS_I: DegreeConcentration = { + user_status: 0, conc_name: "", conc_desc: "", conc_reqs: [CPSC_CORE, CPSC_BS_ELEC, CPSC_SENIOR] diff --git a/frontend/src/database/programs/concs/concs-econ.ts b/frontend/src/database/programs/concs/concs-econ.ts index f7e89fd..ec1ca3e 100644 --- a/frontend/src/database/programs/concs/concs-econ.ts +++ b/frontend/src/database/programs/concs/concs-econ.ts @@ -136,6 +136,7 @@ const ECON_SEN: ConcentrationRequirement = { // // FINAL export const CONC_ECON_BA_I: DegreeConcentration = { + user_status: 0, conc_name: "", conc_desc: "", conc_reqs: [ECON_INTRO, ECON_CORE, ECON_ELECTIVE, ECON_SEN] diff --git a/frontend/src/database/programs/concs/concs-hist.ts b/frontend/src/database/programs/concs/concs-hist.ts index 1cfa69a..218b063 100644 --- a/frontend/src/database/programs/concs/concs-hist.ts +++ b/frontend/src/database/programs/concs/concs-hist.ts @@ -221,12 +221,14 @@ const HIST_SEN: ConcentrationRequirement = { // EXPORT export const CONC_HIST_BA_GLOB: DegreeConcentration = { + user_status: 0, conc_name: "GLOBALIST", conc_desc: "", conc_reqs: [HIST_PRE, HIST_GLOB_CORE, HIST_SEM, HIST_GLOB_ELEC, HIST_SEN] } export const CONC_HIST_BA_SPEC: DegreeConcentration = { + user_status: 0, conc_name: "SPECIALIST", conc_desc: "", conc_reqs: [HIST_PRE, HIST_SPEC_CORE, HIST_SEM, HIST_SPEC_ELEC, HIST_SEN] diff --git a/frontend/src/database/programs/concs/concs-plsc.ts b/frontend/src/database/programs/concs/concs-plsc.ts index 422947f..06c0054 100644 --- a/frontend/src/database/programs/concs/concs-plsc.ts +++ b/frontend/src/database/programs/concs/concs-plsc.ts @@ -208,12 +208,14 @@ const PLSC_SEN_INTE: ConcentrationRequirement = { // EXPORT export const CONC_PLSC_BA_STAN: DegreeConcentration = { + user_status: 0, conc_name: "STANDARD", conc_desc: "", conc_reqs: [PLSC_INTRO, PLSC_CORE_STAN, PLSC_SUB, PLSC_SEMINAR, PLSC_SEN_STAN] } export const CONC_PLSC_BA_INTE: DegreeConcentration = { + user_status: 0, conc_name: "INTENSIVE", conc_desc: "", conc_reqs: [PLSC_INTRO, PLSC_CORE_INTE, PLSC_SUB, PLSC_SEMINAR, PLSC_SEN_INTE] diff --git a/frontend/src/types/type-program.ts b/frontend/src/types/type-program.ts index e930036..018981d 100644 --- a/frontend/src/types/type-program.ts +++ b/frontend/src/types/type-program.ts @@ -1,12 +1,6 @@ import { Course, StudentCourse } from "./type-user"; -export interface StudentDegree { - status: string; // DA | ADD | PIN - programIndex: number; - degreeIndex: number; -} - export interface ElectiveRange { dept: string; min_code: number; @@ -42,6 +36,7 @@ export interface ConcentrationRequirement { } export interface DegreeConcentration { + user_status: number; conc_name: string; conc_desc: string; conc_reqs: ConcentrationRequirement[]; diff --git a/frontend/src/types/type-user.ts b/frontend/src/types/type-user.ts index 85f4a23..3700ec7 100644 --- a/frontend/src/types/type-user.ts +++ b/frontend/src/types/type-user.ts @@ -1,5 +1,5 @@ -import { Program, StudentDegree } from "./type-program"; +import { MajorsIndex, Program } from "./type-program"; export interface LanguagePlacement { language: string; @@ -38,12 +38,17 @@ export interface StudentTermArrangement { senior: number[]; } +export interface StudentConc { + user_status: number; + majors_index: MajorsIndex; +} + export interface FYP { languagePlacement: LanguagePlacement; studentCourses: StudentCourse[]; studentTermArrangement: StudentTermArrangement; - programs: Program[]; - declarations: StudentDegree[]; + prog_list: Program[]; + decl_list: StudentConc[]; } export interface User { From 05d88bff1c0bec283aede37693dcd1de1ba587d5 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Tue, 11 Mar 2025 17:26:54 -0700 Subject: [PATCH 21/38] local majors icons --- .../courses/years/semester/SemesterBox.tsx | 2 +- .../years/semester/course/CourseBox.tsx | 2 +- .../course-icon/MajorsCourseIcon.module.css | 24 +++++ .../majors/course-icon/MajorsCourseIcon.tsx | 88 +++++++++++++++++++ frontend/src/app/majors/page.tsx | 2 +- .../app/majors/requirements/Requirements.tsx | 7 +- .../src/components/course-icon/CourseIcon.tsx | 2 +- frontend/src/database/data-courses.ts | 2 +- .../src/database/programs/concs/concs-cpsc.ts | 2 +- frontend/src/types/type-program.ts | 2 + .../CourseDisplay.module.css | 0 .../{ => course-display}/CourseDisplay.tsx | 0 .../src/utils/preprocessing/BackendAdapt.ts | 1 + 13 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css create mode 100644 frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx rename frontend/src/utils/{ => course-display}/CourseDisplay.module.css (100%) rename frontend/src/utils/{ => course-display}/CourseDisplay.tsx (100%) create mode 100644 frontend/src/utils/preprocessing/BackendAdapt.ts diff --git a/frontend/src/app/courses/years/semester/SemesterBox.tsx b/frontend/src/app/courses/years/semester/SemesterBox.tsx index 5cc3625..6823900 100644 --- a/frontend/src/app/courses/years/semester/SemesterBox.tsx +++ b/frontend/src/app/courses/years/semester/SemesterBox.tsx @@ -3,7 +3,7 @@ import React, {useState, useEffect} from "react"; import Style from "./SemesterBox.module.css" import { StudentSemester, User } from "@/types/type-user"; -import { TransformTermNumber, IsTermActive } from "@/utils/CourseDisplay"; +import { TransformTermNumber, IsTermActive } from "@/utils/course-display/CourseDisplay"; import CourseBox from "./course/CourseBox"; import AddCourseButton from "./add-course/AddCourseButton"; diff --git a/frontend/src/app/courses/years/semester/course/CourseBox.tsx b/frontend/src/app/courses/years/semester/course/CourseBox.tsx index 7c460ec..336dd46 100644 --- a/frontend/src/app/courses/years/semester/course/CourseBox.tsx +++ b/frontend/src/app/courses/years/semester/course/CourseBox.tsx @@ -2,7 +2,7 @@ import Style from "./CourseBox.module.css"; import { User, StudentCourse } from "@/types/type-user"; -import { RenderMark, SeasonIcon, GetCourseColor, IsTermActive } from "./../../../../../utils/CourseDisplay"; +import { RenderMark, SeasonIcon, GetCourseColor, IsTermActive } from "../../../../../utils/course-display/CourseDisplay"; import DistributionCircle from "@/components/distribution-circle/DistributionsCircle"; // import { useModal } from "../../../hooks/modalContext"; diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css b/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css new file mode 100644 index 0000000..a4dac89 --- /dev/null +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css @@ -0,0 +1,24 @@ + +.CourseIcon { + display: flex; + flex-direction: row; + + align-items: center; + + border-radius: 15px; + + width: max-content; + padding: 2px 4px; + min-height: 18px; + + font-size: 14px; + font-weight: bold; + + background-color: #F5F5F5; + transition: filter 0.4s ease; +} + +.CourseIcon:hover { + cursor: pointer; + filter: brightness(95%); +} diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx new file mode 100644 index 0000000..ecb401c --- /dev/null +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx @@ -0,0 +1,88 @@ + +import React from "react"; +import Style from "./MajorsCourseIcon.module.css" + +import { StudentCourse, Course } from "@/types/type-user"; +import { RenderMark, GetCourseColor } from "@/utils/course-display/CourseDisplay"; + +import DistributionCircle from "@/components/distribution-circle/DistributionsCircle"; + + +function CourseSeasonIcon(props: { seasons: Array }) { + const seasonImageMap: { [key: string]: string } = { + "Fall": "./fall.svg", + "Spring": "./spring.svg", + }; + + return ( +
+ {props.seasons.map((szn, index) => ( +
0 ? "-7.5px" : 0 }}> + {seasonImageMap[szn] && ( + {szn} + )} +
+ ))} +
+ ); +} + + +function DistCircDiv(props: { dist: string[] }) +{ + if(!Array.isArray(props.dist) || props.dist.length === 0){ + return( +
+ +
+ ); + } + + return( +
+ +
+ ); +} + + +export function StudentCourseIcon(props: { studentCourse: StudentCourse, utilityButton?: React.ReactNode }) { + + const dist = props.studentCourse.course.dist || []; + + // style={{ backgroundColor: GetCourseColor(props.studentCourse.term) }} + + return ( +
+ {props.utilityButton && props.utilityButton} + {props.studentCourse.status === "" + ? + : + } + {props.studentCourse.course.codes[0]} + {/* */} +
+ ); +} + + +export function CourseIcon(props: { course: Course, studentCourse?: StudentCourse }){ + + if(props.studentCourse){ + return( + + ); + } + + return( +
+ + {props.course.codes[0]} + +
+ ); +} diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index 415a612..487f486 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -49,7 +49,7 @@ function Majors()
updateIndex({ ...index, conc: -1 })}/>
- +
); diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index 295c9ff..e323fcf 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -4,8 +4,7 @@ import Style from "./Requirements.module.css"; import { Course } from "@/types/type-user"; import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; -import { CourseIcon } from "@/components/course-icon/CourseIcon"; - +import { CourseIcon } from "../course-icon/MajorsCourseIcon"; function RenderSubrequirementCourse(props: { edit?: boolean, course: Course | null, subreq: ConcentrationSubrequirement }){ @@ -160,7 +159,7 @@ function RenderRequirement(props: { edit: boolean, req: ConcentrationRequirement ); } -function RequirementsContent(props: { edit: boolean, conc: DegreeConcentration }) +function RequirementsList(props: { edit: boolean, conc: DegreeConcentration }) { return(
@@ -192,7 +191,7 @@ function Requirements(props: { conc: DegreeConcentration | null }) {props.conc.user_status == 1 ? (
setEdit(!edit)}>⚙
) : (
)}
- +
); diff --git a/frontend/src/components/course-icon/CourseIcon.tsx b/frontend/src/components/course-icon/CourseIcon.tsx index 31eb639..fd16aae 100644 --- a/frontend/src/components/course-icon/CourseIcon.tsx +++ b/frontend/src/components/course-icon/CourseIcon.tsx @@ -3,7 +3,7 @@ import React from "react"; import styles from "./CourseIcon.module.css"; import { StudentCourse, Course } from "@/types/type-user"; -import { RenderMark, GetCourseColor } from "@/utils/CourseDisplay"; +import { RenderMark, GetCourseColor } from "@/utils/course-display/CourseDisplay"; import DistributionCircle from "../distribution-circle/DistributionsCircle"; diff --git a/frontend/src/database/data-courses.ts b/frontend/src/database/data-courses.ts index 49f9010..e16787c 100644 --- a/frontend/src/database/data-courses.ts +++ b/frontend/src/database/data-courses.ts @@ -51,7 +51,7 @@ export const SC_CPSC_201: StudentCourse = { term: 202403, status: "DA", result: // export const SC_CPSC_202: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_202 } export const SC_CPSC_223: StudentCourse = { term: 202501, status: "DA", result: "GRADE_PASS", course: CPSC_223 } export const SC_CPSC_323: StudentCourse = { term: 202503, status: "DA", result: "GRADE_PASS", course: CPSC_323 } -export const SC_CPSC_381: StudentCourse = { term: 202503, status: "MA", result: "IP", course: CPSC_381 } +export const SC_CPSC_381: StudentCourse = { term: 202503, status: "DA", result: "GRADE_PASS", course: CPSC_381 } // ECON COURSES export const SC_ECON_110: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: ECON_110 } diff --git a/frontend/src/database/programs/concs/concs-cpsc.ts b/frontend/src/database/programs/concs/concs-cpsc.ts index 6b025d6..7095d2e 100644 --- a/frontend/src/database/programs/concs/concs-cpsc.ts +++ b/frontend/src/database/programs/concs/concs-cpsc.ts @@ -145,4 +145,4 @@ export const CONC_CPSC_BS_I: DegreeConcentration = { conc_name: "", conc_desc: "", conc_reqs: [CPSC_CORE, CPSC_BS_ELEC, CPSC_SENIOR] -} \ No newline at end of file +} diff --git a/frontend/src/types/type-program.ts b/frontend/src/types/type-program.ts index 018981d..62a1b5e 100644 --- a/frontend/src/types/type-program.ts +++ b/frontend/src/types/type-program.ts @@ -16,7 +16,9 @@ export interface ConcentrationSubrequirement { courses_required: number; courses_options: (Course | null)[]; courses_elective_range: SubreqElectiveRange; + courses_any_bool: boolean; + flags?: string[]; student_courses_satisfying: StudentCourse[]; } diff --git a/frontend/src/utils/CourseDisplay.module.css b/frontend/src/utils/course-display/CourseDisplay.module.css similarity index 100% rename from frontend/src/utils/CourseDisplay.module.css rename to frontend/src/utils/course-display/CourseDisplay.module.css diff --git a/frontend/src/utils/CourseDisplay.tsx b/frontend/src/utils/course-display/CourseDisplay.tsx similarity index 100% rename from frontend/src/utils/CourseDisplay.tsx rename to frontend/src/utils/course-display/CourseDisplay.tsx diff --git a/frontend/src/utils/preprocessing/BackendAdapt.ts b/frontend/src/utils/preprocessing/BackendAdapt.ts new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/frontend/src/utils/preprocessing/BackendAdapt.ts @@ -0,0 +1 @@ + From d728aadc0066e16b04907a140eb2e7eee2a03225 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Wed, 12 Mar 2025 11:03:39 -0700 Subject: [PATCH 22/38] remove majors icon --- .../course-icon/MajorsCourseIcon.module.css | 47 ++++++- .../majors/course-icon/MajorsCourseIcon.tsx | 119 +++++++++++------- frontend/src/app/majors/page.tsx | 2 +- .../app/majors/requirements/Requirements.tsx | 112 ++++++++--------- .../majors/requirements/RequirementsUtils.ts | 80 ++++++++++++ .../DistributionsCircle.tsx | 9 +- .../src/database/programs/concs/concs-econ.ts | 2 +- 7 files changed, 266 insertions(+), 105 deletions(-) create mode 100644 frontend/src/app/majors/requirements/RequirementsUtils.ts diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css b/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css index a4dac89..3529a73 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css @@ -1,5 +1,5 @@ -.CourseIcon { +.Icon { display: flex; flex-direction: row; @@ -18,7 +18,50 @@ transition: filter 0.4s ease; } -.CourseIcon:hover { +.Icon:hover { cursor: pointer; filter: brightness(95%); } + +.CourseIcon { + background-color: #F5F5F5; +} + +.StudentCourseIcon { + background-color: #E1E9F8; +} + +.EmptyIcon { + width: 18px; /* Ensure it's a perfect circle */ + height: 18px; + aspect-ratio: 1 / 1; /* Keeps it circular */ + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; /* Ensures a perfect circle */ + background-color: #f5f5f5; +} + +.EmptyIcon svg { + width: 10px; /* Increase size of "+" */ + height: 10px; +} + +.RemoveButton { + margin-right: 4px; + width: 14px; + height: 14px; + border-radius: 50%; + background-color: white; + color: black; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + font-weight: bold; + cursor: pointer; + /* position: absolute; + top: -5px; + right: -5px; + z-index: 10; */ +} diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx index ecb401c..bbd517a 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx @@ -1,9 +1,10 @@ +"use client"; import React from "react"; import Style from "./MajorsCourseIcon.module.css" +import { ConcentrationSubrequirement } from "@/types/type-program"; import { StudentCourse, Course } from "@/types/type-user"; -import { RenderMark, GetCourseColor } from "@/utils/course-display/CourseDisplay"; import DistributionCircle from "@/components/distribution-circle/DistributionsCircle"; @@ -31,58 +32,92 @@ function CourseSeasonIcon(props: { seasons: Array }) { ); } - -function DistCircDiv(props: { dist: string[] }) -{ - if(!Array.isArray(props.dist) || props.dist.length === 0){ - return( -
- -
- ); - } - - return( -
- +// ✅ Modify `MajorsCourseIcon` to handle remove clicks +function MajorsCourseIcon(props: { + edit: boolean; + course: Course; + subreq: ConcentrationSubrequirement; + onRemoveCourse: Function; +}) { + return ( +
+ {/* ✅ Only show remove button in edit mode */} + {props.edit && ( + props.onRemoveCourse(props.course, props.subreq, false)} /> + )} + + {props.course.codes[0]} +
); } +// ✅ Modify `MajorsStudentCourseIcon` to handle remove clicks +function MajorsStudentCourseIcon(props: { + edit: boolean; + studentCourse: StudentCourse; + subreq: ConcentrationSubrequirement; + onRemoveCourse: Function; +}) { + return ( +
+ {/* ✅ Only show remove button in edit mode */} + {props.edit && ( + props.onRemoveCourse(props.studentCourse.course, props.subreq, true)} /> + )} + ✓ {props.studentCourse.course.codes[0]} +
+ ); +} -export function StudentCourseIcon(props: { studentCourse: StudentCourse, utilityButton?: React.ReactNode }) { - - const dist = props.studentCourse.course.dist || []; - - // style={{ backgroundColor: GetCourseColor(props.studentCourse.term) }} - +// ✅ Modify `RemoveButton` to accept an `onClick` prop +function RemoveButton({ onClick }: { onClick: () => void }) { return ( -
- {props.utilityButton && props.utilityButton} - {props.studentCourse.status === "" - ? - : - } - {props.studentCourse.course.codes[0]} - {/* */} +
+ ❌ {/* Placeholder remove icon */}
); } +function MajorsEmptyIcon({ edit }: { edit: boolean }) { + return ( +
+ {edit && ( + + + + )} +
+ ); +} -export function CourseIcon(props: { course: Course, studentCourse?: StudentCourse }){ - - if(props.studentCourse){ - return( - - ); - } +export function MajorsIcon(props: { + edit: boolean; + contentCourse: Course | StudentCourse | null; + subreq: ConcentrationSubrequirement; + onRemoveCourse: Function; +}) { + // If no course exists, render the "Add" icon + if (!props.contentCourse) { + return ; + } - return( -
- - {props.course.codes[0]} - -
+ // ✅ Determine if `contentCourse` is a StudentCourse (i.e., has a `course` field inside) + const isStudentCourse = "course" in props.contentCourse; + + return isStudentCourse ? ( + + ) : ( + ); } diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index 487f486..5e793ee 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -49,7 +49,7 @@ function Majors()
updateIndex({ ...index, conc: -1 })}/>
- +
); diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index e323fcf..e9ff9da 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -1,51 +1,42 @@ +"use client"; import { useState } from "react"; +import { useAuth } from "@/app/providers"; import Style from "./Requirements.module.css"; import { Course } from "@/types/type-user"; -import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; -import { CourseIcon } from "../course-icon/MajorsCourseIcon"; +import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration, MajorsIndex } from "@/types/type-program"; -function RenderSubrequirementCourse(props: { edit?: boolean, course: Course | null, subreq: ConcentrationSubrequirement }){ +import { updateCourseInSubreq } from "./RequirementsUtils"; +import { MajorsIcon } from "../course-icon/MajorsCourseIcon"; - if(props.course === null){ - return( -
- {props.edit ? (
e
) : (
)} -
- ); - } - - const matchingStudentCourse = props.subreq.student_courses_satisfying.find( +function RenderSubrequirementCourse(props: { + edit?: boolean; + course: Course | null; + subreq: ConcentrationSubrequirement; + onRemoveCourse: Function; +}) +{ + // Find if this course exists in student_courses_satisfying (meaning it's a StudentCourse) + const matchingStudentCourse = props.subreq.student_courses_satisfying.find( (studentCourse) => studentCourse.course === props.course ); - return( -
- -
- ) + return ( +
+ {/* ✅ Pass `onRemoveCourse` down to `MajorsIcon`, along with subreq & whether it's a StudentCourse */} + +
+ ); } -function RenderSubrequirement(props: { edit: boolean, subreq: ConcentrationSubrequirement }) { - const [showAll, setShowAll] = useState(false); - - // Separate null and non-null courses - const nullCourses = props.subreq.courses_options.filter((course) => course === null); - const nonNullCourses = props.subreq.courses_options.filter((course) => course !== null) as Course[]; - const satisfiedCourses = props.subreq.student_courses_satisfying.map((studentCourse) => studentCourse.course); - - // Determine which courses to show - const isSatisfied = props.subreq.student_courses_satisfying.length === props.subreq.courses_required; - const displayedNonNullCourses = showAll - ? nonNullCourses - : isSatisfied - ? satisfiedCourses - : nonNullCourses.slice(0, 4); - - // Ensure null courses are always displayed - const displayedCourses = [...nullCourses, ...displayedNonNullCourses]; - +function RenderSubrequirement(props: { edit: boolean, subreq: ConcentrationSubrequirement, onRemoveCourse: Function }) +{ return (
@@ -55,27 +46,23 @@ function RenderSubrequirement(props: { edit: boolean, subreq: ConcentrationSubre {props.subreq.subreq_desc}
- {displayedCourses.map((course, index) => ( -
- -
+ {props.subreq.courses_options.map((course, index) => ( + ))} - {/* Toggle Button to Expand / Collapse */} - {nonNullCourses.length > 4 && ( -
setShowAll(!showAll)}> - {showAll ? "<<" : ">>"} -
- )}
); } -function RenderRequirement(props: { edit: boolean, req: ConcentrationRequirement }){ +function RenderRequirement(props: { edit: boolean, req: ConcentrationRequirement, onRemoveCourse: Function }) +{ // const { user, setUser } = useAuth(); // const { req, programIndex, degreeIndex } = props; @@ -149,31 +136,33 @@ function RenderRequirement(props: { edit: boolean, req: ConcentrationRequirement
{subreqs_required_count ? subreqs_list.slice(0, subreqs_required_count).map((subreq, index) => ( - + )) : subreqs_list.map((subreq, index) => ( - + ))}
); } -function RequirementsList(props: { edit: boolean, conc: DegreeConcentration }) +function RequirementsList(props: { edit: boolean, conc: DegreeConcentration, onRemoveCourse: Function }) { return(
{props.conc.conc_reqs.map((req, index) => ( - + ))}
); } -function Requirements(props: { conc: DegreeConcentration | null }) +function Requirements(props: { conc: DegreeConcentration | null, index: MajorsIndex }) { const [edit, setEdit] = useState(false); + const { user, setUser } = useAuth(); + if(props.conc == null){ return(
@@ -184,6 +173,15 @@ function Requirements(props: { conc: DegreeConcentration | null }) ) } + function onRemoveCourse(course: Course | null, subreq: ConcentrationSubrequirement, isStudentCourse: boolean = false) { + updateCourseInSubreq(user, setUser, props.index, subreq, course, "remove", isStudentCourse); + } + + // ✅ Handles adding a new course + // function onAddCourse(course: Course, subreq: ConcentrationSubrequirement) { + // updateCourseInSubreq(user, setUser, props.index, subreq, course, "add"); + // } + return(
@@ -191,7 +189,7 @@ function Requirements(props: { conc: DegreeConcentration | null }) {props.conc.user_status == 1 ? (
setEdit(!edit)}>⚙
) : (
)}
- +
); diff --git a/frontend/src/app/majors/requirements/RequirementsUtils.ts b/frontend/src/app/majors/requirements/RequirementsUtils.ts new file mode 100644 index 0000000..01d445a --- /dev/null +++ b/frontend/src/app/majors/requirements/RequirementsUtils.ts @@ -0,0 +1,80 @@ + +import { ConcentrationSubrequirement, MajorsIndex } from "@/types/type-program"; +import { Course, User } from "@/types/type-user"; + +export function updateCourseInSubreq( + user: User, + setUser: Function, + majorsIndex: MajorsIndex, + subreq: ConcentrationSubrequirement, + course: Course | null, + action: "add" | "remove", + isStudentCourse?: boolean +) { + setUser((prevUser: User) => { + const updatedUser = { ...prevUser }; // Clone user object + + // Locate the correct program, degree, and concentration + const program = updatedUser.FYP.prog_list[majorsIndex.prog]; + const degree = program.prog_degs[majorsIndex.deg]; + const concentration = degree.deg_concs[majorsIndex.conc]; + + // Locate the requirement that contains this subrequirement + const requirementIndex = concentration.conc_reqs.findIndex((req) => + req.subreqs_list.includes(subreq) + ); + + if (requirementIndex === -1) return prevUser; // Safety check + + // Create a **new** subrequirement list with modifications + const updatedSubreqs = concentration.conc_reqs[requirementIndex].subreqs_list.map((s) => { + if (s !== subreq) return s; // Keep other subreqs unchanged + + return { + ...s, + courses_options: [...s.courses_options].map((c) => (c === course ? null : c)), // Ensure a new array is created + student_courses_satisfying: isStudentCourse + ? [...s.student_courses_satisfying].filter((sc) => sc.course !== course) // Ensure a new array is created + : s.student_courses_satisfying, + }; + }); + + // Create a **new** requirement list with the updated subrequirement + const updatedConcReqs = concentration.conc_reqs.map((req, idx) => + idx === requirementIndex ? { ...req, subreqs_list: updatedSubreqs } : req + ); + + // Create a **new** concentration object with updated requirements + const updatedConcentration = { ...concentration, conc_reqs: updatedConcReqs }; + + // Create a **new** degree object with updated concentrations + const updatedDegree = { + ...degree, + deg_concs: degree.deg_concs.map((conc, idx) => + idx === majorsIndex.conc ? updatedConcentration : conc + ), + }; + + // Create a **new** program object with updated degrees + const updatedProgram = { + ...program, + prog_degs: program.prog_degs.map((deg, idx) => + idx === majorsIndex.deg ? updatedDegree : deg + ), + }; + + // Create a **new** user object with updated programs + const updatedUserFinal = { + ...updatedUser, + FYP: { + ...updatedUser.FYP, + prog_list: updatedUser.FYP.prog_list.map((prog, idx) => + idx === majorsIndex.prog ? updatedProgram : prog + ), + }, + }; + + return updatedUserFinal; + }); +} + diff --git a/frontend/src/components/distribution-circle/DistributionsCircle.tsx b/frontend/src/components/distribution-circle/DistributionsCircle.tsx index 3824bdc..b7db107 100644 --- a/frontend/src/components/distribution-circle/DistributionsCircle.tsx +++ b/frontend/src/components/distribution-circle/DistributionsCircle.tsx @@ -25,7 +25,12 @@ function getData(distributions: string[]) { const width = 12; const height = 12; -export default function DistributionCircle(props: { distributions: string[] }) { +export default function DistributionCircle(props: { distributions: string[] }) +{ + if(props.distributions.length == 0){ + return
; + } + const radius = Math.min(width, height) / 2; const data = useMemo(() => getData(props.distributions), [props.distributions]); @@ -48,7 +53,7 @@ export default function DistributionCircle(props: { distributions: string[] }) { return (
- + {arcs.map((arc: string | null, i: number) => arc ? : null diff --git a/frontend/src/database/programs/concs/concs-econ.ts b/frontend/src/database/programs/concs/concs-econ.ts index ec1ca3e..91e813f 100644 --- a/frontend/src/database/programs/concs/concs-econ.ts +++ b/frontend/src/database/programs/concs/concs-econ.ts @@ -9,7 +9,7 @@ const INTRO_MATH: ConcentrationSubrequirement = { subreq_name: "MATH", subreq_desc: "118 or 120 recommended. Any MATH 200+ satisfies.", courses_required: 1, - courses_options: [MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151], + courses_options: [MATH_118, MATH_120], courses_elective_range: null, courses_any_bool: false, student_courses_satisfying: [], From bb586e2e3d8435345cbdd2a756892499049e7509 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Thu, 13 Mar 2025 11:42:11 -0700 Subject: [PATCH 23/38] add popup --- .../course-icon/MajorsCourseIcon.module.css | 68 ++++--- .../majors/course-icon/MajorsCourseIcon.tsx | 65 +++++-- frontend/src/app/majors/page.tsx | 2 +- .../requirements/Requirements.module.css | 31 +-- .../app/majors/requirements/Requirements.tsx | 141 ++++---------- .../majors/requirements/RequirementsUtils.ts | 178 ++++++++++++------ 6 files changed, 254 insertions(+), 231 deletions(-) diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css b/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css index 3529a73..c580b22 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css @@ -31,37 +31,61 @@ background-color: #E1E9F8; } -.EmptyIcon { - width: 18px; /* Ensure it's a perfect circle */ - height: 18px; - aspect-ratio: 1 / 1; /* Keeps it circular */ - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; /* Ensures a perfect circle */ - background-color: #f5f5f5; -} - -.EmptyIcon svg { - width: 10px; /* Increase size of "+" */ - height: 10px; -} - .RemoveButton { margin-right: 4px; width: 14px; height: 14px; border-radius: 50%; - background-color: white; - color: black; + background-color: rgb(237, 237, 237); display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: bold; cursor: pointer; - /* position: absolute; - top: -5px; - right: -5px; - z-index: 10; */ +} + + + + + + + + +.IconContainer { + position: relative; + display: inline-block; +} + +.EmptyIcon { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background-color: #f5f5f5; + cursor: default; +} + +.EmptyIcon:hover { + cursor: pointer; + background-color: #e0e0e0; +} + +.AddCoursePopup { + position: absolute; + top: 22px; + left: 50%; + transform: translateX(-50%); + background: white; + border: 1px solid #ddd; + padding: 6px; + box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + gap: 5px; + border-radius: 5px; + z-index: 10; + min-width: 120px; } diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx index bbd517a..efc7361 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx @@ -1,6 +1,5 @@ -"use client"; -import React from "react"; +import { useState, useRef, useEffect } from "react"; import Style from "./MajorsCourseIcon.module.css" import { ConcentrationSubrequirement } from "@/types/type-program"; @@ -32,7 +31,6 @@ function CourseSeasonIcon(props: { seasons: Array }) { ); } -// ✅ Modify `MajorsCourseIcon` to handle remove clicks function MajorsCourseIcon(props: { edit: boolean; course: Course; @@ -52,7 +50,6 @@ function MajorsCourseIcon(props: { ); } -// ✅ Modify `MajorsStudentCourseIcon` to handle remove clicks function MajorsStudentCourseIcon(props: { edit: boolean; studentCourse: StudentCourse; @@ -70,27 +67,71 @@ function MajorsStudentCourseIcon(props: { ); } -// ✅ Modify `RemoveButton` to accept an `onClick` prop function RemoveButton({ onClick }: { onClick: () => void }) { return (
- ❌ {/* Placeholder remove icon */} +
); } -function MajorsEmptyIcon({ edit }: { edit: boolean }) { +function MajorsEmptyIcon(props: { edit: boolean }) { + const [isAdding, setIsAdding] = useState(false); + const [courseCode, setCourseCode] = useState(""); + const popupRef = useRef(null); + const inputRef = useRef(null); + + // 🔹 Close input box when clicking outside + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (popupRef.current && !popupRef.current.contains(event.target as Node)) { + setIsAdding(false); + } + } + + if (isAdding) { + document.addEventListener("mousedown", handleClickOutside); + } + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [isAdding]); + + // 🔹 Auto-focus input when it appears + useEffect(() => { + if (isAdding) { + inputRef.current?.focus(); + } + }, [isAdding]); + return ( -
- {edit && ( - - - +
{/* ✅ Keeps relative positioning */} + {props.edit ? ( + <> +
setIsAdding(true)}>+
+ + {isAdding && ( +
+ setCourseCode(e.target.value)} + /> +
+ )} + + ) : ( +
// ✅ Non-editable version stays a simple gray circle )}
); } + + + export function MajorsIcon(props: { edit: boolean; contentCourse: Course | StudentCourse | null; diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index 5e793ee..0def287 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -49,7 +49,7 @@ function Majors()
updateIndex({ ...index, conc: -1 })}/>
- +
); diff --git a/frontend/src/app/majors/requirements/Requirements.module.css b/frontend/src/app/majors/requirements/Requirements.module.css index f7fcb74..b0680e7 100644 --- a/frontend/src/app/majors/requirements/Requirements.module.css +++ b/frontend/src/app/majors/requirements/Requirements.module.css @@ -18,6 +18,7 @@ .ReqsList { border: 1px solid white; + margin-left: 30px; /* height: 430px; scrollbar-color: rgb(131, 131, 131) transparent; @@ -45,20 +46,7 @@ font-style: italic; font-size: 12px; font-weight: 500; -} - -.EmptyCourse { - border-radius: 15px; - background-color: #F5F5F5; - transition: filter 0.4s ease; - width: 24px; /* Adjust width to match CourseIcon */ - height: 24px; /* Adjust height to match CourseIcon */ - display: inline-block; /* Keeps it inline with other elements */ -} - -.EmptyCourse:hover { - cursor: pointer; - filter: brightness(95%); + margin-bottom: 4px; } .ToggleButton { @@ -71,21 +59,6 @@ font-size: 14px; } -.resetButton { - margin-right: 10px; /* Add margin to the right of the reset button */ - opacity: 0; /* Make it invisible */ - pointer-events: none; /* Make it unclickable */ -} - -.resetButton.visible { - opacity: 1; /* Make it visible */ - pointer-events: auto; /* Make it clickable */ -} - -.resetButton:active, .editButton:active { - color: #333; /* Darker color on click */ -} - .ButtonRow { display: flex; gap: 6px; diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index e9ff9da..0d45fa7 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -1,30 +1,24 @@ "use client"; -import { useState } from "react"; import { useAuth } from "@/app/providers"; + +import { useState } from "react"; import Style from "./Requirements.module.css"; import { Course } from "@/types/type-user"; import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration, MajorsIndex } from "@/types/type-program"; -import { updateCourseInSubreq } from "./RequirementsUtils"; +import { removeCourseInSubreq } from "./RequirementsUtils"; import { MajorsIcon } from "../course-icon/MajorsCourseIcon"; -function RenderSubrequirementCourse(props: { - edit?: boolean; - course: Course | null; - subreq: ConcentrationSubrequirement; - onRemoveCourse: Function; -}) +function RenderSubrequirementCourse(props: { edit?: boolean; course: Course | null; subreq: ConcentrationSubrequirement; onRemoveCourse: Function }) { - // Find if this course exists in student_courses_satisfying (meaning it's a StudentCourse) const matchingStudentCourse = props.subreq.student_courses_satisfying.find( (studentCourse) => studentCourse.course === props.course ); return (
- {/* ✅ Pass `onRemoveCourse` down to `MajorsIcon`, along with subreq & whether it's a StudentCourse */}
@@ -46,14 +45,14 @@ function RenderSubrequirement(props: { edit: boolean, subreq: ConcentrationSubre {props.subreq.subreq_desc}
- {props.subreq.courses_options.map((course, index) => ( + {props.subreq.courses_options.map((course, course_index) => ( ))}
@@ -61,52 +60,9 @@ function RenderSubrequirement(props: { edit: boolean, subreq: ConcentrationSubre ); } -function RenderRequirement(props: { edit: boolean, req: ConcentrationRequirement, onRemoveCourse: Function }) +function RenderRequirement(props: { edit: boolean, majorsIndex: MajorsIndex, reqIndex: number, req: ConcentrationRequirement }) { - - // const { user, setUser } = useAuth(); - // const { req, programIndex, degreeIndex } = props; - const { subreqs_list, subreqs_required_count } = props.req; - - // // Get the correct degree configuration (assumes only one degree per program) - // const degreeConfig = user.FYP.degreeConfigurations[programIndex][0]; - - // Find the corresponding requirement in `degreeConfig` - // const requirement = degreeConfig[0].reqs_list.find((r: DegreeRequirement) => r.req_name === req.req_name); - - // if (!requirement) return null; // Fail-safe, shouldn't happen - - // Move clicked subreq to the front if it's beyond the first `subreqs_required_count` - // const handleSubreqClick = (subreq: DegreeSubrequirement) => { - // if (!subreqs_required_count) return; // Ignore clicks if not applicable - - // setUser((prevUser: User) => { - // const newUser = { ...prevUser }; - - // // Get the degree and requirement again inside state update - // const updatedDegree = newUser.FYP.degreeConfigurations[programIndex][degreeIndex]; - // const updatedRequirement = updatedDegree[0].reqs_list.find((r) => r.req_name === req.req_name); // FIXXX - - // if (!updatedRequirement) return prevUser; // Failsafe - - // const updatedSubreqs = [...updatedRequirement.subreqs_list]; - // const index = updatedSubreqs.findIndex((s) => s.subreq_name === subreq.subreq_name); - - // if (index >= subreqs_required_count) { - // // Move it to the front - // updatedSubreqs.splice(index, 1); - // updatedSubreqs.unshift(subreq); - // } - - // // Update the requirement's subreqs_list in user state - // updatedRequirement.subreqs_list = updatedSubreqs; - - // return newUser; - // }); - // }; - - - return ( + return(
@@ -116,53 +72,29 @@ function RenderRequirement(props: { edit: boolean, req: ConcentrationRequirement {props.req.checkbox !== undefined ? props.req.courses_satisfied_count === props.req.courses_required_count ? "✅" : "❌" : `${props.req.courses_satisfied_count}|${props.req.courses_required_count}`}
- -
+
{props.req.req_desc}
- - {/* Subreq Toggle Buttons - Only show if subreqs_required_count exists and < total subreqs */} - {/* {subreqs_required_count && subreqs_list.length > subreqs_required_count && ( -
- {subreqs_list.map((subreq, index) => ( -
handleSubreqClick(subreq)}> - {subreq.subreq_name} -
- ))} -
- )} */} - - {/* Display Selected Subreqs - Enforce subreqs_required_count if present */} -
- {subreqs_required_count - ? subreqs_list.slice(0, subreqs_required_count).map((subreq, index) => ( - - )) - : subreqs_list.map((subreq, index) => ( - - ))} +
+ {props.req.subreqs_list.map((subreq, subreq_index) => ( + + ))}
); } -function RequirementsList(props: { edit: boolean, conc: DegreeConcentration, onRemoveCourse: Function }) -{ - return( -
- {props.conc.conc_reqs.map((req, index) => ( - - ))} -
- ); -} - -function Requirements(props: { conc: DegreeConcentration | null, index: MajorsIndex }) +function Requirements(props: { conc: DegreeConcentration | null, majorsIndex: MajorsIndex }) { const [edit, setEdit] = useState(false); - const { user, setUser } = useAuth(); - if(props.conc == null){ return(
@@ -173,23 +105,16 @@ function Requirements(props: { conc: DegreeConcentration | null, index: MajorsIn ) } - function onRemoveCourse(course: Course | null, subreq: ConcentrationSubrequirement, isStudentCourse: boolean = false) { - updateCourseInSubreq(user, setUser, props.index, subreq, course, "remove", isStudentCourse); - } - - // ✅ Handles adding a new course - // function onAddCourse(course: Course, subreq: ConcentrationSubrequirement) { - // updateCourseInSubreq(user, setUser, props.index, subreq, course, "add"); - // } - return(
Requirements
- {props.conc.user_status == 1 ? (
setEdit(!edit)}>⚙
) : (
)} + {props.conc.user_status == 1 &&
setEdit(!edit)}>⚙
}
-
- +
+ {props.conc.conc_reqs.map((req, i) => ( + + ))}
); diff --git a/frontend/src/app/majors/requirements/RequirementsUtils.ts b/frontend/src/app/majors/requirements/RequirementsUtils.ts index 01d445a..54cb564 100644 --- a/frontend/src/app/majors/requirements/RequirementsUtils.ts +++ b/frontend/src/app/majors/requirements/RequirementsUtils.ts @@ -1,80 +1,140 @@ -import { ConcentrationSubrequirement, MajorsIndex } from "@/types/type-program"; +import { MajorsIndex } from "@/types/type-program"; import { Course, User } from "@/types/type-user"; +import { ConcentrationSubrequirement } from "@/types/type-program"; -export function updateCourseInSubreq( - user: User, - setUser: Function, +function updateUserWithNewSubreq( + prevUser: User, majorsIndex: MajorsIndex, - subreq: ConcentrationSubrequirement, - course: Course | null, - action: "add" | "remove", - isStudentCourse?: boolean -) { - setUser((prevUser: User) => { - const updatedUser = { ...prevUser }; // Clone user object + reqIndex: number, + subreqIndex: number, + updatedSubreq: ConcentrationSubrequirement +): User { + const updatedUser = { ...prevUser }; + + // Directly access the correct structures using indices + const program = updatedUser.FYP.prog_list[majorsIndex.prog]; + const degree = program.prog_degs[majorsIndex.deg]; + const concentration = degree.deg_concs[majorsIndex.conc]; + const requirement = concentration.conc_reqs[reqIndex]; + + // ✅ Modify only the affected subrequirement + const updatedSubreqs = [...requirement.subreqs_list]; + updatedSubreqs[subreqIndex] = updatedSubreq; + + // ✅ Modify only the affected requirement + const updatedConcReqs = [...concentration.conc_reqs]; + updatedConcReqs[reqIndex] = { ...requirement, subreqs_list: updatedSubreqs }; + + // ✅ Modify only the affected concentration + const updatedConcentration = { ...concentration, conc_reqs: updatedConcReqs }; - // Locate the correct program, degree, and concentration - const program = updatedUser.FYP.prog_list[majorsIndex.prog]; + // ✅ Modify only the affected degree + const updatedDegree = { + ...degree, + deg_concs: degree.deg_concs.map((conc, idx) => + idx === majorsIndex.conc ? updatedConcentration : conc + ) + }; + + // ✅ Modify only the affected program + const updatedProgram = { + ...program, + prog_degs: program.prog_degs.map((deg, idx) => + idx === majorsIndex.deg ? updatedDegree : deg + ) + }; + + // ✅ Modify only the affected user object + return { + ...updatedUser, + FYP: { + ...updatedUser.FYP, + prog_list: updatedUser.FYP.prog_list.map((prog, idx) => + idx === majorsIndex.prog ? updatedProgram : prog + ) + } + }; +} + +export function addCourseInSubreq( + setUser: Function, + majorsIndex: MajorsIndex, + reqIndex: number, + subreqIndex: number, + courseCode: string +): boolean { + setUser((prevUser: User) => { + const program = prevUser.FYP.prog_list[majorsIndex.prog]; const degree = program.prog_degs[majorsIndex.deg]; const concentration = degree.deg_concs[majorsIndex.conc]; + const requirement = concentration.conc_reqs[reqIndex]; + const subreq = requirement.subreqs_list[subreqIndex]; - // Locate the requirement that contains this subrequirement - const requirementIndex = concentration.conc_reqs.findIndex((req) => - req.subreqs_list.includes(subreq) + // ✅ Find the StudentCourse that matches the course code + const matchingStudentCourse = prevUser.FYP.studentCourses.find((studentCourse) => + studentCourse.course.codes.includes(courseCode) ); - if (requirementIndex === -1) return prevUser; // Safety check + if (!matchingStudentCourse) return prevUser; // 🚨 No update if course doesn't exist - // Create a **new** subrequirement list with modifications - const updatedSubreqs = concentration.conc_reqs[requirementIndex].subreqs_list.map((s) => { - if (s !== subreq) return s; // Keep other subreqs unchanged + // ✅ Find the first null spot in courses_options + const updatedCoursesOptions = [...subreq.courses_options]; + const firstNullIndex = updatedCoursesOptions.indexOf(null); + if (firstNullIndex === -1) return prevUser; // 🚨 No space available, no update - return { - ...s, - courses_options: [...s.courses_options].map((c) => (c === course ? null : c)), // Ensure a new array is created - student_courses_satisfying: isStudentCourse - ? [...s.student_courses_satisfying].filter((sc) => sc.course !== course) // Ensure a new array is created - : s.student_courses_satisfying, - }; - }); - - // Create a **new** requirement list with the updated subrequirement - const updatedConcReqs = concentration.conc_reqs.map((req, idx) => - idx === requirementIndex ? { ...req, subreqs_list: updatedSubreqs } : req - ); + updatedCoursesOptions[firstNullIndex] = matchingStudentCourse.course; // Replace null with new course - // Create a **new** concentration object with updated requirements - const updatedConcentration = { ...concentration, conc_reqs: updatedConcReqs }; + // ✅ Add the StudentCourse to `student_courses_satisfying` + const updatedStudentCourses = [...subreq.student_courses_satisfying, matchingStudentCourse]; - // Create a **new** degree object with updated concentrations - const updatedDegree = { - ...degree, - deg_concs: degree.deg_concs.map((conc, idx) => - idx === majorsIndex.conc ? updatedConcentration : conc - ), + // ✅ Create the updated subrequirement + const updatedSubreq = { + ...subreq, + courses_options: updatedCoursesOptions, + student_courses_satisfying: updatedStudentCourses }; - // Create a **new** program object with updated degrees - const updatedProgram = { - ...program, - prog_degs: program.prog_degs.map((deg, idx) => - idx === majorsIndex.deg ? updatedDegree : deg - ), - }; + return updateUserWithNewSubreq(prevUser, majorsIndex, reqIndex, subreqIndex, updatedSubreq); + }); + + return true; // ✅ Successfully added course +} + +export function removeCourseInSubreq( + setUser: Function, + majorsIndex: MajorsIndex, + reqIndex: number, + subreqIndex: number, + course: Course | null, + isStudentCourse?: boolean +) { + setUser((prevUser: User) => { + const program = prevUser.FYP.prog_list[majorsIndex.prog]; + const degree = program.prog_degs[majorsIndex.deg]; + const concentration = degree.deg_concs[majorsIndex.conc]; + const requirement = concentration.conc_reqs[reqIndex]; + const subreq = requirement.subreqs_list[subreqIndex]; + + // ✅ Modify `courses_options` directly + const updatedCoursesOptions = [...subreq.courses_options]; + const courseIndex = updatedCoursesOptions.indexOf(course); + if (courseIndex !== -1) { + updatedCoursesOptions[courseIndex] = null; + } - // Create a **new** user object with updated programs - const updatedUserFinal = { - ...updatedUser, - FYP: { - ...updatedUser.FYP, - prog_list: updatedUser.FYP.prog_list.map((prog, idx) => - idx === majorsIndex.prog ? updatedProgram : prog - ), - }, + // ✅ Filter out StudentCourse if necessary + const updatedStudentCourses = isStudentCourse + ? subreq.student_courses_satisfying.filter((sc) => sc.course !== course) + : subreq.student_courses_satisfying; + + // ✅ Create the updated subrequirement + const updatedSubreq = { + ...subreq, + courses_options: updatedCoursesOptions, + student_courses_satisfying: updatedStudentCourses }; - return updatedUserFinal; + return updateUserWithNewSubreq(prevUser, majorsIndex, reqIndex, subreqIndex, updatedSubreq); }); } - From 577130e3db6647703b66b50489e8d971b76d6e4f Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Thu, 13 Mar 2025 14:45:13 -0700 Subject: [PATCH 24/38] add majors course func --- .../course-icon/MajorsCourseIcon.module.css | 17 ++++++- .../majors/course-icon/MajorsCourseIcon.tsx | 44 +++++++++++-------- .../requirements/Requirements.module.css | 4 +- .../app/majors/requirements/Requirements.tsx | 13 ++++-- .../majors/requirements/RequirementsUtils.ts | 8 +++- frontend/src/app/providers.tsx | 5 ++- frontend/src/database/data-user.ts | 3 +- 7 files changed, 63 insertions(+), 31 deletions(-) diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css b/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css index c580b22..d54253f 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css @@ -84,8 +84,21 @@ box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); display: flex; align-items: center; - gap: 5px; border-radius: 5px; z-index: 10; - min-width: 120px; + width: fit-content; + min-width: 60px; +} + +.AddCoursePopup input { + width: 75px; + padding: 2px; + border: 1px solid #ccc; + border-radius: 4px; + font-size: 12px; +} + +.ConfirmButton { + cursor: pointer; + color: green; } diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx index efc7361..468bf0e 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx @@ -75,21 +75,22 @@ function RemoveButton({ onClick }: { onClick: () => void }) { ); } -function MajorsEmptyIcon(props: { edit: boolean }) { +function MajorsEmptyIcon(props: { edit: boolean, onAddCourse: Function }) +{ const [isAdding, setIsAdding] = useState(false); const [courseCode, setCourseCode] = useState(""); const popupRef = useRef(null); const inputRef = useRef(null); - // 🔹 Close input box when clicking outside useEffect(() => { function handleClickOutside(event: MouseEvent) { if (popupRef.current && !popupRef.current.contains(event.target as Node)) { setIsAdding(false); + setCourseCode(""); } } - if (isAdding) { + if(isAdding){ document.addEventListener("mousedown", handleClickOutside); } @@ -98,49 +99,54 @@ function MajorsEmptyIcon(props: { edit: boolean }) { }; }, [isAdding]); - // 🔹 Auto-focus input when it appears useEffect(() => { if (isAdding) { inputRef.current?.focus(); } }, [isAdding]); - return ( + function handleAddCourse() { + const success = props.onAddCourse(courseCode); + if(success){ + setIsAdding(false); + setCourseCode(""); + } + } + + return(
{/* ✅ Keeps relative positioning */} {props.edit ? ( - <> -
setIsAdding(true)}>+
+
+
setIsAdding(true)}> + + +
{isAdding && (
- setCourseCode(e.target.value)} - /> + setCourseCode(e.target.value)}/> +
)} - +
) : ( -
// ✅ Non-editable version stays a simple gray circle +
+ +
)}
); } - - - export function MajorsIcon(props: { edit: boolean; contentCourse: Course | StudentCourse | null; subreq: ConcentrationSubrequirement; onRemoveCourse: Function; + onAddCourse: Function; }) { // If no course exists, render the "Add" icon if (!props.contentCourse) { - return ; + return ; } // ✅ Determine if `contentCourse` is a StudentCourse (i.e., has a `course` field inside) diff --git a/frontend/src/app/majors/requirements/Requirements.module.css b/frontend/src/app/majors/requirements/Requirements.module.css index b0680e7..2afe981 100644 --- a/frontend/src/app/majors/requirements/Requirements.module.css +++ b/frontend/src/app/majors/requirements/Requirements.module.css @@ -93,8 +93,8 @@ } .GoldBackground { - background-color: rgba(255, 215, 0, 0.3); /* Gold with 40% opacity */ - border-radius: 2px; + /* background-color: rgba(255, 215, 0, 0.3); + border-radius: 2px; */ } .EditButton:hover { diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index 0d45fa7..9329609 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -8,10 +8,10 @@ import Style from "./Requirements.module.css"; import { Course } from "@/types/type-user"; import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration, MajorsIndex } from "@/types/type-program"; -import { removeCourseInSubreq } from "./RequirementsUtils"; +import { removeCourseInSubreq, addCourseInSubreq } from "./RequirementsUtils"; import { MajorsIcon } from "../course-icon/MajorsCourseIcon"; -function RenderSubrequirementCourse(props: { edit?: boolean; course: Course | null; subreq: ConcentrationSubrequirement; onRemoveCourse: Function }) +function RenderSubrequirementCourse(props: { edit?: boolean; course: Course | null; subreq: ConcentrationSubrequirement; onRemoveCourse: Function, onAddCourse: Function }) { const matchingStudentCourse = props.subreq.student_courses_satisfying.find( (studentCourse) => studentCourse.course === props.course @@ -24,6 +24,7 @@ function RenderSubrequirementCourse(props: { edit?: boolean; course: Course | nu contentCourse={matchingStudentCourse ?? props.course} subreq={props.subreq} onRemoveCourse={props.onRemoveCourse} + onAddCourse={props.onAddCourse} />
); @@ -32,10 +33,15 @@ function RenderSubrequirementCourse(props: { edit?: boolean; course: Course | nu function RenderSubrequirement(props: { edit: boolean, majorsIndex: MajorsIndex, reqIndex: number, subreqIndex: number, subreq: ConcentrationSubrequirement }) { const { setUser } = useAuth(); - function handleRemoveCourse(course: Course | null, isStudentCourse: boolean = false) { + + function handleRemoveCourse(course: Course | null, isStudentCourse: boolean = false){ removeCourseInSubreq(setUser, props.majorsIndex, props.reqIndex, props.subreqIndex, course, isStudentCourse); } + function handleAddCourse(courseCode: string){ + return addCourseInSubreq(setUser, props.majorsIndex, props.reqIndex, props.subreqIndex, courseCode); + } + return (
@@ -53,6 +59,7 @@ function RenderSubrequirement(props: { edit: boolean, majorsIndex: MajorsIndex, edit={props.edit} // {...(props.subreq.courses_any_bool ? { edit: props.edit } : {})} onRemoveCourse={handleRemoveCourse} + onAddCourse={handleAddCourse} /> ))}
diff --git a/frontend/src/app/majors/requirements/RequirementsUtils.ts b/frontend/src/app/majors/requirements/RequirementsUtils.ts index 54cb564..b1b4ca8 100644 --- a/frontend/src/app/majors/requirements/RequirementsUtils.ts +++ b/frontend/src/app/majors/requirements/RequirementsUtils.ts @@ -64,6 +64,8 @@ export function addCourseInSubreq( subreqIndex: number, courseCode: string ): boolean { + let success = false; // ✅ Track success state + setUser((prevUser: User) => { const program = prevUser.FYP.prog_list[majorsIndex.prog]; const degree = program.prog_degs[majorsIndex.deg]; @@ -83,7 +85,7 @@ export function addCourseInSubreq( const firstNullIndex = updatedCoursesOptions.indexOf(null); if (firstNullIndex === -1) return prevUser; // 🚨 No space available, no update - updatedCoursesOptions[firstNullIndex] = matchingStudentCourse.course; // Replace null with new course + updatedCoursesOptions[firstNullIndex] = matchingStudentCourse.course; // ✅ Replace null with new course // ✅ Add the StudentCourse to `student_courses_satisfying` const updatedStudentCourses = [...subreq.student_courses_satisfying, matchingStudentCourse]; @@ -95,10 +97,12 @@ export function addCourseInSubreq( student_courses_satisfying: updatedStudentCourses }; + // ✅ Update state with new data + success = true; // ✅ Mark operation as successful return updateUserWithNewSubreq(prevUser, majorsIndex, reqIndex, subreqIndex, updatedSubreq); }); - return true; // ✅ Successfully added course + return success; // ✅ Now properly returns success/failure } export function removeCourseInSubreq( diff --git a/frontend/src/app/providers.tsx b/frontend/src/app/providers.tsx index 020a3cb..a1e9310 100644 --- a/frontend/src/app/providers.tsx +++ b/frontend/src/app/providers.tsx @@ -3,11 +3,12 @@ import { createContext, useContext, useState, useEffect } from "react"; import { User } from "@/types/type-user"; -import { NullUser, Ryan } from "@/database/data-user"; +import { Ryan } from "@/database/data-user"; const AuthContext = createContext(null); -export function AuthProvider({ children }: { children: React.ReactNode }) { +export function AuthProvider({ children }: { children: React.ReactNode }) +{ const [auth, setAuth] = useState({ loggedIn: false }); const [user, setUser] = useState(Ryan); diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index 6c6438a..ed59de3 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -1,13 +1,14 @@ import { User } from "./../types/type-user"; import { PROG_CPSC, PROG_ECON, PROG_HIST, PROG_PLSC } from "./programs/data-program"; +import { SC_CPSC_201 } from "./data-courses"; export const Ryan: User = { name: "Ryan", netID: "rgg32", onboard: false, FYP: { - studentCourses: [], + studentCourses: [SC_CPSC_201], studentTermArrangement: { first_year: [0, 202403, 202501], sophomore: [0, 202503, 202601], From 0586b757fb38131ac890b4a1460824f4e2b0f749 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Fri, 14 Mar 2025 14:05:23 -0700 Subject: [PATCH 25/38] program provider --- frontend/src/app/courses/page.tsx | 2 +- frontend/src/app/layout.tsx | 7 +- frontend/src/app/login/page.tsx | 2 +- frontend/src/app/majors/Majors.module.css | 2 +- .../course-icon/MajorsCourseIcon.module.css | 28 ++-- .../majors/course-icon/MajorsCourseIcon.tsx | 19 +-- frontend/src/app/majors/metadata/Metadata.tsx | 148 ++++++++++-------- .../src/app/majors/metadata/MetadataUtils.ts | 47 ++++++ .../src/app/majors/overhead/pinned/Pinned.tsx | 4 +- frontend/src/app/majors/page.tsx | 22 ++- .../requirements/Requirements.module.css | 15 +- .../app/majors/requirements/Requirements.tsx | 114 +++++++++----- .../majors/requirements/RequirementsUtils.ts | 127 ++++++--------- frontend/src/app/not-found.tsx | 2 +- frontend/src/app/page.tsx | 2 +- .../AuthProvider.tsx} | 3 +- frontend/src/context/ProgramProvider.tsx | 27 ++++ frontend/src/database/data-user.ts | 39 ++--- .../src/database/programs/concs/concs-cpsc.ts | 2 +- .../src/database/programs/data-program.ts | 12 +- frontend/src/types/type-user.ts | 7 +- 21 files changed, 367 insertions(+), 264 deletions(-) create mode 100644 frontend/src/app/majors/metadata/MetadataUtils.ts rename frontend/src/{app/providers.tsx => context/AuthProvider.tsx} (92%) create mode 100644 frontend/src/context/ProgramProvider.tsx diff --git a/frontend/src/app/courses/page.tsx b/frontend/src/app/courses/page.tsx index c237f74..554107c 100644 --- a/frontend/src/app/courses/page.tsx +++ b/frontend/src/app/courses/page.tsx @@ -3,7 +3,7 @@ import React, { useState, useEffect } from "react"; import Style from "./Courses.module.css"; -import { useAuth } from "../providers"; +import { useAuth } from "@/context/AuthProvider"; import { StudentYear } from "@/types/type-user"; import { BuildStudentYears } from "./CoursesUtils"; diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index ec5a50e..4f0bf4c 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,6 +1,7 @@ import "./globals.css"; -import { AuthProvider } from "./providers"; +import { AuthProvider } from "@/context/AuthProvider"; +import { ProgramProvider } from "@/context/ProgramProvider"; export const metadata = { title: "MajorAudit" @@ -12,7 +13,9 @@ export default function RootLayout({children}: {children: React.ReactNode}) - {children} + + {children} + diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx index 4eb3ab4..81263e4 100644 --- a/frontend/src/app/login/page.tsx +++ b/frontend/src/app/login/page.tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/navigation"; import Style from "./Login.module.css"; -import { useAuth } from "../providers"; +import { useAuth } from "@/context/AuthProvider"; import NavBar from "@/components/navbar/NavBar"; function Login() diff --git a/frontend/src/app/majors/Majors.module.css b/frontend/src/app/majors/Majors.module.css index 24bbae8..42209e4 100644 --- a/frontend/src/app/majors/Majors.module.css +++ b/frontend/src/app/majors/Majors.module.css @@ -23,7 +23,7 @@ height: calc(100% - 100px); } -.EditButton { +.ListButton { position: fixed; top: 95px; diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css b/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css index d54253f..13a33a4 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css @@ -1,18 +1,22 @@ + /* */ + /* min-height: 18px; */ + .Icon { display: flex; flex-direction: row; - + align-items: center; + justify-content: center; - border-radius: 15px; + font-size: 14px; + font-weight: bold; width: max-content; - padding: 2px 4px; - min-height: 18px; + height: max-content; - font-size: 14px; - font-weight: bold; + padding: 2px 4px; + border-radius: 15px; background-color: #F5F5F5; transition: filter 0.4s ease; @@ -23,20 +27,16 @@ filter: brightness(95%); } -.CourseIcon { - background-color: #F5F5F5; -} - .StudentCourseIcon { background-color: #E1E9F8; } .RemoveButton { - margin-right: 4px; - width: 14px; - height: 14px; + margin-right: 3px; + width: 15px; + height: 15px; border-radius: 50%; - background-color: rgb(237, 237, 237); + background-color: rgb(255, 255, 255); display: flex; align-items: center; justify-content: center; diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx index 468bf0e..dcff863 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx @@ -38,14 +38,13 @@ function MajorsCourseIcon(props: { onRemoveCourse: Function; }) { return ( -
- {/* ✅ Only show remove button in edit mode */} +
{props.edit && ( props.onRemoveCourse(props.course, props.subreq, false)} /> )} - + {props.course.codes[0]} - +
); } @@ -114,11 +113,11 @@ function MajorsEmptyIcon(props: { edit: boolean, onAddCourse: Function }) } return( -
{/* ✅ Keeps relative positioning */} +
{props.edit ? ( -
-
setIsAdding(true)}> - + + <> +
setIsAdding(true)}> +
{isAdding && ( @@ -127,7 +126,7 @@ function MajorsEmptyIcon(props: { edit: boolean, onAddCourse: Function })
)} -
+ ) : (
@@ -144,12 +143,10 @@ export function MajorsIcon(props: { onRemoveCourse: Function; onAddCourse: Function; }) { - // If no course exists, render the "Add" icon if (!props.contentCourse) { return ; } - // ✅ Determine if `contentCourse` is a StudentCourse (i.e., has a `course` field inside) const isStudentCourse = "course" in props.contentCourse; return isStudentCourse ? ( diff --git a/frontend/src/app/majors/metadata/Metadata.tsx b/frontend/src/app/majors/metadata/Metadata.tsx index d9212c1..14013f1 100644 --- a/frontend/src/app/majors/metadata/Metadata.tsx +++ b/frontend/src/app/majors/metadata/Metadata.tsx @@ -4,19 +4,29 @@ import Style from "./Metadata.module.css"; import Link from 'next/link'; import { MajorsIndex, Program } from "@/types/type-program"; +import { usePrograms } from "@/context/ProgramProvider"; +import { useAuth } from "@/context/AuthProvider"; -function MetadataTopshelf(props: { program: Program }) -{ - return ( +import { toggleConcentrationPin } from "./MetadataUtils"; + +function MetadataTopshelf(props: { + program: Program; + index: MajorsIndex; +}){ + const { setUser } = useAuth(); + const { progList } = usePrograms(); + + function handlePinClick() { + toggleConcentrationPin(setUser, progList, props.index); + } + + return(
- {/*
pinProgram(props.programIndex, 0, props.user, props.setUser)}> - -
-
addProgram(props.programIndex, 0, props.user, props.setUser)}> - -
*/}
+
+ 📌 +
{props.program.prog_data.prog_name}
@@ -74,34 +84,11 @@ function MetadataTopshelf(props: { program: Program }) // ); // } -function MetadataBody(props: { program: Program, index: MajorsIndex }){ - - return( - // style={{ marginLeft: "80px" }} -
- {/* */} -
- ABOUT -
-
- {props.program.prog_degs[props.index.deg].deg_concs[props.index.conc].conc_desc} -
-
- DUS -
-
- {props.program.prog_data.prog_dus.dus_name}; {props.program.prog_data.prog_dus.dus_email} -
-
-
MAJOR CATALOG
-
MAJOR WEBSITE
-
-
- ); -} - -function MetadataToggle(props: { program: Program, index: MajorsIndex, setIndex: Function }) -{ +function MetadataToggle(props: { + program: Program, + index: MajorsIndex, + setIndex: Function +}){ return (
@@ -134,19 +121,40 @@ function MetadataToggle(props: { program: Program, index: MajorsIndex, setIndex: ); } -function MetadataContent(props: { program: Program, index: MajorsIndex, setIndex: Function }) -{ - return ( -
- - - -
- ); +function MetadataBody(props: { + program: Program, + index: MajorsIndex +}){ + return( + // style={{ marginLeft: "80px" }} +
+ {/* */} +
+ ABOUT +
+
+ {props.program.prog_degs[props.index.deg].deg_concs[props.index.conc].conc_desc} +
+
+ DUS +
+
+ {props.program.prog_data.prog_dus.dus_name}; {props.program.prog_data.prog_dus.dus_email} +
+
+
MAJOR CATALOG
+
MAJOR WEBSITE
+
+
+ ); } -function MetadataScrollButton(props: { programs: Program[], index: MajorsIndex, setIndex: Function; dir: number }) -{ +function MetadataScrollButton(props: { + programs: Program[], + index: MajorsIndex, + setIndex: Function; + dir: number +}){ return(
props.setIndex({ conc: 0, deg: 0, prog: props.index.prog + props.dir })}>
@@ -163,36 +171,46 @@ function MetadataScrollButton(props: { programs: Program[], index: MajorsIndex, ); } -function ProgramContent(props: { programs: Program[], index: MajorsIndex, setIndex: Function }){ - return( -
- - - -
- ) -} - -function ProgramList(props: { programs: Program[], setIndex: Function }){ +function ProgramList(props: { + programs: Program[], + setIndex: Function +}){ return(
{props.programs.map((program: Program, prog_index: number) => ( -
props.setIndex({ conc: 0, deg: 0, prog: prog_index })}> - {program.prog_data.prog_name} {program.prog_data.prog_abbr} +
props.setIndex({ conc: 0, deg: 0, prog: prog_index })} + > + {program.prog_data.prog_name} {program.prog_data.prog_abbr}
))}
) } -function Metadata(props: { programs: Program[], index: MajorsIndex, setIndex: Function }) -{ +function Metadata(props: { + index: MajorsIndex, + setIndex: Function +}){ + const { progList } = usePrograms(); + const currProgram = progList[props.index.prog]; + return(
{props.index.conc == -1 ? ( - + ) : ( - +
+ +
+ + + +
+ +
) }
diff --git a/frontend/src/app/majors/metadata/MetadataUtils.ts b/frontend/src/app/majors/metadata/MetadataUtils.ts new file mode 100644 index 0000000..ca25948 --- /dev/null +++ b/frontend/src/app/majors/metadata/MetadataUtils.ts @@ -0,0 +1,47 @@ + +import { User, StudentConc } from "@/types/type-user"; +import { Program, MajorsIndex } from "@/types/type-program"; + +export function toggleConcentrationPin( + setUser: Function, + progList: Program[], + majorsIndex: MajorsIndex +) { + setUser((prevUser: User) => { + const existingConcIndex = prevUser.FYP.decl_list.findIndex( + (sc) => + sc.conc_majors_index.prog === majorsIndex.prog && + sc.conc_majors_index.deg === majorsIndex.deg && + sc.conc_majors_index.conc === majorsIndex.conc + ); + + // ✅ If already pinned, remove it (unpin) + if (existingConcIndex !== -1) { + return { + ...prevUser, + FYP: { + ...prevUser.FYP, + decl_list: prevUser.FYP.decl_list.filter((_, idx) => idx !== existingConcIndex), + }, + }; + } + + // ✅ Otherwise, pin it + const newConc = progList[majorsIndex.prog].prog_degs[majorsIndex.deg].deg_concs[majorsIndex.conc]; + + const newStudentConc: StudentConc = { + conc_majors_index: majorsIndex, + user_status: 1, // Assume 1 means pinned + user_conc: { ...newConc }, // Clone conc so changes don't affect `progList` + user_conc_name: newConc.conc_name, + }; + + return { + ...prevUser, + FYP: { + ...prevUser.FYP, + decl_list: [...prevUser.FYP.decl_list, newStudentConc], // Append to `decl_list` + }, + }; + }); +} diff --git a/frontend/src/app/majors/overhead/pinned/Pinned.tsx b/frontend/src/app/majors/overhead/pinned/Pinned.tsx index 08c163c..f5fd67b 100644 --- a/frontend/src/app/majors/overhead/pinned/Pinned.tsx +++ b/frontend/src/app/majors/overhead/pinned/Pinned.tsx @@ -6,8 +6,8 @@ import { User, StudentConc } from "@/types/type-user"; function ConcIcon(props: { user: User, setIndex: Function, studentConc: StudentConc }) { return( -
props.setIndex(props.studentConc.majors_index)}> - 📌{props.user.FYP.prog_list[props.studentConc.majors_index.prog].prog_data.prog_abbr} +
props.setIndex(props.studentConc.conc_majors_index)}> + 📌{props.studentConc.user_conc_name}
); } diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index 0def287..ebcfb27 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -1,10 +1,13 @@ "use client"; +import { useAuth } from "@/context/AuthProvider"; +import { usePrograms } from "@/context/ProgramProvider"; + import { useState, useEffect } from "react"; -import { useAuth } from "../providers"; +import Style from "./Majors.module.css"; + import { MajorsIndex } from "@/types/type-program"; -import Style from "./Majors.module.css"; import NavBar from "@/components/navbar/NavBar"; import Overhead from "./overhead/Overhead"; import Metadata from "./metadata/Metadata"; @@ -13,6 +16,7 @@ import Requirements from "./requirements/Requirements"; function Majors() { const { user } = useAuth(); + const { progList } = usePrograms(); const [index, setIndex] = useState(null); @@ -30,26 +34,30 @@ function Majors() }, [index]); const updateIndex = (newIndex: MajorsIndex) => { + console.log("old"); + console.log(newIndex); setIndex((prev) => ({ ...prev!, ...newIndex, prog: newIndex.prog !== undefined - ? (newIndex.prog + user.FYP.prog_list.length) % user.FYP.prog_list.length + ? (newIndex.prog + progList.length) % progList.length : prev!.prog, conc: newIndex.conc === -1 ? (prev!.conc === -1 ? 0 : -1) : newIndex.conc, })); + console.log("new"); + console.log(index); }; if(index === null) return null; return(
- }/> + }/>
-
updateIndex({ ...index, conc: -1 })}/> - +
updateIndex({ ...index, conc: -1 })}/> +
- +
); diff --git a/frontend/src/app/majors/requirements/Requirements.module.css b/frontend/src/app/majors/requirements/Requirements.module.css index 2afe981..96dbaa7 100644 --- a/frontend/src/app/majors/requirements/Requirements.module.css +++ b/frontend/src/app/majors/requirements/Requirements.module.css @@ -17,16 +17,17 @@ } .ReqsList { - border: 1px solid white; + /* border-bottom: 1px solid grey; */ margin-left: 30px; -/* - height: 430px; + + /* height: 750px; scrollbar-color: rgb(131, 131, 131) transparent; scrollbar-width: thin; - overflow-y: scroll; - padding-right: 7px; - overflow-x: hidden; -*/ + overflow-y: scroll; */ +} + +.SubreqsList { + margin-left: 30px; } .ReqHeader { diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index 9329609..d647a40 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -1,18 +1,24 @@ "use client"; -import { useAuth } from "@/app/providers"; +import { useAuth } from "@/context/AuthProvider"; +import { usePrograms } from "@/context/ProgramProvider"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import Style from "./Requirements.module.css"; -import { Course } from "@/types/type-user"; -import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration, MajorsIndex } from "@/types/type-program"; +import { Course, StudentConc } from "@/types/type-user"; +import { ConcentrationSubrequirement, ConcentrationRequirement, MajorsIndex } from "@/types/type-program"; import { removeCourseInSubreq, addCourseInSubreq } from "./RequirementsUtils"; import { MajorsIcon } from "../course-icon/MajorsCourseIcon"; -function RenderSubrequirementCourse(props: { edit?: boolean; course: Course | null; subreq: ConcentrationSubrequirement; onRemoveCourse: Function, onAddCourse: Function }) -{ +function RenderSubrequirementCourse(props: { + edit?: boolean; + course: Course | null; + subreq: ConcentrationSubrequirement; + onRemoveCourse: Function, + onAddCourse: Function +}){ const matchingStudentCourse = props.subreq.student_courses_satisfying.find( (studentCourse) => studentCourse.course === props.course ); @@ -30,8 +36,13 @@ function RenderSubrequirementCourse(props: { edit?: boolean; course: Course | nu ); } -function RenderSubrequirement(props: { edit: boolean, majorsIndex: MajorsIndex, reqIndex: number, subreqIndex: number, subreq: ConcentrationSubrequirement }) -{ +function RenderSubrequirement(props: { + edit: boolean, + majorsIndex: MajorsIndex, + reqIndex: number, + subreqIndex: number, + subreq: ConcentrationSubrequirement +}){ const { setUser } = useAuth(); function handleRemoveCourse(course: Course | null, isStudentCourse: boolean = false){ @@ -42,7 +53,14 @@ function RenderSubrequirement(props: { edit: boolean, majorsIndex: MajorsIndex, return addCourseInSubreq(setUser, props.majorsIndex, props.reqIndex, props.subreqIndex, courseCode); } - return ( + const filteredCourses = + props.subreq.student_courses_satisfying.length >= props.subreq.courses_required + ? props.subreq.courses_options.filter((course) => + props.subreq.student_courses_satisfying.some((sc) => sc.course === course) + ) + : props.subreq.courses_options; + + return(
{props.subreq.student_courses_satisfying.length}|{props.subreq.courses_required} {props.subreq.subreq_name} @@ -51,13 +69,12 @@ function RenderSubrequirement(props: { edit: boolean, majorsIndex: MajorsIndex, {props.subreq.subreq_desc}
- {props.subreq.courses_options.map((course, course_index) => ( + {filteredCourses.map((course, course_index) => ( @@ -67,8 +84,12 @@ function RenderSubrequirement(props: { edit: boolean, majorsIndex: MajorsIndex, ); } -function RenderRequirement(props: { edit: boolean, majorsIndex: MajorsIndex, reqIndex: number, req: ConcentrationRequirement }) -{ +function RenderRequirement(props: { + edit: boolean, + majorsIndex: MajorsIndex, + reqIndex: number, + req: ConcentrationRequirement +}){ return(
@@ -82,14 +103,14 @@ function RenderRequirement(props: { edit: boolean, majorsIndex: MajorsIndex, req
{props.req.req_desc}
-
- {props.req.subreqs_list.map((subreq, subreq_index) => ( +
+ {props.req.subreqs_list.map((subreq, i) => ( ))} @@ -98,31 +119,54 @@ function RenderRequirement(props: { edit: boolean, majorsIndex: MajorsIndex, req ); } -function Requirements(props: { conc: DegreeConcentration | null, majorsIndex: MajorsIndex }) -{ - const [edit, setEdit] = useState(false); - - if(props.conc == null){ +function Requirements(props: { + majorsIndex: MajorsIndex | null +}){ + if(props.majorsIndex == null){ return( -
-
- Requirements -
-
+
) } + const [edit, setEdit] = useState(false); + const { user } = useAuth(); + const { progList } = usePrograms(); + + useEffect(() => { + setEdit(false); + }, [props.majorsIndex]); + + const userConc = user.FYP.decl_list.find((sc: StudentConc) => + sc.conc_majors_index.prog === props.majorsIndex?.prog && + sc.conc_majors_index.deg === props.majorsIndex?.deg && + sc.conc_majors_index.conc === props.majorsIndex?.conc + ); + + const conc = userConc ? userConc.user_conc : progList[props.majorsIndex.prog].prog_degs[props.majorsIndex.deg].deg_concs[props.majorsIndex.conc]; + return(
-
-
Requirements
- {props.conc.user_status == 1 &&
setEdit(!edit)}>⚙
} +
+
+ Requirements +
+ {userConc && +
setEdit(!edit)}> + ⚙ +
+ }
- {props.conc.conc_reqs.map((req, i) => ( - - ))} -
+ {conc.conc_reqs.map((req: ConcentrationRequirement, reqIndex: number) => ( + + ))} +
); } diff --git a/frontend/src/app/majors/requirements/RequirementsUtils.ts b/frontend/src/app/majors/requirements/RequirementsUtils.ts index b1b4ca8..df9888f 100644 --- a/frontend/src/app/majors/requirements/RequirementsUtils.ts +++ b/frontend/src/app/majors/requirements/RequirementsUtils.ts @@ -10,50 +10,29 @@ function updateUserWithNewSubreq( subreqIndex: number, updatedSubreq: ConcentrationSubrequirement ): User { - const updatedUser = { ...prevUser }; - - // Directly access the correct structures using indices - const program = updatedUser.FYP.prog_list[majorsIndex.prog]; - const degree = program.prog_degs[majorsIndex.deg]; - const concentration = degree.deg_concs[majorsIndex.conc]; - const requirement = concentration.conc_reqs[reqIndex]; - - // ✅ Modify only the affected subrequirement - const updatedSubreqs = [...requirement.subreqs_list]; - updatedSubreqs[subreqIndex] = updatedSubreq; - - // ✅ Modify only the affected requirement - const updatedConcReqs = [...concentration.conc_reqs]; - updatedConcReqs[reqIndex] = { ...requirement, subreqs_list: updatedSubreqs }; - - // ✅ Modify only the affected concentration - const updatedConcentration = { ...concentration, conc_reqs: updatedConcReqs }; - - // ✅ Modify only the affected degree - const updatedDegree = { - ...degree, - deg_concs: degree.deg_concs.map((conc, idx) => - idx === majorsIndex.conc ? updatedConcentration : conc - ) - }; - - // ✅ Modify only the affected program - const updatedProgram = { - ...program, - prog_degs: program.prog_degs.map((deg, idx) => - idx === majorsIndex.deg ? updatedDegree : deg - ) - }; - - // ✅ Modify only the affected user object return { - ...updatedUser, + ...prevUser, FYP: { - ...updatedUser.FYP, - prog_list: updatedUser.FYP.prog_list.map((prog, idx) => - idx === majorsIndex.prog ? updatedProgram : prog - ) - } + ...prevUser.FYP, + decl_list: prevUser.FYP.decl_list.map((studentConc) => { + if( + studentConc.conc_majors_index.prog === majorsIndex.prog && + studentConc.conc_majors_index.deg === majorsIndex.deg && + studentConc.conc_majors_index.conc === majorsIndex.conc + ){ + const updatedConcReqs = studentConc.user_conc.conc_reqs.map((req, idx) => + idx === reqIndex + ? { ...req, subreqs_list: req.subreqs_list.map((subreq, sidx) => + sidx === subreqIndex ? updatedSubreq : subreq + ) } + : req + ); + + return { ...studentConc, user_conc: { ...studentConc.user_conc, conc_reqs: updatedConcReqs } }; + } + return studentConc; + }), + }, }; } @@ -63,46 +42,39 @@ export function addCourseInSubreq( reqIndex: number, subreqIndex: number, courseCode: string -): boolean { - let success = false; // ✅ Track success state - +) { setUser((prevUser: User) => { - const program = prevUser.FYP.prog_list[majorsIndex.prog]; - const degree = program.prog_degs[majorsIndex.deg]; - const concentration = degree.deg_concs[majorsIndex.conc]; - const requirement = concentration.conc_reqs[reqIndex]; + const userConc = prevUser.FYP.decl_list.find( + (sc) => sc.conc_majors_index.prog === majorsIndex.prog && + sc.conc_majors_index.deg === majorsIndex.deg && + sc.conc_majors_index.conc === majorsIndex.conc + ); + + if (!userConc) return prevUser; + + const requirement = userConc.user_conc.conc_reqs[reqIndex]; const subreq = requirement.subreqs_list[subreqIndex]; - // ✅ Find the StudentCourse that matches the course code - const matchingStudentCourse = prevUser.FYP.studentCourses.find((studentCourse) => - studentCourse.course.codes.includes(courseCode) + const matchingStudentCourse = prevUser.FYP.studentCourses.find((sc) => + sc.course.codes.includes(courseCode) ); - if (!matchingStudentCourse) return prevUser; // 🚨 No update if course doesn't exist + if (!matchingStudentCourse) return prevUser; - // ✅ Find the first null spot in courses_options const updatedCoursesOptions = [...subreq.courses_options]; const firstNullIndex = updatedCoursesOptions.indexOf(null); - if (firstNullIndex === -1) return prevUser; // 🚨 No space available, no update - - updatedCoursesOptions[firstNullIndex] = matchingStudentCourse.course; // ✅ Replace null with new course + if (firstNullIndex === -1) return prevUser; - // ✅ Add the StudentCourse to `student_courses_satisfying` - const updatedStudentCourses = [...subreq.student_courses_satisfying, matchingStudentCourse]; + updatedCoursesOptions[firstNullIndex] = matchingStudentCourse.course; - // ✅ Create the updated subrequirement const updatedSubreq = { ...subreq, courses_options: updatedCoursesOptions, - student_courses_satisfying: updatedStudentCourses + student_courses_satisfying: [...subreq.student_courses_satisfying, matchingStudentCourse], }; - // ✅ Update state with new data - success = true; // ✅ Mark operation as successful return updateUserWithNewSubreq(prevUser, majorsIndex, reqIndex, subreqIndex, updatedSubreq); }); - - return success; // ✅ Now properly returns success/failure } export function removeCourseInSubreq( @@ -114,25 +86,26 @@ export function removeCourseInSubreq( isStudentCourse?: boolean ) { setUser((prevUser: User) => { - const program = prevUser.FYP.prog_list[majorsIndex.prog]; - const degree = program.prog_degs[majorsIndex.deg]; - const concentration = degree.deg_concs[majorsIndex.conc]; - const requirement = concentration.conc_reqs[reqIndex]; + const userConc = prevUser.FYP.decl_list.find( + (sc) => sc.conc_majors_index.prog === majorsIndex.prog && + sc.conc_majors_index.deg === majorsIndex.deg && + sc.conc_majors_index.conc === majorsIndex.conc + ); + + if (!userConc) return prevUser; // 🚨 Safety check + + const requirement = userConc.user_conc.conc_reqs[reqIndex]; const subreq = requirement.subreqs_list[subreqIndex]; - // ✅ Modify `courses_options` directly - const updatedCoursesOptions = [...subreq.courses_options]; - const courseIndex = updatedCoursesOptions.indexOf(course); - if (courseIndex !== -1) { - updatedCoursesOptions[courseIndex] = null; - } + // ✅ Modify `courses_options` + const updatedCoursesOptions = subreq.courses_options.map((c) => (c === course ? null : c)); - // ✅ Filter out StudentCourse if necessary + // ✅ Modify `student_courses_satisfying` const updatedStudentCourses = isStudentCourse ? subreq.student_courses_satisfying.filter((sc) => sc.course !== course) : subreq.student_courses_satisfying; - // ✅ Create the updated subrequirement + // ✅ Create updated subrequirement const updatedSubreq = { ...subreq, courses_options: updatedCoursesOptions, diff --git a/frontend/src/app/not-found.tsx b/frontend/src/app/not-found.tsx index 0b7ddd6..f8da259 100644 --- a/frontend/src/app/not-found.tsx +++ b/frontend/src/app/not-found.tsx @@ -2,7 +2,7 @@ "use client"; import { useEffect } from "react"; import { useRouter } from "next/navigation"; -import { useAuth } from "@/app/providers"; +import { useAuth } from "@/context/AuthProvider"; export default function NotFoundPage() { const { auth } = useAuth(); diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 839aa68..b58e4b4 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -2,7 +2,7 @@ "use client"; import { useEffect } from "react"; import { useRouter } from "next/navigation"; -import { useAuth } from "./providers"; +import { useAuth } from "@/context/AuthProvider"; export default function MajorAudit() { diff --git a/frontend/src/app/providers.tsx b/frontend/src/context/AuthProvider.tsx similarity index 92% rename from frontend/src/app/providers.tsx rename to frontend/src/context/AuthProvider.tsx index a1e9310..18c0e5a 100644 --- a/frontend/src/app/providers.tsx +++ b/frontend/src/context/AuthProvider.tsx @@ -12,7 +12,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) const [auth, setAuth] = useState({ loggedIn: false }); const [user, setUser] = useState(Ryan); - // uh this isnt right useEffect(() => { setUser(Ryan); }, []); @@ -24,6 +23,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) ); } -export function useAuth() { +export function useAuth(){ return useContext(AuthContext); } diff --git a/frontend/src/context/ProgramProvider.tsx b/frontend/src/context/ProgramProvider.tsx new file mode 100644 index 0000000..fc19e32 --- /dev/null +++ b/frontend/src/context/ProgramProvider.tsx @@ -0,0 +1,27 @@ + +"use client"; +import { createContext, useContext, useState, useEffect } from "react"; +import { Program } from "@/types/type-program"; + +import { PROG_LIST } from "@/database/programs/data-program"; + +const ProgramContext = createContext<{ progList: Program[] }>({ progList: [] }); + +export function ProgramProvider({ children }: { children: React.ReactNode }) { + + const [progList, setProgList] = useState([]); + + useEffect(() => { + setProgList(PROG_LIST) + }, []); + + return( + + {children} + + ); +} + +export function usePrograms() { + return useContext(ProgramContext); +} diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index ed59de3..0b56db5 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -1,8 +1,9 @@ import { User } from "./../types/type-user"; -import { PROG_CPSC, PROG_ECON, PROG_HIST, PROG_PLSC } from "./programs/data-program"; import { SC_CPSC_201 } from "./data-courses"; +import { CONC_CPSC_BS_I } from "./programs/concs/concs-cpsc"; + export const Ryan: User = { name: "Ryan", netID: "rgg32", @@ -16,33 +17,13 @@ export const Ryan: User = { senior: [0, 202703, 202801], }, languagePlacement: { language: "Spanish", level: 5 }, - prog_list: [PROG_CPSC, PROG_ECON, PROG_HIST, PROG_PLSC], - decl_list: [{ user_status: 1, majors_index: { conc: 0, deg: 0, prog: 0 } }], + decl_list: [ + { + conc_majors_index: { conc: 0, deg: 1, prog: 0 }, // check + user_status: 1, + user_conc: CONC_CPSC_BS_I, + user_conc_name: "cpsc bs", + } + ], } } - -// export const NullUser: User = { -// name: "", -// netID: "", -// onboard: false, -// FYP: { -// studentCourses: [], -// studentTermArrangement: { -// first_year: [], -// sophomore: [], -// junior: [], -// senior: [], -// }, -// languagePlacement: { language: "", level: 0 }, -// degreeDeclarations: [], -// degreeConfigurations: [ -// [], -// [], -// [], -// [] -// ], -// } -// } - - - diff --git a/frontend/src/database/programs/concs/concs-cpsc.ts b/frontend/src/database/programs/concs/concs-cpsc.ts index 7095d2e..fafb5bd 100644 --- a/frontend/src/database/programs/concs/concs-cpsc.ts +++ b/frontend/src/database/programs/concs/concs-cpsc.ts @@ -11,7 +11,7 @@ const CORE_INTRO: ConcentrationSubrequirement = { courses_required: 1, courses_options: [CPSC_201], courses_elective_range: null, - courses_any_bool: false, + courses_any_bool: true, student_courses_satisfying: [], } diff --git a/frontend/src/database/programs/data-program.ts b/frontend/src/database/programs/data-program.ts index d56d96b..f590c22 100644 --- a/frontend/src/database/programs/data-program.ts +++ b/frontend/src/database/programs/data-program.ts @@ -5,7 +5,7 @@ import { CONC_ECON_BA_I } from "./concs/concs-econ"; import { CONC_HIST_BA_GLOB, CONC_HIST_BA_SPEC } from "./concs/concs-hist"; import { CONC_PLSC_BA_INTE, CONC_PLSC_BA_STAN } from "./concs/concs-plsc"; -export const PROG_CPSC: Program = { +const PROG_CPSC: Program = { prog_data: { prog_name: "Computer Science", prog_abbr: "CPSC", @@ -20,7 +20,7 @@ export const PROG_CPSC: Program = { ] } -export const PROG_ECON: Program = { +const PROG_ECON: Program = { prog_data: { prog_name: "Economics", prog_abbr: "ECON", @@ -34,7 +34,7 @@ export const PROG_ECON: Program = { ] } -export const PROG_PLSC: Program = { +const PROG_PLSC: Program = { prog_data: { prog_name: "Political Science", prog_abbr: "PLSC", @@ -48,7 +48,7 @@ export const PROG_PLSC: Program = { ] } -export const PROG_HIST: Program = { +const PROG_HIST: Program = { prog_data: { prog_name: "History", prog_abbr: "HIST", @@ -64,3 +64,7 @@ export const PROG_HIST: Program = { } ] } + +export const PROG_LIST: Program[] = [ + PROG_CPSC, PROG_ECON, PROG_PLSC, PROG_HIST +] diff --git a/frontend/src/types/type-user.ts b/frontend/src/types/type-user.ts index 3700ec7..a0549a0 100644 --- a/frontend/src/types/type-user.ts +++ b/frontend/src/types/type-user.ts @@ -1,5 +1,5 @@ -import { MajorsIndex, Program } from "./type-program"; +import { DegreeConcentration, MajorsIndex, Program } from "./type-program"; export interface LanguagePlacement { language: string; @@ -39,15 +39,16 @@ export interface StudentTermArrangement { } export interface StudentConc { + conc_majors_index: MajorsIndex; user_status: number; - majors_index: MajorsIndex; + user_conc: DegreeConcentration; + user_conc_name: string; } export interface FYP { languagePlacement: LanguagePlacement; studentCourses: StudentCourse[]; studentTermArrangement: StudentTermArrangement; - prog_list: Program[]; decl_list: StudentConc[]; } From 9c6eb92d6daf314ffc386fb6d74906178037a096 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Fri, 14 Mar 2025 15:21:06 -0700 Subject: [PATCH 26/38] subreq selection --- .../majors/course-icon/MajorsCourseIcon.tsx | 2 +- .../src/app/majors/metadata/MetadataUtils.ts | 3 +- frontend/src/app/majors/page.tsx | 4 - .../requirements/Requirements.module.css | 42 ++++- .../app/majors/requirements/Requirements.tsx | 37 ++++- .../majors/requirements/RequirementsUtils.ts | 154 ++++++++++++------ frontend/src/database/data-user.ts | 11 +- .../src/database/programs/concs/concs-cpsc.ts | 4 +- .../src/database/programs/concs/concs-hist.ts | 2 +- frontend/src/types/type-program.ts | 2 + frontend/src/types/type-user.ts | 1 + 11 files changed, 190 insertions(+), 72 deletions(-) diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx index dcff863..e0b7b45 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx @@ -116,7 +116,7 @@ function MajorsEmptyIcon(props: { edit: boolean, onAddCourse: Function })
{props.edit ? ( <> -
setIsAdding(true)}> +
setIsAdding(true)}>
diff --git a/frontend/src/app/majors/metadata/MetadataUtils.ts b/frontend/src/app/majors/metadata/MetadataUtils.ts index ca25948..290f7fb 100644 --- a/frontend/src/app/majors/metadata/MetadataUtils.ts +++ b/frontend/src/app/majors/metadata/MetadataUtils.ts @@ -32,8 +32,9 @@ export function toggleConcentrationPin( const newStudentConc: StudentConc = { conc_majors_index: majorsIndex, user_status: 1, // Assume 1 means pinned - user_conc: { ...newConc }, // Clone conc so changes don't affect `progList` + user_conc: { ...newConc }, user_conc_name: newConc.conc_name, + selected_subreqs: {}, }; return { diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index ebcfb27..6feb588 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -34,8 +34,6 @@ function Majors() }, [index]); const updateIndex = (newIndex: MajorsIndex) => { - console.log("old"); - console.log(newIndex); setIndex((prev) => ({ ...prev!, ...newIndex, @@ -44,8 +42,6 @@ function Majors() : prev!.prog, conc: newIndex.conc === -1 ? (prev!.conc === -1 ? 0 : -1) : newIndex.conc, })); - console.log("new"); - console.log(index); }; if(index === null) return null; diff --git a/frontend/src/app/majors/requirements/Requirements.module.css b/frontend/src/app/majors/requirements/Requirements.module.css index 96dbaa7..62c7ced 100644 --- a/frontend/src/app/majors/requirements/Requirements.module.css +++ b/frontend/src/app/majors/requirements/Requirements.module.css @@ -50,6 +50,18 @@ margin-bottom: 4px; } + + + + + + + + + + + + .ToggleButton { margin-top: 3px; @@ -66,7 +78,17 @@ margin-bottom: 2px; } -.SubreqButton { + + + +.SubreqSelectionRow { + display: flex; + gap: 5px; + margin-bottom: 8px; + flex-wrap: wrap; +} + +.SubreqOption { font-size: 10px; padding: 3px; @@ -78,12 +100,28 @@ transition: background-color 0.3s ease; } -.SubreqButton.Selected { +.SubreqOption.Selected { background-color: rgb(100, 178, 238); } + + + + + + + + + + + + + + + + .RequirementsContainerHeader { display: flex; flex-direction: row; diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index d647a40..5118c09 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -9,7 +9,7 @@ import Style from "./Requirements.module.css"; import { Course, StudentConc } from "@/types/type-user"; import { ConcentrationSubrequirement, ConcentrationRequirement, MajorsIndex } from "@/types/type-program"; -import { removeCourseInSubreq, addCourseInSubreq } from "./RequirementsUtils"; +import { getStudentConcentration, removeCourseInSubreq, addCourseInSubreq, toggleSubreqSelection } from "./RequirementsUtils"; import { MajorsIcon } from "../course-icon/MajorsCourseIcon"; function RenderSubrequirementCourse(props: { @@ -90,6 +90,20 @@ function RenderRequirement(props: { reqIndex: number, req: ConcentrationRequirement }){ + const { user, setUser } = useAuth(); + const userConc = getStudentConcentration(user, props.majorsIndex); + const selectedSubreqs = userConc?.selected_subreqs[props.reqIndex] ?? []; + + const visibleSubreqs = selectedSubreqs.length > 0 + ? props.req.subreqs_list.filter((_, i) => selectedSubreqs.includes(i)) + : props.req.subreqs_list; + + function handleToggleSubreq(subreqIndex: number) { + if(userConc){ + toggleSubreqSelection(setUser, props.majorsIndex, props.reqIndex, subreqIndex, props.req.subreqs_required_count ?? props.req.subreqs_list.length); + } + } + return(
@@ -103,14 +117,29 @@ function RenderRequirement(props: {
{props.req.req_desc}
+ + {(props.req.subreqs_required_count !== undefined && props.req.subreqs_required_count < props.req.subreqs_list.length) && ( +
+ {props.req.subreqs_list.map((subreq, i) => ( +
handleToggleSubreq(i)} + > + {subreq.subreq_name} +
+ ))} +
+ )} +
- {props.req.subreqs_list.map((subreq, i) => ( + {visibleSubreqs.map((subreq, subreqIndex) => ( ))} diff --git a/frontend/src/app/majors/requirements/RequirementsUtils.ts b/frontend/src/app/majors/requirements/RequirementsUtils.ts index df9888f..4eeb2be 100644 --- a/frontend/src/app/majors/requirements/RequirementsUtils.ts +++ b/frontend/src/app/majors/requirements/RequirementsUtils.ts @@ -1,8 +1,20 @@ import { MajorsIndex } from "@/types/type-program"; -import { Course, User } from "@/types/type-user"; +import { Course, User, StudentConc } from "@/types/type-user"; import { ConcentrationSubrequirement } from "@/types/type-program"; +export function getStudentConcentration( + user: User, + majorsIndex: MajorsIndex +): StudentConc | undefined { + return user.FYP.decl_list.find( + (sc) => + sc.conc_majors_index.prog === majorsIndex.prog && + sc.conc_majors_index.deg === majorsIndex.deg && + sc.conc_majors_index.conc === majorsIndex.conc + ); +} + function updateUserWithNewSubreq( prevUser: User, majorsIndex: MajorsIndex, @@ -10,28 +22,33 @@ function updateUserWithNewSubreq( subreqIndex: number, updatedSubreq: ConcentrationSubrequirement ): User { + const matchingConc = getStudentConcentration(prevUser, majorsIndex); + if (!matchingConc) return prevUser; + return { ...prevUser, FYP: { ...prevUser.FYP, - decl_list: prevUser.FYP.decl_list.map((studentConc) => { - if( - studentConc.conc_majors_index.prog === majorsIndex.prog && - studentConc.conc_majors_index.deg === majorsIndex.deg && - studentConc.conc_majors_index.conc === majorsIndex.conc - ){ - const updatedConcReqs = studentConc.user_conc.conc_reqs.map((req, idx) => - idx === reqIndex - ? { ...req, subreqs_list: req.subreqs_list.map((subreq, sidx) => - sidx === subreqIndex ? updatedSubreq : subreq - ) } - : req - ); - - return { ...studentConc, user_conc: { ...studentConc.user_conc, conc_reqs: updatedConcReqs } }; - } - return studentConc; - }), + decl_list: prevUser.FYP.decl_list.map((studentConc) => + studentConc === matchingConc + ? { + ...studentConc, + user_conc: { + ...studentConc.user_conc, + conc_reqs: studentConc.user_conc.conc_reqs.map((req, idx) => + idx === reqIndex + ? { + ...req, + subreqs_list: req.subreqs_list.map((subreq, sidx) => + sidx === subreqIndex ? updatedSubreq : subreq + ), + } + : req + ), + }, + } + : studentConc + ), }, }; } @@ -42,14 +59,9 @@ export function addCourseInSubreq( reqIndex: number, subreqIndex: number, courseCode: string -) { +){ setUser((prevUser: User) => { - const userConc = prevUser.FYP.decl_list.find( - (sc) => sc.conc_majors_index.prog === majorsIndex.prog && - sc.conc_majors_index.deg === majorsIndex.deg && - sc.conc_majors_index.conc === majorsIndex.conc - ); - + const userConc = getStudentConcentration(prevUser, majorsIndex); if (!userConc) return prevUser; const requirement = userConc.user_conc.conc_reqs[reqIndex]; @@ -67,13 +79,11 @@ export function addCourseInSubreq( updatedCoursesOptions[firstNullIndex] = matchingStudentCourse.course; - const updatedSubreq = { + return updateUserWithNewSubreq(prevUser, majorsIndex, reqIndex, subreqIndex, { ...subreq, courses_options: updatedCoursesOptions, student_courses_satisfying: [...subreq.student_courses_satisfying, matchingStudentCourse], - }; - - return updateUserWithNewSubreq(prevUser, majorsIndex, reqIndex, subreqIndex, updatedSubreq); + }); }); } @@ -84,34 +94,84 @@ export function removeCourseInSubreq( subreqIndex: number, course: Course | null, isStudentCourse?: boolean -) { +){ setUser((prevUser: User) => { - const userConc = prevUser.FYP.decl_list.find( - (sc) => sc.conc_majors_index.prog === majorsIndex.prog && - sc.conc_majors_index.deg === majorsIndex.deg && - sc.conc_majors_index.conc === majorsIndex.conc - ); - - if (!userConc) return prevUser; // 🚨 Safety check + const userConc = getStudentConcentration(prevUser, majorsIndex); + if (!userConc) return prevUser; const requirement = userConc.user_conc.conc_reqs[reqIndex]; const subreq = requirement.subreqs_list[subreqIndex]; - // ✅ Modify `courses_options` - const updatedCoursesOptions = subreq.courses_options.map((c) => (c === course ? null : c)); - - // ✅ Modify `student_courses_satisfying` + // ✅ Remove student course const updatedStudentCourses = isStudentCourse ? subreq.student_courses_satisfying.filter((sc) => sc.course !== course) : subreq.student_courses_satisfying; - // ✅ Create updated subrequirement - const updatedSubreq = { + // ✅ Remove the course & ensure nulls don't exceed `courses_required` + let updatedCoursesOptions = subreq.courses_options.map((c) => (c === course ? null : c)); + const nullCount = updatedCoursesOptions.filter((c) => c === null).length; + + if (nullCount > subreq.courses_required) { + updatedCoursesOptions = updatedCoursesOptions.filter((c) => c !== null); + updatedCoursesOptions = [...updatedCoursesOptions, ...Array(subreq.courses_required).fill(null)]; + } + + return updateUserWithNewSubreq(prevUser, majorsIndex, reqIndex, subreqIndex, { ...subreq, courses_options: updatedCoursesOptions, - student_courses_satisfying: updatedStudentCourses - }; + student_courses_satisfying: updatedStudentCourses, + }); + }); +} + +export function toggleSubreqSelection( + setUser: Function, + majorsIndex: MajorsIndex, + reqIndex: number, + subreqIndex: number, + maxSelected: number +) { + setUser((prevUser: User) => { + // ✅ Find the matching concentration + const userConc = getStudentConcentration(prevUser, majorsIndex); + if (!userConc) return prevUser; // 🚨 Safety check + + // ✅ Create a fresh copy of selected_subreqs + const updatedSelectedSubreqs = { ...userConc.selected_subreqs }; - return updateUserWithNewSubreq(prevUser, majorsIndex, reqIndex, subreqIndex, updatedSubreq); + // ✅ Get currently selected subreqs or default to an empty array + const currentSelected = updatedSelectedSubreqs[reqIndex] ?? []; + + let newSelected = [...currentSelected]; + + if (newSelected.includes(subreqIndex)) { + // ✅ Remove if already selected + newSelected = newSelected.filter((i) => i !== subreqIndex); + } else if (newSelected.length < maxSelected) { + // ✅ Add if under max limit + newSelected.push(subreqIndex); + } + + // ✅ Ensure `selected_subreqs` is always properly structured + updatedSelectedSubreqs[reqIndex] = newSelected.length > 0 ? newSelected : []; + + // ✅ Return a fully **new** user object with updates applied + return { + ...prevUser, + FYP: { + ...prevUser.FYP, + decl_list: prevUser.FYP.decl_list.map((studentConc) => + studentConc.conc_majors_index.prog === majorsIndex.prog && + studentConc.conc_majors_index.deg === majorsIndex.deg && + studentConc.conc_majors_index.conc === majorsIndex.conc + ? { + ...studentConc, + selected_subreqs: updatedSelectedSubreqs, // ✅ Fully replace with new object + } + : studentConc + ), + }, + }; }); } + diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index 0b56db5..ddd65f6 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -2,8 +2,6 @@ import { User } from "./../types/type-user"; import { SC_CPSC_201 } from "./data-courses"; -import { CONC_CPSC_BS_I } from "./programs/concs/concs-cpsc"; - export const Ryan: User = { name: "Ryan", netID: "rgg32", @@ -17,13 +15,6 @@ export const Ryan: User = { senior: [0, 202703, 202801], }, languagePlacement: { language: "Spanish", level: 5 }, - decl_list: [ - { - conc_majors_index: { conc: 0, deg: 1, prog: 0 }, // check - user_status: 1, - user_conc: CONC_CPSC_BS_I, - user_conc_name: "cpsc bs", - } - ], + decl_list: [], } } diff --git a/frontend/src/database/programs/concs/concs-cpsc.ts b/frontend/src/database/programs/concs/concs-cpsc.ts index fafb5bd..16f8c85 100644 --- a/frontend/src/database/programs/concs/concs-cpsc.ts +++ b/frontend/src/database/programs/concs/concs-cpsc.ts @@ -21,7 +21,7 @@ const CORE_MATH: ConcentrationSubrequirement = { courses_required: 1, courses_options: [CPSC_202, MATH_244], courses_elective_range: null, - courses_any_bool: false, + courses_any_bool: true, student_courses_satisfying: [], } @@ -31,7 +31,7 @@ const CORE_DATA: ConcentrationSubrequirement = { courses_required: 1, courses_options: [CPSC_223], courses_elective_range: null, - courses_any_bool: false, + courses_any_bool: true, student_courses_satisfying: [SC_CPSC_223], } diff --git a/frontend/src/database/programs/concs/concs-hist.ts b/frontend/src/database/programs/concs/concs-hist.ts index 218b063..2d8700a 100644 --- a/frontend/src/database/programs/concs/concs-hist.ts +++ b/frontend/src/database/programs/concs/concs-hist.ts @@ -8,7 +8,7 @@ const PRE_REQ: ConcentrationSubrequirement = { courses_required: 2, courses_options: [null, null], courses_elective_range: null, - courses_any_bool: false, + courses_any_bool: true, student_courses_satisfying: [], } diff --git a/frontend/src/types/type-program.ts b/frontend/src/types/type-program.ts index 62a1b5e..e42cd81 100644 --- a/frontend/src/types/type-program.ts +++ b/frontend/src/types/type-program.ts @@ -21,6 +21,8 @@ export interface ConcentrationSubrequirement { flags?: string[]; student_courses_satisfying: StudentCourse[]; + + // selected?: boolean; } export interface ConcentrationRequirement { diff --git a/frontend/src/types/type-user.ts b/frontend/src/types/type-user.ts index a0549a0..cdd89ac 100644 --- a/frontend/src/types/type-user.ts +++ b/frontend/src/types/type-user.ts @@ -43,6 +43,7 @@ export interface StudentConc { user_status: number; user_conc: DegreeConcentration; user_conc_name: string; + selected_subreqs: Record; } export interface FYP { From c1ee832b71867d85ebb59d5a5ca6ebbe7509bf5e Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Fri, 14 Mar 2025 15:57:19 -0700 Subject: [PATCH 27/38] dyn req count --- frontend/src/app/majors/Majors.module.css | 24 +++++++++++++++++++ .../app/majors/requirements/Requirements.tsx | 14 +++++++++-- .../src/database/programs/concs/concs-hist.ts | 2 +- .../src/database/programs/concs/concs-plsc.ts | 2 +- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/majors/Majors.module.css b/frontend/src/app/majors/Majors.module.css index 42209e4..28296f4 100644 --- a/frontend/src/app/majors/Majors.module.css +++ b/frontend/src/app/majors/Majors.module.css @@ -25,6 +25,7 @@ .ListButton { position: fixed; + cursor: pointer; top: 95px; left: 20px; @@ -42,5 +43,28 @@ border-radius: 50%; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.25); + z-index: 2; +} + +.FilterButton { + position: fixed; + cursor: pointer; + + top: 140px; + left: 20px; + + width: 30px; + height: 30px; + + color: white; + text-align: center; + line-height: 30px; + font-size: 20px; + + background-color: #61fe73; + border: none; + border-radius: 50%; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.25); + z-index: 2; } \ No newline at end of file diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index 5118c09..01e6fce 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -104,6 +104,13 @@ function RenderRequirement(props: { } } + let dynamicRequiredCount: number | string = props.req.courses_required_count; + if (props.req.courses_required_count === -1) { + dynamicRequiredCount = selectedSubreqs.length > 0 + ? selectedSubreqs.reduce((sum, idx) => sum + props.req.subreqs_list[idx].courses_required, 0) + : "~"; + } + return(
@@ -111,8 +118,11 @@ function RenderRequirement(props: { {props.req.req_name}
- {props.req.checkbox !== undefined ? props.req.courses_satisfied_count === props.req.courses_required_count ? "✅" : "❌" : `${props.req.courses_satisfied_count}|${props.req.courses_required_count}`} -
+ {props.req.checkbox !== undefined + ? props.req.courses_satisfied_count === props.req.courses_required_count ? "✅" : "❌" + : `${props.req.courses_satisfied_count}|${dynamicRequiredCount}` + } +
{props.req.req_desc} diff --git a/frontend/src/database/programs/concs/concs-hist.ts b/frontend/src/database/programs/concs/concs-hist.ts index 2d8700a..39ce192 100644 --- a/frontend/src/database/programs/concs/concs-hist.ts +++ b/frontend/src/database/programs/concs/concs-hist.ts @@ -211,7 +211,7 @@ const SEN_TWO: ConcentrationSubrequirement = { const HIST_SEN: ConcentrationRequirement = { req_name: "SENIOR", req_desc: "", - courses_required_count: 0, + courses_required_count: -1, courses_satisfied_count: 0, subreqs_required_count: 1, subreqs_satisfied_count: 0, diff --git a/frontend/src/database/programs/concs/concs-plsc.ts b/frontend/src/database/programs/concs/concs-plsc.ts index 06c0054..e3bee47 100644 --- a/frontend/src/database/programs/concs/concs-plsc.ts +++ b/frontend/src/database/programs/concs/concs-plsc.ts @@ -178,7 +178,7 @@ const SEN_STAN_TWO: ConcentrationSubrequirement = { const PLSC_SEN_STAN: ConcentrationRequirement = { req_name: "SENIOR", req_desc: "", - courses_required_count: 0, + courses_required_count: -1, courses_satisfied_count: 0, subreqs_required_count: 1, subreqs_satisfied_count: 0, From 5fb1cf3131c92f62ce1377a091a5fc007fddbc41 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Fri, 14 Mar 2025 17:50:34 -0700 Subject: [PATCH 28/38] addable course back --- .../course-icon/MajorsCourseIcon.module.css | 87 ++++++-------- .../majors/course-icon/MajorsCourseIcon.tsx | 113 ++++++++++-------- .../src/database/programs/concs/concs-econ.ts | 4 +- 3 files changed, 106 insertions(+), 98 deletions(-) diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css b/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css index 13a33a4..c6ffe4e 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css @@ -1,7 +1,4 @@ - /* */ - /* min-height: 18px; */ - .Icon { display: flex; flex-direction: row; @@ -12,14 +9,15 @@ font-size: 14px; font-weight: bold; - width: max-content; - height: max-content; - padding: 2px 4px; border-radius: 15px; + width: max-content; + min-width: 14px; + height: 18px; + background-color: #F5F5F5; - transition: filter 0.4s ease; + transition: filter 0.3s ease; } .Icon:hover { @@ -48,57 +46,48 @@ - - - - -.IconContainer { - position: relative; - display: inline-block; -} - -.EmptyIcon { - width: 20px; - height: 20px; +.AddButton { display: flex; align-items: center; justify-content: center; - border-radius: 50%; - background-color: #f5f5f5; - cursor: default; -} -.EmptyIcon:hover { + width: 22px; + height: 22px; + + font-size: 12px; + font-weight: bold; + + border-radius: 50%; + background-color: #F5F5F5; + cursor: pointer; - background-color: #e0e0e0; + overflow: hidden; } -.AddCoursePopup { - position: absolute; - top: 22px; - left: 50%; - transform: translateX(-50%); - background: white; - border: 1px solid #ddd; - padding: 6px; - box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); +.AddCanvas { display: flex; + flex-direction: row; align-items: center; - border-radius: 5px; - z-index: 10; - width: fit-content; - min-width: 60px; -} -.AddCoursePopup input { - width: 75px; - padding: 2px; - border: 1px solid #ccc; - border-radius: 4px; - font-size: 12px; -} + height: 20px; + width: 90px; -.ConfirmButton { - cursor: pointer; - color: green; + background-color: #F5F5F5; + border-radius: 15px; + padding: 1px 4px; } + +.CodeSearch { + height: 14px; + width: 70px; + + font-size: 12px; /* Matching font size */ + padding: 1px; + + border: none; + outline: none; + background-color: white; + color: black; + + border-radius: 3px; /* Makes it a circle */ +} \ No newline at end of file diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx index e0b7b45..2c82f80 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx @@ -2,16 +2,18 @@ import { useState, useRef, useEffect } from "react"; import Style from "./MajorsCourseIcon.module.css" +import Image from "next/image"; + import { ConcentrationSubrequirement } from "@/types/type-program"; import { StudentCourse, Course } from "@/types/type-user"; import DistributionCircle from "@/components/distribution-circle/DistributionsCircle"; - -function CourseSeasonIcon(props: { seasons: Array }) { +function SeasonComp(props: { seasons: string[] }) +{ const seasonImageMap: { [key: string]: string } = { - "Fall": "./fall.svg", - "Spring": "./spring.svg", + "Fall": "/fall.svg", + "Spring": "/spring.svg", }; return ( @@ -19,10 +21,11 @@ function CourseSeasonIcon(props: { seasons: Array }) { {props.seasons.map((szn, index) => (
0 ? "-7.5px" : 0 }}> {seasonImageMap[szn] && ( - {szn} )}
@@ -31,25 +34,27 @@ function CourseSeasonIcon(props: { seasons: Array }) { ); } -function MajorsCourseIcon(props: { +function CourseIcon(props: { edit: boolean; course: Course; subreq: ConcentrationSubrequirement; onRemoveCourse: Function; -}) { +}){ return (
{props.edit && ( props.onRemoveCourse(props.course, props.subreq, false)} /> )} - + {props.course.codes[0]} - +
+ +
); } -function MajorsStudentCourseIcon(props: { +function StudentCourseIcon(props: { edit: boolean; studentCourse: StudentCourse; subreq: ConcentrationSubrequirement; @@ -74,22 +79,29 @@ function RemoveButton({ onClick }: { onClick: () => void }) { ); } -function MajorsEmptyIcon(props: { edit: boolean, onAddCourse: Function }) -{ +function EmptyIcon(props: { + edit: boolean, + onAddCourse: Function, +}){ const [isAdding, setIsAdding] = useState(false); const [courseCode, setCourseCode] = useState(""); const popupRef = useRef(null); const inputRef = useRef(null); + useEffect(() => { + if (isAdding) { + inputRef.current?.focus(); + } + }, [isAdding]); + useEffect(() => { function handleClickOutside(event: MouseEvent) { if (popupRef.current && !popupRef.current.contains(event.target as Node)) { - setIsAdding(false); - setCourseCode(""); + deactivate(); } } - if(isAdding){ + if (isAdding) { document.addEventListener("mousedown", handleClickOutside); } @@ -98,44 +110,51 @@ function MajorsEmptyIcon(props: { edit: boolean, onAddCourse: Function }) }; }, [isAdding]); - useEffect(() => { - if (isAdding) { - inputRef.current?.focus(); - } - }, [isAdding]); + function activate() { + setIsAdding(true); + } - function handleAddCourse() { - const success = props.onAddCourse(courseCode); - if(success){ - setIsAdding(false); - setCourseCode(""); + function deactivate() { + setIsAdding(false); + setCourseCode(""); + } + + function handleAddCourse() { + const success = props.onAddCourse(courseCode.trim().toUpperCase()); + if (success) { + deactivate(); } } - return( -
- {props.edit ? ( - <> -
setIsAdding(true)}> - -
- - {isAdding && ( -
- setCourseCode(e.target.value)}/> -
-
- )} - - ) : ( -
+ if (!props.edit) { + return
; + } -
+ return ( +
+ {!isAdding ? ( +
+ + +
+ ) : ( +
+
+ setCourseCode(e.target.value)} + maxLength={9} + className={Style.CodeSearch} + /> +
+
)}
); } + export function MajorsIcon(props: { edit: boolean; contentCourse: Course | StudentCourse | null; @@ -144,20 +163,20 @@ export function MajorsIcon(props: { onAddCourse: Function; }) { if (!props.contentCourse) { - return ; + return ; } const isStudentCourse = "course" in props.contentCourse; return isStudentCourse ? ( - ) : ( - Date: Sat, 15 Mar 2025 16:59:20 -0700 Subject: [PATCH 29/38] progDict --- frontend/src/app/majors/metadata/Metadata.tsx | 62 ++++++++++++------- .../src/app/majors/metadata/MetadataUtils.ts | 31 ++++++---- frontend/src/app/majors/page.tsx | 56 ++++++++++++----- .../app/majors/requirements/Requirements.tsx | 14 ++--- frontend/src/context/ProgramProvider.tsx | 18 +++--- .../src/database/programs/data-program.ts | 11 ++-- frontend/src/types/type-program.ts | 4 +- 7 files changed, 123 insertions(+), 73 deletions(-) diff --git a/frontend/src/app/majors/metadata/Metadata.tsx b/frontend/src/app/majors/metadata/Metadata.tsx index 14013f1..a166c2e 100644 --- a/frontend/src/app/majors/metadata/Metadata.tsx +++ b/frontend/src/app/majors/metadata/Metadata.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from "react"; import Style from "./Metadata.module.css"; import Link from 'next/link'; -import { MajorsIndex, Program } from "@/types/type-program"; +import { MajorsIndex, Program, ProgramDict } from "@/types/type-program"; import { usePrograms } from "@/context/ProgramProvider"; import { useAuth } from "@/context/AuthProvider"; @@ -14,10 +14,10 @@ function MetadataTopshelf(props: { index: MajorsIndex; }){ const { setUser } = useAuth(); - const { progList } = usePrograms(); + const { progDict } = usePrograms(); function handlePinClick() { - toggleConcentrationPin(setUser, progList, props.index); + toggleConcentrationPin(setUser, progDict, props.index); } return( @@ -150,20 +150,25 @@ function MetadataBody(props: { } function MetadataScrollButton(props: { - programs: Program[], + programs: ProgramDict, index: MajorsIndex, setIndex: Function; + filteredProgKeys: string[]; dir: number }){ - return( -
props.setIndex({ conc: 0, deg: 0, prog: props.index.prog + props.dir })}> + const currentProgIndex = props.filteredProgKeys.indexOf(props.index.prog); + const nextProgIndex = (currentProgIndex + props.dir + props.filteredProgKeys.length) % props.filteredProgKeys.length; + const nextProg = props.filteredProgKeys[nextProgIndex]; + + return ( +
props.setIndex({ prog: nextProg, conc: 0, deg: 0 })}>
- {props.programs[(props.index.prog + props.dir + props.programs.length) % props.programs.length].prog_data.prog_name} + {props.programs[nextProg].prog_data.prog_name}
- {props.programs[(props.index.prog + props.dir + props.programs.length) % props.programs.length].prog_data.prog_abbr} + {props.programs[nextProg].prog_data.prog_abbr}
@@ -172,44 +177,59 @@ function MetadataScrollButton(props: { } function ProgramList(props: { - programs: Program[], + programs: ProgramDict, setIndex: Function }){ - return( + return (
- {props.programs.map((program: Program, prog_index: number) => ( + {Object.entries(props.programs).map(([progCode, program]) => (
props.setIndex({ conc: 0, deg: 0, prog: prog_index })} + onClick={() => props.setIndex({ conc: 0, deg: 0, prog: progCode })} > - {program.prog_data.prog_name} {program.prog_data.prog_abbr} + {program.prog_data.prog_name} ({program.prog_data.prog_abbr})
))}
- ) + ); } function Metadata(props: { index: MajorsIndex, - setIndex: Function + setIndex: Function, + filteredProgKeys: string[], }){ - const { progList } = usePrograms(); - const currProgram = progList[props.index.prog]; + const { progDict } = usePrograms(); + const currProgram = progDict[props.index.prog]; + + if (!currProgram) return null; return(
{props.index.conc == -1 ? ( - + ) : (
- +
- +
) } diff --git a/frontend/src/app/majors/metadata/MetadataUtils.ts b/frontend/src/app/majors/metadata/MetadataUtils.ts index 290f7fb..ed64437 100644 --- a/frontend/src/app/majors/metadata/MetadataUtils.ts +++ b/frontend/src/app/majors/metadata/MetadataUtils.ts @@ -1,10 +1,10 @@ import { User, StudentConc } from "@/types/type-user"; -import { Program, MajorsIndex } from "@/types/type-program"; +import { ProgramDict, MajorsIndex } from "@/types/type-program"; export function toggleConcentrationPin( setUser: Function, - progList: Program[], + progDict: ProgramDict, majorsIndex: MajorsIndex ) { setUser((prevUser: User) => { @@ -15,9 +15,8 @@ export function toggleConcentrationPin( sc.conc_majors_index.conc === majorsIndex.conc ); - // ✅ If already pinned, remove it (unpin) - if (existingConcIndex !== -1) { - return { + if(existingConcIndex !== -1){ + return{ ...prevUser, FYP: { ...prevUser.FYP, @@ -26,22 +25,28 @@ export function toggleConcentrationPin( }; } - // ✅ Otherwise, pin it - const newConc = progList[majorsIndex.prog].prog_degs[majorsIndex.deg].deg_concs[majorsIndex.conc]; + const program = progDict[majorsIndex.prog]; + if (!program) return prevUser; + + const degree = program.prog_degs[majorsIndex.deg]; + if (!degree) return prevUser; + + const concentration = degree.deg_concs[majorsIndex.conc]; + if (!concentration) return prevUser; const newStudentConc: StudentConc = { conc_majors_index: majorsIndex, - user_status: 1, // Assume 1 means pinned - user_conc: { ...newConc }, - user_conc_name: newConc.conc_name, - selected_subreqs: {}, + user_status: 1, + user_conc: { ...concentration }, + user_conc_name: concentration.conc_name, + selected_subreqs: {}, }; - return { + return{ ...prevUser, FYP: { ...prevUser.FYP, - decl_list: [...prevUser.FYP.decl_list, newStudentConc], // Append to `decl_list` + decl_list: [...prevUser.FYP.decl_list, newStudentConc], }, }; }); diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index 6feb588..8a5d4e0 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -16,16 +16,30 @@ import Requirements from "./requirements/Requirements"; function Majors() { const { user } = useAuth(); - const { progList } = usePrograms(); + const { progDict } = usePrograms(); + const progKeys = Object.keys(progDict); + const [filteredProgKeys, setFilteredProgKeys] = useState(progKeys); const [index, setIndex] = useState(null); - useEffect(() => { - if(typeof window !== "undefined"){ - const storedIndex = sessionStorage.getItem("majorsIndex"); - setIndex(storedIndex ? JSON.parse(storedIndex) : { conc: 0, deg: 0, prog: 0 }); + useEffect(() => { + if(progKeys.length > 0){ + setFilteredProgKeys(progKeys); } - }, []); + }, [progDict]); + + useEffect(() => { + if (typeof window !== "undefined" && filteredProgKeys.length > 0) { + const storedIndex = sessionStorage.getItem("majorsIndex"); + let parsedIndex: MajorsIndex = storedIndex ? JSON.parse(storedIndex) : { conc: 0, deg: 0, prog: filteredProgKeys[0] }; + + if (!filteredProgKeys.includes(parsedIndex.prog)) { + parsedIndex = { ...parsedIndex, prog: filteredProgKeys[0] }; + } + + setIndex(parsedIndex); + } + }, [filteredProgKeys]); useEffect(() => { if(typeof window !== "undefined" && index !== null){ @@ -33,25 +47,33 @@ function Majors() } }, [index]); - const updateIndex = (newIndex: MajorsIndex) => { - setIndex((prev) => ({ - ...prev!, - ...newIndex, - prog: newIndex.prog !== undefined - ? (newIndex.prog + progList.length) % progList.length - : prev!.prog, - conc: newIndex.conc === -1 ? (prev!.conc === -1 ? 0 : -1) : newIndex.conc, - })); + const updateIndex = (newIndex: Partial) => { + setIndex((prev) => { + if (!prev) return { conc: 0, deg: 0, prog: filteredProgKeys[0] || "" }; + + return{ + ...prev, + ...newIndex, + prog: newIndex.prog !== undefined + ? filteredProgKeys[ + (filteredProgKeys.indexOf(newIndex.prog) + filteredProgKeys.length) % filteredProgKeys.length + ] + : prev.prog, + conc: newIndex.conc !== undefined + ? (newIndex.conc === -1 ? (prev.conc === -1 ? 0 : -1) : newIndex.conc) + : prev.conc, + }; + }); }; - if(index === null) return null; + if (index === null || !filteredProgKeys.length) return null; return(
}/>
updateIndex({ ...index, conc: -1 })}/> - +
diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index 01e6fce..0065ebc 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -169,19 +169,17 @@ function Requirements(props: { const [edit, setEdit] = useState(false); const { user } = useAuth(); - const { progList } = usePrograms(); + const { progDict } = usePrograms(); useEffect(() => { setEdit(false); }, [props.majorsIndex]); - const userConc = user.FYP.decl_list.find((sc: StudentConc) => - sc.conc_majors_index.prog === props.majorsIndex?.prog && - sc.conc_majors_index.deg === props.majorsIndex?.deg && - sc.conc_majors_index.conc === props.majorsIndex?.conc - ); - - const conc = userConc ? userConc.user_conc : progList[props.majorsIndex.prog].prog_degs[props.majorsIndex.deg].deg_concs[props.majorsIndex.conc]; + const userConc = getStudentConcentration(user, props.majorsIndex); + const program = progDict[props.majorsIndex.prog]; + const conc = userConc + ? userConc.user_conc + : program?.prog_degs[props.majorsIndex.deg].deg_concs[props.majorsIndex.conc]; return(
diff --git a/frontend/src/context/ProgramProvider.tsx b/frontend/src/context/ProgramProvider.tsx index fc19e32..0294515 100644 --- a/frontend/src/context/ProgramProvider.tsx +++ b/frontend/src/context/ProgramProvider.tsx @@ -1,22 +1,21 @@ "use client"; import { createContext, useContext, useState, useEffect } from "react"; -import { Program } from "@/types/type-program"; +import { ProgramDict } from "@/types/type-program"; +import { PROG_DICT } from "@/database/programs/data-program"; -import { PROG_LIST } from "@/database/programs/data-program"; - -const ProgramContext = createContext<{ progList: Program[] }>({ progList: [] }); +const ProgramContext = createContext<{ progDict: ProgramDict }>({ progDict: {} }); export function ProgramProvider({ children }: { children: React.ReactNode }) { - const [progList, setProgList] = useState([]); + const [progDict, setProgDict] = useState({}); useEffect(() => { - setProgList(PROG_LIST) - }, []); + setProgDict(PROG_DICT); + }, []); - return( - + return ( + {children} ); @@ -25,3 +24,4 @@ export function ProgramProvider({ children }: { children: React.ReactNode }) { export function usePrograms() { return useContext(ProgramContext); } + diff --git a/frontend/src/database/programs/data-program.ts b/frontend/src/database/programs/data-program.ts index f590c22..1c6dcb1 100644 --- a/frontend/src/database/programs/data-program.ts +++ b/frontend/src/database/programs/data-program.ts @@ -1,5 +1,5 @@ -import { Program } from "@/types/type-program"; +import { Program, ProgramDict } from "@/types/type-program"; import { CONC_CPSC_BA_I, CONC_CPSC_BS_I } from "./concs/concs-cpsc"; import { CONC_ECON_BA_I } from "./concs/concs-econ"; import { CONC_HIST_BA_GLOB, CONC_HIST_BA_SPEC } from "./concs/concs-hist"; @@ -65,6 +65,9 @@ const PROG_HIST: Program = { ] } -export const PROG_LIST: Program[] = [ - PROG_CPSC, PROG_ECON, PROG_PLSC, PROG_HIST -] +export const PROG_DICT: ProgramDict = { + "CPSC": PROG_CPSC, + "ECON": PROG_ECON, + "PLSC": PROG_PLSC, + "HIST": PROG_HIST, +}; diff --git a/frontend/src/types/type-program.ts b/frontend/src/types/type-program.ts index e42cd81..bc032e9 100644 --- a/frontend/src/types/type-program.ts +++ b/frontend/src/types/type-program.ts @@ -72,7 +72,9 @@ export interface Program { } export interface MajorsIndex { - prog: number; + prog: string; deg: number; conc: number; } + +export type ProgramDict = Record; From 59dfcee3e078debdc49e6654b2fe3271bf3b18cd Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Sun, 16 Mar 2025 21:21:56 -0700 Subject: [PATCH 30/38] remove styles --- frontend/src/app/majors/MajorsUtils.ts | 36 +++++++++++ .../course-icon/MajorsCourseIcon.module.css | 54 +++-------------- .../majors/course-icon/MajorsCourseIcon.tsx | 24 +++++--- .../app/majors/metadata/Metadata.module.css | 59 ++++++++++--------- frontend/src/app/majors/metadata/Metadata.tsx | 32 +++++----- frontend/src/app/majors/page.tsx | 40 ++++--------- .../requirements/Requirements.module.css | 25 ++++---- .../app/majors/requirements/Requirements.tsx | 36 +++++++---- frontend/src/database/data-user.ts | 11 +++- .../src/database/programs/concs/concs-econ.ts | 4 +- .../src/database/programs/concs/concs-hist.ts | 14 ++--- 11 files changed, 182 insertions(+), 153 deletions(-) create mode 100644 frontend/src/app/majors/MajorsUtils.ts diff --git a/frontend/src/app/majors/MajorsUtils.ts b/frontend/src/app/majors/MajorsUtils.ts new file mode 100644 index 0000000..74cca18 --- /dev/null +++ b/frontend/src/app/majors/MajorsUtils.ts @@ -0,0 +1,36 @@ + +import { MajorsIndex } from "@/types/type-program"; + +export function initializeMajorsIndex( + storedIndex: string | null, + filteredProgKeys: string[] +): MajorsIndex { + let parsedIndex: MajorsIndex = storedIndex + ? JSON.parse(storedIndex) + : { conc: 0, deg: 0, prog: filteredProgKeys[0] }; + + if (!filteredProgKeys.includes(parsedIndex.prog)) { + parsedIndex = { ...parsedIndex, prog: filteredProgKeys[0] }; + } + + return parsedIndex; +} + +export function updateMajorsIndex( + prev: MajorsIndex | null, + newIndex: Partial, + filteredProgKeys: string[] +): MajorsIndex { + if (!prev) return { conc: 0, deg: 0, prog: filteredProgKeys[0] || "" }; + + return { + ...prev, + ...newIndex, + prog: newIndex.prog !== undefined + ? filteredProgKeys[ + (filteredProgKeys.indexOf(newIndex.prog) + filteredProgKeys.length) % filteredProgKeys.length + ] + : prev.prog, + conc: newIndex.conc ?? prev.conc, + }; +} diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css b/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css index c6ffe4e..0e534bf 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.module.css @@ -30,64 +30,28 @@ } .RemoveButton { - margin-right: 3px; - width: 15px; - height: 15px; - border-radius: 50%; - background-color: rgb(255, 255, 255); - display: flex; - align-items: center; - justify-content: center; - font-size: 10px; - font-weight: bold; - cursor: pointer; -} - - - - -.AddButton { - display: flex; - align-items: center; - justify-content: center; + padding: 0; + border: none; + cursor: pointer; - width: 22px; - height: 22px; + margin-right: 2px; + margin-left: 2px; - font-size: 12px; - font-weight: bold; + width: 15px; + height: 15px; border-radius: 50%; - background-color: #F5F5F5; - - cursor: pointer; - overflow: hidden; -} - -.AddCanvas { - display: flex; - flex-direction: row; - align-items: center; - - height: 20px; - width: 90px; - - background-color: #F5F5F5; - border-radius: 15px; - padding: 1px 4px; } .CodeSearch { height: 14px; width: 70px; - font-size: 12px; /* Matching font size */ + font-size: 12px; padding: 1px; border: none; outline: none; background-color: white; color: black; - - border-radius: 3px; /* Makes it a circle */ -} \ No newline at end of file +} diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx index 2c82f80..1d5ae6d 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx @@ -73,9 +73,11 @@ function StudentCourseIcon(props: { function RemoveButton({ onClick }: { onClick: () => void }) { return ( -
- -
+
); } @@ -133,12 +135,16 @@ function EmptyIcon(props: { return (
{!isAdding ? ( -
+
+
) : ( -
-
+
+
)}
diff --git a/frontend/src/app/majors/metadata/Metadata.module.css b/frontend/src/app/majors/metadata/Metadata.module.css index 56af915..16658de 100644 --- a/frontend/src/app/majors/metadata/Metadata.module.css +++ b/frontend/src/app/majors/metadata/Metadata.module.css @@ -78,56 +78,61 @@ background-color: white; } -.thumbtack { - font-size: 28px; - margin-right: 10px; - cursor: pointer; - transition: opacity 0.2s; -} -.thumbtack:hover { - opacity: 0.7; /* Slightly lighter on hover */ -} - -.thumbtack:active { - opacity: 0.5; /* Even lighter on click */ -} .ScrollButton { background-color: white; + margin: 0; + padding: 0; border: none; - cursor: pointer; - /* padding: 10px; */ - display: inline-block; + cursor: pointer; +} + +.PinButton { + cursor: pointer; + background-color: white; + border: none; + margin: 0; + padding: 0; + + + font-size: 30px; + margin-right: 6px; } + + +/* margin-left: 80px; */ + .ToggleContainer { display: flex; flex-direction: row; - - /* margin-left: 80px; */ margin-bottom: 8px; } .ToggleOption { - font-size: 14px; + cursor: pointer; + background-color: white; + border: 1px solid rgb(196, 196, 196); + border-radius: 5px; + color: black; + margin: 0; padding: 4px 10px; - cursor: pointer; - background-color: white; - color: black; + font-size: 14px; transition: background-color 0.3s, color 0.3s; - border-radius: 5px; - - border: 1px solid rgb(196, 196, 196); - } -.ToggleOption.active { +.ToggleOption.Active { background-color: #598ff4; color: white; } + + + + + .ProgramOption { color: gray; font-size: 20px; diff --git a/frontend/src/app/majors/metadata/Metadata.tsx b/frontend/src/app/majors/metadata/Metadata.tsx index a166c2e..1504d16 100644 --- a/frontend/src/app/majors/metadata/Metadata.tsx +++ b/frontend/src/app/majors/metadata/Metadata.tsx @@ -24,9 +24,9 @@ function MetadataTopshelf(props: {
-
+
+
{props.program.prog_data.prog_name}
@@ -89,35 +89,36 @@ function MetadataToggle(props: { index: MajorsIndex, setIndex: Function }){ - return ( + return(
{props.program.prog_degs.map((deg, index) => ( -
1 ? "pointer" : "default" }} onClick={() => props.setIndex({ prog: props.index.prog, deg: index, conc: props.index.conc })} > {deg.deg_type} -
+ ))}
{props.program.prog_degs[props.index.deg].deg_concs.length > 1 && (
{props.program.prog_degs[props.index.deg].deg_concs.map((conc, index) => ( -
1 ? "pointer" : "default" }} onClick={() => props.setIndex({ prog: props.index.prog, deg: props.index.deg, conc: index })} > {conc.conc_name} -
+ ))}
)}
- ); } @@ -161,7 +162,7 @@ function MetadataScrollButton(props: { const nextProg = props.filteredProgKeys[nextProgIndex]; return ( -
props.setIndex({ prog: nextProg, conc: 0, deg: 0 })}> +
+ ); } @@ -196,6 +197,7 @@ function ProgramList(props: { } function Metadata(props: { + listView: boolean, index: MajorsIndex, setIndex: Function, filteredProgKeys: string[], @@ -207,7 +209,7 @@ function Metadata(props: { return(
- {props.index.conc == -1 ? ( + {props.listView ? ( ) : (
diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index 8a5d4e0..273e9c9 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -7,6 +7,7 @@ import { useState, useEffect } from "react"; import Style from "./Majors.module.css"; import { MajorsIndex } from "@/types/type-program"; +import { initializeMajorsIndex, updateMajorsIndex } from "./MajorsUtils"; import NavBar from "@/components/navbar/NavBar"; import Overhead from "./overhead/Overhead"; @@ -21,6 +22,7 @@ function Majors() const progKeys = Object.keys(progDict); const [filteredProgKeys, setFilteredProgKeys] = useState(progKeys); const [index, setIndex] = useState(null); + const [listView, setListView] = useState(false); useEffect(() => { if(progKeys.length > 0){ @@ -31,13 +33,7 @@ function Majors() useEffect(() => { if (typeof window !== "undefined" && filteredProgKeys.length > 0) { const storedIndex = sessionStorage.getItem("majorsIndex"); - let parsedIndex: MajorsIndex = storedIndex ? JSON.parse(storedIndex) : { conc: 0, deg: 0, prog: filteredProgKeys[0] }; - - if (!filteredProgKeys.includes(parsedIndex.prog)) { - parsedIndex = { ...parsedIndex, prog: filteredProgKeys[0] }; - } - - setIndex(parsedIndex); + setIndex(initializeMajorsIndex(storedIndex, filteredProgKeys)); } }, [filteredProgKeys]); @@ -48,23 +44,9 @@ function Majors() }, [index]); const updateIndex = (newIndex: Partial) => { - setIndex((prev) => { - if (!prev) return { conc: 0, deg: 0, prog: filteredProgKeys[0] || "" }; - - return{ - ...prev, - ...newIndex, - prog: newIndex.prog !== undefined - ? filteredProgKeys[ - (filteredProgKeys.indexOf(newIndex.prog) + filteredProgKeys.length) % filteredProgKeys.length - ] - : prev.prog, - conc: newIndex.conc !== undefined - ? (newIndex.conc === -1 ? (prev.conc === -1 ? 0 : -1) : newIndex.conc) - : prev.conc, - }; - }); - }; + setListView(false); + setIndex((prev) => updateMajorsIndex(prev, newIndex, filteredProgKeys)); + }; if (index === null || !filteredProgKeys.length) return null; @@ -72,10 +54,14 @@ function Majors()
}/>
-
updateIndex({ ...index, conc: -1 })}/> - +
setListView((prev) => !prev)}/> +
- +
); diff --git a/frontend/src/app/majors/requirements/Requirements.module.css b/frontend/src/app/majors/requirements/Requirements.module.css index 62c7ced..b1a3058 100644 --- a/frontend/src/app/majors/requirements/Requirements.module.css +++ b/frontend/src/app/majors/requirements/Requirements.module.css @@ -89,14 +89,14 @@ } .SubreqOption { - font-size: 10px; + cursor: pointer; + background-color: rgb(211, 211, 211); + color: white; + border: none; + border-radius: 6px; + margin: 0; padding: 3px; - - cursor: pointer; - border-radius: 6px; - - background-color: rgb(211, 211, 211); - color: white; + font-size: 10px; transition: background-color 0.3s ease; } @@ -128,7 +128,7 @@ justify-content: space-between; margin-bottom: 10px; font-size: 30px; - padding: 2px 8px; + /* padding: 2px 8px; */ } .GoldBackground { @@ -136,6 +136,11 @@ border-radius: 2px; */ } -.EditButton:hover { - cursor: pointer; /* Show pointer cursor on hover */ +.EditButton { + background-color: white; + margin: 0; + padding: 0; + border: none; + cursor: pointer; + font-size: 30px; } diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index 0065ebc..a4b5db2 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -6,7 +6,7 @@ import { usePrograms } from "@/context/ProgramProvider"; import { useState, useEffect } from "react"; import Style from "./Requirements.module.css"; -import { Course, StudentConc } from "@/types/type-user"; +import { Course } from "@/types/type-user"; import { ConcentrationSubrequirement, ConcentrationRequirement, MajorsIndex } from "@/types/type-program"; import { getStudentConcentration, removeCourseInSubreq, addCourseInSubreq, toggleSubreqSelection } from "./RequirementsUtils"; @@ -99,29 +99,40 @@ function RenderRequirement(props: { : props.req.subreqs_list; function handleToggleSubreq(subreqIndex: number) { - if(userConc){ - toggleSubreqSelection(setUser, props.majorsIndex, props.reqIndex, subreqIndex, props.req.subreqs_required_count ?? props.req.subreqs_list.length); + if(userConc && props.edit){ + toggleSubreqSelection( + setUser, + props.majorsIndex, + props.reqIndex, + subreqIndex, + props.req.subreqs_required_count ?? props.req.subreqs_list.length + ); } } let dynamicRequiredCount: number | string = props.req.courses_required_count; - if (props.req.courses_required_count === -1) { + if(props.req.courses_required_count === -1){ dynamicRequiredCount = selectedSubreqs.length > 0 ? selectedSubreqs.reduce((sum, idx) => sum + props.req.subreqs_list[idx].courses_required, 0) : "~"; } + const dynamicSatisfiedCount = props.req.subreqs_list.reduce( + (sum, subreq) => sum + subreq.student_courses_satisfying.length, + 0 + ); + return(
{props.req.req_name}
-
+
{props.req.checkbox !== undefined - ? props.req.courses_satisfied_count === props.req.courses_required_count ? "✅" : "❌" - : `${props.req.courses_satisfied_count}|${dynamicRequiredCount}` - } + ? dynamicSatisfiedCount === dynamicRequiredCount ? "✅" : "❌" + : `${dynamicSatisfiedCount}|${dynamicRequiredCount}` + }
@@ -131,13 +142,14 @@ function RenderRequirement(props: { {(props.req.subreqs_required_count !== undefined && props.req.subreqs_required_count < props.req.subreqs_list.length) && (
{props.req.subreqs_list.map((subreq, i) => ( -
handleToggleSubreq(i)} > {subreq.subreq_name} -
+ ))}
)} @@ -188,9 +200,9 @@ function Requirements(props: { Requirements
{userConc && -
setEdit(!edit)}> +
+ }
diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index ddd65f6..7f7608b 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -1,6 +1,7 @@ import { User } from "./../types/type-user"; import { SC_CPSC_201 } from "./data-courses"; +import { CONC_HIST_BA_GLOB } from "./programs/concs/concs-hist"; export const Ryan: User = { name: "Ryan", @@ -15,6 +16,14 @@ export const Ryan: User = { senior: [0, 202703, 202801], }, languagePlacement: { language: "Spanish", level: 5 }, - decl_list: [], + decl_list: [ + { + conc_majors_index: { prog: "HIST", deg: 0, conc: 0 }, + user_status: 1, + user_conc: CONC_HIST_BA_GLOB, + user_conc_name: "", + selected_subreqs: {}, + } + ], } } diff --git a/frontend/src/database/programs/concs/concs-econ.ts b/frontend/src/database/programs/concs/concs-econ.ts index 7c96ee5..91e813f 100644 --- a/frontend/src/database/programs/concs/concs-econ.ts +++ b/frontend/src/database/programs/concs/concs-econ.ts @@ -18,8 +18,8 @@ const INTRO_MATH: ConcentrationSubrequirement = { const INTRO_MICRO: ConcentrationSubrequirement = { subreq_name: "INTRO MICRO", subreq_desc: "", - courses_required: 2, - courses_options: [ECON_108, ECON_110, ECON_115, null], + courses_required: 1, + courses_options: [ECON_108, ECON_110, ECON_115], courses_elective_range: null, courses_any_bool: false, student_courses_satisfying: [SC_ECON_110], diff --git a/frontend/src/database/programs/concs/concs-hist.ts b/frontend/src/database/programs/concs/concs-hist.ts index 39ce192..960e095 100644 --- a/frontend/src/database/programs/concs/concs-hist.ts +++ b/frontend/src/database/programs/concs/concs-hist.ts @@ -29,7 +29,7 @@ const GLOB_CORE_AFRICA: ConcentrationSubrequirement = { courses_required: 1, courses_options: [null], courses_elective_range: null, - courses_any_bool: false, + courses_any_bool: true, student_courses_satisfying: [], } @@ -39,7 +39,7 @@ const GLOB_CORE_ASIA: ConcentrationSubrequirement = { courses_required: 1, courses_options: [null], courses_elective_range: null, - courses_any_bool: false, + courses_any_bool: true, student_courses_satisfying: [], } @@ -49,7 +49,7 @@ const GLOB_CORE_EURO: ConcentrationSubrequirement = { courses_required: 1, courses_options: [null], courses_elective_range: null, - courses_any_bool: false, + courses_any_bool: true, student_courses_satisfying: [], } @@ -59,7 +59,7 @@ const GLOB_CORE_LA: ConcentrationSubrequirement = { courses_required: 1, courses_options: [null], courses_elective_range: null, - courses_any_bool: false, + courses_any_bool: true, student_courses_satisfying: [], } @@ -69,7 +69,7 @@ const GLOB_CORE_ME: ConcentrationSubrequirement = { courses_required: 1, courses_options: [null], courses_elective_range: null, - courses_any_bool: false, + courses_any_bool: true, student_courses_satisfying: [], } @@ -79,7 +79,7 @@ const GLOB_CORE_US: ConcentrationSubrequirement = { courses_required: 1, courses_options: [null], courses_elective_range: null, - courses_any_bool: false, + courses_any_bool: true, student_courses_satisfying: [], } @@ -90,7 +90,7 @@ const HIST_GLOB_CORE: ConcentrationRequirement = { courses_satisfied_count: 0, subreqs_required_count: 5, subreqs_satisfied_count: 0, - subreqs_list: [GLOB_CORE_AFRICA, GLOB_CORE_AFRICA, GLOB_CORE_EURO, GLOB_CORE_LA, GLOB_CORE_ME, GLOB_CORE_US] + subreqs_list: [GLOB_CORE_AFRICA, GLOB_CORE_ASIA, GLOB_CORE_EURO, GLOB_CORE_LA, GLOB_CORE_ME, GLOB_CORE_US] } // SPEC CORE From 0ba3e797e1516cf5c56805035816a20fbe5c5b96 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Mon, 17 Mar 2025 14:04:28 -0700 Subject: [PATCH 31/38] I don't remember --- frontend/src/app/courses/page.tsx | 14 +- frontend/src/app/courses/years/YearBox.tsx | 40 ++++- .../courses/years/semester/SemesterBox.tsx | 29 +++- .../add-course/AddCourseButton.module.css | 142 +++++----------- .../semester/add-course/AddCourseButton.tsx | 154 ++++++++++-------- .../semester/add-course/AddCourseUtils.ts | 148 ++++++++++++++--- .../semester/course/CourseBox.module.css | 52 ++---- .../years/semester/course/CourseBox.tsx | 59 ++++--- frontend/src/app/majors/page.tsx | 4 +- frontend/src/context/ProgramProvider.tsx | 4 +- frontend/src/database/data-catalog.ts | 18 +- frontend/src/database/data-courses.ts | 8 +- frontend/src/database/data-user.ts | 22 +-- .../src/database/programs/concs/concs-cpsc.ts | 12 +- .../src/database/programs/concs/concs-econ.ts | 4 +- .../src/utils/preprocessing/BackendAdapt.ts | 1 - frontend/src/utils/preprocessing/Fill.ts | 27 +++ 17 files changed, 432 insertions(+), 306 deletions(-) delete mode 100644 frontend/src/utils/preprocessing/BackendAdapt.ts create mode 100644 frontend/src/utils/preprocessing/Fill.ts diff --git a/frontend/src/app/courses/page.tsx b/frontend/src/app/courses/page.tsx index 554107c..2fe260d 100644 --- a/frontend/src/app/courses/page.tsx +++ b/frontend/src/app/courses/page.tsx @@ -11,7 +11,7 @@ import NavBar from "@/components/navbar/NavBar"; import YearBox from "./years/YearBox"; function Courses(){ - const { user, setUser } = useAuth(); + const { user } = useAuth(); const [edit, setEdit] = useState(false); const toggleEdit = () => { setEdit(!edit); }; @@ -28,7 +28,13 @@ function Courses(){ useEffect(() => { const newRenderedYears = studentYears.map((studentYear: StudentYear, index: number) => ( - + )); setRenderedYears(newRenderedYears); }, [edit, columns, studentYears, user]); @@ -37,8 +43,8 @@ function Courses(){
- - +
); diff --git a/frontend/src/app/courses/years/semester/SemesterBox.tsx b/frontend/src/app/courses/years/semester/SemesterBox.tsx index 6823900..37e6e08 100644 --- a/frontend/src/app/courses/years/semester/SemesterBox.tsx +++ b/frontend/src/app/courses/years/semester/SemesterBox.tsx @@ -7,11 +7,18 @@ import { TransformTermNumber, IsTermActive } from "@/utils/course-display/Course import CourseBox from "./course/CourseBox"; import AddCourseButton from "./add-course/AddCourseButton"; +import { useAuth } from "@/context/AuthProvider"; -function RenderCourses(props: { edit: boolean, studentSemester: StudentSemester, user: User, setUser: Function }) -{ +function RenderCourses(props: { + edit: boolean, + studentSemester: StudentSemester +}){ const renderedCourses = props.studentSemester.studentCourses.map((studentCourse, index) => ( - + )); return( @@ -21,15 +28,21 @@ function RenderCourses(props: { edit: boolean, studentSemester: StudentSemester, ); } -function SemesterBox(props: { edit: boolean, studentSemester: StudentSemester, user: User, setUser: Function }) { - +function SemesterBox(props: { + edit: boolean, + studentSemester: StudentSemester, +}){ + const { user } = useAuth(); const [renderedCourses, setRenderedCourses] = useState(null); useEffect(() => { setRenderedCourses( - + ); - }, [props.edit, props.studentSemester, props.user]); + }, [props.edit, props.studentSemester, user]); return(
@@ -38,7 +51,7 @@ function SemesterBox(props: { edit: boolean, studentSemester: StudentSemester, u
{renderedCourses} - {props.edit && } + {props.edit && }
); diff --git a/frontend/src/app/courses/years/semester/add-course/AddCourseButton.module.css b/frontend/src/app/courses/years/semester/add-course/AddCourseButton.module.css index 86542a7..1f311ce 100644 --- a/frontend/src/app/courses/years/semester/add-course/AddCourseButton.module.css +++ b/frontend/src/app/courses/years/semester/add-course/AddCourseButton.module.css @@ -4,82 +4,45 @@ flex-direction: row; } -/* */ - -.AddButton { +.CourseBox { display: flex; + flex-direction: row; + + gap: 4px; + justify-content: center; align-items: center; - width: 36px; + + background: #F5F5F5; + border: black; + + width: max-content; + min-width: 36px; height: 36px; - border-radius: 50%; - margin-bottom: 5px; - background-color: #F5F5F5; + + border-radius: 16px; cursor: pointer; } -.AddButton:hover, -.AddButton:active { - background-color: #E0E0E0; -} -.AddCanvas { +.DropBox { display: flex; flex-direction: row; - justify-content: space-between; - align-items: center; - width: 420px; - height: 36px; - border-radius: 16px; - margin-bottom: 5px; - padding-left: 10px; - padding-right: 10px; - background-color: #F5F5F5; - transition: filter 0.4s ease; -} -/* */ + align-items: center; -.TermBox { - border: 1px solid #ccc; - padding: 0 8px; + border: none; background-color: white; + + position: relative; cursor: pointer; - border-radius: 4px; - font-size: 12px; + height: 22px; - width: 90px; - display: flex; - align-items: center; + width: 80px; box-sizing: border-box; - color: black; - position: relative; - transition: border-color 0.3s ease; -} -.TermBox:hover { - border-color: #aaa; -} -.ResultBox { - border: 1px solid #ccc; - padding: 0 8px; - background-color: white; - cursor: pointer; - border-radius: 4px; font-size: 12px; - height: 22px; - width: 60px; - display: flex; - align-items: center; - box-sizing: border-box; color: black; - position: relative; - transition: border-color 0.3s ease; } -.ResultBox:hover { - border-color: #aaa; -} - -/* */ .DropdownOptions { position: absolute; @@ -110,67 +73,40 @@ } .SelectedDropdownOption { - background-color: #93c7ff !important; /* Light blue background for the selected term */ - color: black; /* Optional: change text color for selected term */ + background-color: #93c7ff !important; + color: black; } -/* */ - -.CodeBox { +.InputBox { background-color: white; - border: 1px solid #ccc; - height: 22px; - width: 90px; - padding-left: 8px; - margin-left: 10px; + + border: none; outline: none; + + height: 22px; + box-sizing: border-box; + font-size: 12px; font-weight: 500; - border-radius: 4px; - box-sizing: border-box; - margin-right: 4px; - - /* box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); */ + } -.CodeBox:focus { +.InputBox:focus { border-color: #aaa; outline: none; } -.CodeBox::placeholder { +.InputBox::placeholder { color: grey; font-style: italic; } -/* */ - -.ConfirmButton { - display: flex; - justify-content: center; - align-items: center; - width: 18px; - height: 18px; - border-radius: 50%; - background-color: #dbdbdb; +.FuncButton { + border: none; + margin: 0; + padding: 0; + border: none; cursor: pointer; - margin-right: 5px; -} -.ConfirmButton:hover { - background-color: #D3D3D3; -} - -/* */ -.RemoveButton { - display: flex; - justify-content: center; - align-items: center; - width: 16px; - height: 16px; + width: 20px; + height: 20px; border-radius: 50%; - background-color: #ededed; - cursor: pointer; - margin-right: 5px; -} -.RemoveButton:hover { - background-color: #D3D3D3; } diff --git a/frontend/src/app/courses/years/semester/add-course/AddCourseButton.tsx b/frontend/src/app/courses/years/semester/add-course/AddCourseButton.tsx index bffdeaf..8ade1f8 100644 --- a/frontend/src/app/courses/years/semester/add-course/AddCourseButton.tsx +++ b/frontend/src/app/courses/years/semester/add-course/AddCourseButton.tsx @@ -2,46 +2,29 @@ import { useRef, useState, useEffect } from "react"; import Style from "./AddCourseButton.module.css"; -import { User, StudentCourse } from "@/types/type-user"; -import { getCatalogCourse, getCatalogTerms } from "@/database/data-catalog"; +import { useAuth } from "@/context/AuthProvider"; +import { executeAddCourse } from "./AddCourseUtils"; +import { getCatalogTerms } from "@/database/data-catalog"; +import { usePrograms } from "@/context/ProgramProvider"; -interface AddCourseDisplay { +export interface AddCourseDisplay { active: boolean; termDropVis: boolean; resultDropVis: boolean; } -function executeAddCourse( - props: { term: number; user: User; setUser: Function }, - inputRef: React.RefObject, - selectedTerm: number, - selectedResult: string, - setAddDisplay: Function -){ - if(inputRef.current){ - const targetCode = inputRef.current.value; - const targetCourse = getCatalogCourse(selectedTerm, targetCode); - - if(targetCourse){ - const status = selectedTerm === props.term ? "DA" : "MA"; - const newCourse: StudentCourse = { course: targetCourse, status, term: props.term, result: selectedResult }; - - const updatedCourses = [...props.user.FYP.studentCourses, newCourse]; - - props.setUser({ ...props.user, FYP: { ...props.user.FYP, studentCourses: updatedCourses } }); - setAddDisplay((prevState: AddCourseDisplay) => ({ ...prevState, active: false })); - } - } -} - -function AddCourseButton(props: { term: number; user: User; setUser: Function }) { - +function AddCourseButton(props: { + term: number +}){ + const { user, setUser } = useAuth(); + const { progDict, setProgDict } = usePrograms(); + const inputRef = useRef(null); const addRef = useRef(null); const [addDisplay, setAddDisplay] = useState({ active: false, termDropVis: false, resultDropVis: false }); const [selectedTerm, setSelectedTerm] = useState(props.term); - const [selectedResult, setSelectedResult] = useState(""); + const [selectedResult, setSelectedResult] = useState("GRADE"); const resultOptions = ["GRADE", "CR", "D/F"]; const catalogTerms = getCatalogTerms() @@ -74,52 +57,85 @@ function AddCourseButton(props: { term: number; user: User; setUser: Function }) } }; - const handleKeyPress = (event: React.KeyboardEvent) => { - if(event.key === "Enter"){ - executeAddCourse(props, inputRef, selectedTerm, selectedResult, setAddDisplay); - } - }; - return(
{!addDisplay.active ? ( -
setAddDisplay((prevState) => ({...prevState, active: true}))}> +
setAddDisplay((prevState) => ({...prevState, active: true}))} + > +
) : ( -
-
-
setAddDisplay((prevState) => ({...prevState, active: false}))}> -
-
setAddDisplay((prevState) => ({...prevState, termDropVis: !prevState.termDropVis}))}> - {selectedTerm} - {addDisplay.termDropVis && ( -
- {catalogTerms.map((term, index) => ( -
setSelectedTerm(term)} className={term === selectedTerm ? Style.SelectedDropdownOption : ""}> - {term} -
- ))} -
- )} -
- - -
setAddDisplay((prevState) => ({...prevState, resultDropVis: !prevState.resultDropVis}))}> - {selectedResult || ""} - {addDisplay.resultDropVis && ( -
- {resultOptions.map((result, index) => ( -
setSelectedResult(result)} className={result === selectedResult ? Style.SelectedDropdownOption : ""}> - {result} -
- ))} -
- )} -
-
executeAddCourse(props, inputRef, selectedTerm, selectedResult, setAddDisplay)}> -
-
+
+
)}
diff --git a/frontend/src/app/courses/years/semester/add-course/AddCourseUtils.ts b/frontend/src/app/courses/years/semester/add-course/AddCourseUtils.ts index ce7fd3e..50c8131 100644 --- a/frontend/src/app/courses/years/semester/add-course/AddCourseUtils.ts +++ b/frontend/src/app/courses/years/semester/add-course/AddCourseUtils.ts @@ -1,28 +1,130 @@ -export async function fetchAndCacheCourses( +import { User, StudentCourse } from "@/types/type-user"; +import { getCatalogCourse } from "@/database/data-catalog"; +import { AddCourseDisplay } from "./AddCourseButton"; +import { usePrograms } from "@/context/ProgramProvider"; +import { ConcentrationSubrequirement, DegreeConcentration, ProgramDegree, ProgramDict } from "@/types/type-program"; + +// TODO: +// Two tasks. +// (1) Iterate through the entire progDict. If the new StudentCourse's course attribute +// corresponds to a course in a subreqs course_options array, add the StudentCourse +// to the user_courses_satisfying array. +// (2) Iterate through all studentconcs in user.FYP.decl list, updating their +// user_conc DegreeConcentrations in the same way. + +export function executeAddCourse( + term: number, + user: User, + setUser: Function, + progDict: ProgramDict, + setProgDict: Function, + inputRef: React.RefObject, selectedTerm: number, - setSearchData: Function + selectedResult: string, + setAddDisplay: Function ){ - const cachedData = localStorage.getItem(`courses-${selectedTerm}`); - if(cachedData){ - setSearchData(JSON.parse(cachedData)); - console.log("Loaded From Cache"); - }else{ - // try{ - // const data = await getCatalog(selectedTerm.toString()); - // setSearchData(data); - // try { - // localStorage.setItem(`courses-${selectedTerm}`, JSON.stringify(data)); - // console.log("Retrieved & Cached"); - // } catch (e: any) { - // if (e.name === "QuotaExceededError" || e.code === 22) { - // console.error("Quota Exceeded: ", e); - // } else { - // console.error("Error Unknown: ", e); - // } - // } - // } catch (error) { - // console.error("Error Retrieving: ", error); - // } + if (!inputRef.current) return; + + const targetCode = inputRef.current.value.trim().toUpperCase(); + const targetCourse = getCatalogCourse(selectedTerm, targetCode); + + if (!targetCourse) return; + + const status = selectedTerm === term ? "DA" : "MA"; + const newCourse: StudentCourse = { course: targetCourse, status, term, result: selectedResult }; + + // ✅ Step 1: Update `user.FYP.studentCourses` + const updatedCourses = [...user.FYP.studentCourses, newCourse]; + + // ✅ Step 2: Function to update `student_courses_satisfying` in a given subreq + function updateSubreqCourses(subreq: ConcentrationSubrequirement) { + // Check if the course is in `courses_options` and if it's not already in `student_courses_satisfying` + if (subreq.courses_options.some((c) => c?.codes.includes(targetCode)) && + !subreq.student_courses_satisfying.some((sc) => sc.course.codes.includes(targetCode))) { + return { + ...subreq, + student_courses_satisfying: [...subreq.student_courses_satisfying, newCourse] + }; + } + return subreq; } + + // ✅ Step 3: Iterate through `user.FYP.decl_list` (Pinned Concentrations) + const updatedDeclList = user.FYP.decl_list.map((studentConc) => { + return { + ...studentConc, + user_conc: { + ...studentConc.user_conc, + conc_reqs: studentConc.user_conc.conc_reqs.map((req) => ({ + ...req, + subreqs_list: req.subreqs_list.map(updateSubreqCourses) + })) + } + }; + }); + + // ✅ Step 4: Iterate through `progDict` to update **ALL** programs + const updatedProgDict = { ...progDict }; + + Object.keys(updatedProgDict).forEach((progKey) => { + updatedProgDict[progKey].prog_degs = updatedProgDict[progKey].prog_degs.map((deg: ProgramDegree) => ({ + ...deg, + deg_concs: deg.deg_concs.map((conc: DegreeConcentration) => ({ + ...conc, + conc_reqs: conc.conc_reqs.map((req) => ({ + ...req, + subreqs_list: req.subreqs_list.map(updateSubreqCourses) + })) + })) + })); + }); + + // ✅ Step 5: Update State + setUser({ + ...user, + FYP: { + ...user.FYP, + studentCourses: updatedCourses, + decl_list: updatedDeclList + } + }); + + setProgDict(updatedProgDict); + + // ✅ Step 6: Close Input Box + setAddDisplay((prevState: AddCourseDisplay) => ({ ...prevState, active: false })); } + + + + + + +// export async function fetchAndCacheCourses( +// selectedTerm: number, +// setSearchData: Function +// ){ +// const cachedData = localStorage.getItem(`courses-${selectedTerm}`); +// if(cachedData){ +// setSearchData(JSON.parse(cachedData)); +// console.log("Loaded From Cache"); +// }else{ +// // try{ +// // const data = await getCatalog(selectedTerm.toString()); +// // setSearchData(data); +// // try { +// // localStorage.setItem(`courses-${selectedTerm}`, JSON.stringify(data)); +// // console.log("Retrieved & Cached"); +// // } catch (e: any) { +// // if (e.name === "QuotaExceededError" || e.code === 22) { +// // console.error("Quota Exceeded: ", e); +// // } else { +// // console.error("Error Unknown: ", e); +// // } +// // } +// // } catch (error) { +// // console.error("Error Retrieving: ", error); +// // } +// } +// } diff --git a/frontend/src/app/courses/years/semester/course/CourseBox.module.css b/frontend/src/app/courses/years/semester/course/CourseBox.module.css index c44cf5f..56b7b80 100644 --- a/frontend/src/app/courses/years/semester/course/CourseBox.module.css +++ b/frontend/src/app/courses/years/semester/course/CourseBox.module.css @@ -1,53 +1,26 @@ -.row { +.Row { display: flex; flex-direction: row; } - -.RemoveButton { +.Column { display: flex; - justify-content: center; - align-items: center; - width: 16px; /* adjust size as needed */ - height: 16px; - border-radius: 50%; - background-color: #ededed; - cursor: pointer; - margin-right: 5px; - /* optional border for better visibility */ - /* border: 1px solid #C0C0C0; */ + flex-direction: column; } -.RemoveButton:hover { - background-color: #D3D3D3; /* slightly darker grey on hover */ -} - -.Checkmark { - justify-content: center; - text-align: center; - - font-weight: 550; - margin-right: 2px; -} - -.courseBox { +.CourseBox { display: flex; flex-direction: row; + justify-content: space-between; align-items: center; width: 420px; height: 36px; + padding: 0 10px; border-radius: 16px; - margin-bottom: 5px; - - padding-left: 10px; - padding-right: 10px; - - background-color: #F5F5F5; - transition: filter 0.4s ease; } .CourseCode { @@ -60,7 +33,14 @@ font-weight: 500; } -.SeasonImage { - margin-top: 3px; - margin-right: 6px; +.FuncButton { + border: none; + margin: 0; + padding: 0; + border: none; + cursor: pointer; + + width: 20px; + height: 20px; + border-radius: 50%; } diff --git a/frontend/src/app/courses/years/semester/course/CourseBox.tsx b/frontend/src/app/courses/years/semester/course/CourseBox.tsx index 336dd46..648cfdf 100644 --- a/frontend/src/app/courses/years/semester/course/CourseBox.tsx +++ b/frontend/src/app/courses/years/semester/course/CourseBox.tsx @@ -2,43 +2,54 @@ import Style from "./CourseBox.module.css"; import { User, StudentCourse } from "@/types/type-user"; +import { useAuth } from "@/context/AuthProvider"; + import { RenderMark, SeasonIcon, GetCourseColor, IsTermActive } from "../../../../../utils/course-display/CourseDisplay"; import DistributionCircle from "@/components/distribution-circle/DistributionsCircle"; -// import { useModal } from "../../../hooks/modalContext"; -// const { setModalOpen } = useModal(); function openModal() { setModalOpen(props.SC.course) } // onClick={openModal} - -function RemoveCourse(props: { studentCourse: StudentCourse; user: User; setUser: Function }) -{ - const remove = () => { +function RemoveButton(props: { + studentCourse: StudentCourse; + user: User; + setUser: Function +}){ + const removeStudentCourse = () => { const updatedStudentCourses = props.user.FYP.studentCourses.filter( (course) => course.course.title !== props.studentCourse.course.title ); - const updatedUser = { - ...props.user, - FYP: { - ...props.user.FYP, - studentCourses: updatedStudentCourses - } - }; - + const updatedUser = { ...props.user, FYP: { ...props.user.FYP, studentCourses: updatedStudentCourses } }; props.setUser(updatedUser); }; return ( -
+
); } -function CourseBox(props: {edit: boolean, studentCourse: StudentCourse, user: User, setUser: Function }){ +function CourseBox(props: { + edit: boolean, + studentCourse: StudentCourse, +}){ + const { user, setUser } = useAuth(); + return( -
-
- {(props.edit && IsTermActive(props.studentCourse.term)) && } - +
+
+ {(props.edit && IsTermActive(props.studentCourse.term)) && + + } + -
+
{props.studentCourse.course.codes[0]}
@@ -47,11 +58,7 @@ function CourseBox(props: {edit: boolean, studentCourse: StudentCourse, user: Us
-
-
- -
-
+
); } diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index 273e9c9..dc9ea91 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -13,11 +13,12 @@ import NavBar from "@/components/navbar/NavBar"; import Overhead from "./overhead/Overhead"; import Metadata from "./metadata/Metadata"; import Requirements from "./requirements/Requirements"; +import { fill } from "@/utils/preprocessing/Fill"; function Majors() { const { user } = useAuth(); - const { progDict } = usePrograms(); + const { progDict, setProgDict } = usePrograms(); const progKeys = Object.keys(progDict); const [filteredProgKeys, setFilteredProgKeys] = useState(progKeys); @@ -55,6 +56,7 @@ function Majors() }/>
setListView((prev) => !prev)}/> +
fill(user.FYP.studentCourses, progDict, setProgDict)}/> ({ progDict: {} }); +const ProgramContext = createContext(null); export function ProgramProvider({ children }: { children: React.ReactNode }) { @@ -15,7 +15,7 @@ export function ProgramProvider({ children }: { children: React.ReactNode }) { }, []); return ( - + {children} ); diff --git a/frontend/src/database/data-catalog.ts b/frontend/src/database/data-catalog.ts index 70a9827..2ec6f10 100644 --- a/frontend/src/database/data-catalog.ts +++ b/frontend/src/database/data-catalog.ts @@ -1,6 +1,6 @@ import { Course } from "@/types/type-user"; -import { HSAR_401 } from "./data-courses"; +import { CPSC_490, HSAR_401 } from "./data-courses"; interface Catalog { number: number; @@ -15,7 +15,21 @@ export const Catalogs: Catalog[] = [ { number: 202401, courses: [HSAR_401] }, { number: 202402, courses: [HSAR_401] }, { number: 202403, courses: [HSAR_401] }, - { number: 202501, courses: [HSAR_401] }, + { number: 202501, courses: [HSAR_401, CPSC_490] }, + { number: 202502, courses: [HSAR_401] }, + { number: 202503, courses: [HSAR_401] }, + { number: 202601, courses: [HSAR_401] }, + { number: 202602, courses: [HSAR_401] }, + { number: 202603, courses: [HSAR_401] }, + { number: 202701, courses: [HSAR_401] }, + { number: 202702, courses: [HSAR_401] }, + { number: 202703, courses: [HSAR_401] }, + { number: 202801, courses: [HSAR_401] }, + { number: 202802, courses: [HSAR_401] }, + { number: 202803, courses: [HSAR_401] }, + { number: 202901, courses: [HSAR_401] }, + { number: 202902, courses: [HSAR_401] }, + { number: 202903, courses: [HSAR_401] }, ] export const getCatalogCourse = (catalogNumber: number, courseCode: string): Course | null => { diff --git a/frontend/src/database/data-courses.ts b/frontend/src/database/data-courses.ts index e16787c..bb85299 100644 --- a/frontend/src/database/data-courses.ts +++ b/frontend/src/database/data-courses.ts @@ -4,7 +4,7 @@ import { Course, StudentCourse } from "@/types/type-user" // COURSES // HSAR PROGRAM -export const HSAR_401: Course = { codes: ["HSAR 401"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const HSAR_401: Course = { codes: ["HSAR 401"], title: "Critical Approaches To Art History", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } // PLSC PROGRAM export const PLSC_474: Course = { codes: ["PSLC 474"], title: "", credit: 1, dist: ["So"], seasons: ["Spring"]} @@ -12,7 +12,7 @@ export const PLSC_490: Course = { codes: ["PSLC 490"], title: "", credit: 1, dis export const PLSC_493: Course = { codes: ["PSLC 493"], title: "", credit: 1, dist: ["So"], seasons: ["Fall", "Spring"]} // CPSC PROGRAM -export const CPSC_201: Course = { codes: ["CPSC 201"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_201: Course = { codes: ["CPSC 201"], title: "Introduction To Computer Science", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } export const CPSC_202: Course = { codes: ["CPSC 202"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } export const MATH_244: Course = { codes: ["MATH 244"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } export const CPSC_223: Course = { codes: ["CPSC 223"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } @@ -20,7 +20,7 @@ export const CPSC_323: Course = { codes: ["CPSC 323"], title: "", credit: 1, dis export const CPSC_365: Course = { codes: ["CPSC 365"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } export const CPSC_366: Course = { codes: ["CPSC 366"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } export const CPSC_381: Course = { codes: ["CPSC 381"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_490: Course = { codes: ["CPSC 490"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +export const CPSC_490: Course = { codes: ["CPSC 490"], title: "Senior Project", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } // ECON PROGRAM export const MATH_110: Course = { codes: ["MATH 110"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } @@ -48,7 +48,7 @@ export const ECON_136: Course = { codes: ["ECON 136"], title: "", credit: 1, dis // CPSC COURSES export const SC_CPSC_201: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_201 } -// export const SC_CPSC_202: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_202 } +export const SC_CPSC_202: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_202 } export const SC_CPSC_223: StudentCourse = { term: 202501, status: "DA", result: "GRADE_PASS", course: CPSC_223 } export const SC_CPSC_323: StudentCourse = { term: 202503, status: "DA", result: "GRADE_PASS", course: CPSC_323 } export const SC_CPSC_381: StudentCourse = { term: 202503, status: "DA", result: "GRADE_PASS", course: CPSC_381 } diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index 7f7608b..aa2f8ae 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -1,6 +1,6 @@ import { User } from "./../types/type-user"; -import { SC_CPSC_201 } from "./data-courses"; +import { SC_CPSC_201, SC_CPSC_202, SC_CPSC_381, SC_ECON_110 } from "./data-courses"; import { CONC_HIST_BA_GLOB } from "./programs/concs/concs-hist"; export const Ryan: User = { @@ -8,7 +8,7 @@ export const Ryan: User = { netID: "rgg32", onboard: false, FYP: { - studentCourses: [SC_CPSC_201], + studentCourses: [SC_CPSC_201, SC_CPSC_202, SC_CPSC_381, SC_ECON_110], studentTermArrangement: { first_year: [0, 202403, 202501], sophomore: [0, 202503, 202601], @@ -16,14 +16,14 @@ export const Ryan: User = { senior: [0, 202703, 202801], }, languagePlacement: { language: "Spanish", level: 5 }, - decl_list: [ - { - conc_majors_index: { prog: "HIST", deg: 0, conc: 0 }, - user_status: 1, - user_conc: CONC_HIST_BA_GLOB, - user_conc_name: "", - selected_subreqs: {}, - } - ], + decl_list: [], } } + +// { +// conc_majors_index: { prog: "HIST", deg: 0, conc: 0 }, +// user_status: 1, +// user_conc: CONC_HIST_BA_GLOB, +// user_conc_name: "", +// selected_subreqs: {}, +// } diff --git a/frontend/src/database/programs/concs/concs-cpsc.ts b/frontend/src/database/programs/concs/concs-cpsc.ts index 16f8c85..73770be 100644 --- a/frontend/src/database/programs/concs/concs-cpsc.ts +++ b/frontend/src/database/programs/concs/concs-cpsc.ts @@ -32,7 +32,7 @@ const CORE_DATA: ConcentrationSubrequirement = { courses_options: [CPSC_223], courses_elective_range: null, courses_any_bool: true, - student_courses_satisfying: [SC_CPSC_223], + student_courses_satisfying: [], } const CORE_SYS: ConcentrationSubrequirement = { @@ -42,7 +42,7 @@ const CORE_SYS: ConcentrationSubrequirement = { courses_options: [CPSC_323], courses_elective_range: null, courses_any_bool: false, - student_courses_satisfying: [SC_CPSC_323], + student_courses_satisfying: [], } const CORE_ALGO: ConcentrationSubrequirement = { @@ -69,20 +69,20 @@ const ELEC_MULT_BA: ConcentrationSubrequirement = { subreq_name: "", subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", courses_required: 3, - courses_options: [CPSC_381, null, null], + courses_options: [null, null, null], courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, courses_any_bool: false, - student_courses_satisfying: [SC_CPSC_381] + student_courses_satisfying: [] } const ELEC_MULT_BS: ConcentrationSubrequirement = { subreq_name: "", subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", courses_required: 5, - courses_options: [CPSC_381, null, null, null, null], + courses_options: [null, null, null, null, null], courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, courses_any_bool: false, - student_courses_satisfying: [SC_CPSC_381] + student_courses_satisfying: [] } const ELEC_SUB: ConcentrationSubrequirement = { diff --git a/frontend/src/database/programs/concs/concs-econ.ts b/frontend/src/database/programs/concs/concs-econ.ts index 91e813f..b832a93 100644 --- a/frontend/src/database/programs/concs/concs-econ.ts +++ b/frontend/src/database/programs/concs/concs-econ.ts @@ -22,7 +22,7 @@ const INTRO_MICRO: ConcentrationSubrequirement = { courses_options: [ECON_108, ECON_110, ECON_115], courses_elective_range: null, courses_any_bool: false, - student_courses_satisfying: [SC_ECON_110], + student_courses_satisfying: [], } const INTRO_MACRO: ConcentrationSubrequirement = { @@ -90,7 +90,7 @@ const ELEC_STAN: ConcentrationSubrequirement = { subreq_desc: "Standard elective or DUS approved extra-department substitution.", courses_required: 1, courses_options: [null], - courses_elective_range: { dept: "CPSC", min_code: 123, max_code: 999 }, + courses_elective_range: { dept: "ECON", min_code: 123, max_code: 999 }, courses_any_bool: false, student_courses_satisfying: [] } diff --git a/frontend/src/utils/preprocessing/BackendAdapt.ts b/frontend/src/utils/preprocessing/BackendAdapt.ts deleted file mode 100644 index 8b13789..0000000 --- a/frontend/src/utils/preprocessing/BackendAdapt.ts +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/src/utils/preprocessing/Fill.ts b/frontend/src/utils/preprocessing/Fill.ts new file mode 100644 index 0000000..2ad5563 --- /dev/null +++ b/frontend/src/utils/preprocessing/Fill.ts @@ -0,0 +1,27 @@ + +import { StudentCourse } from "@/types/type-user"; +import { ProgramDict, DegreeConcentration, ConcentrationSubrequirement } from "@/types/type-program"; + + + + + + + + +export function fill( + studentCourses: StudentCourse[], + progDict: ProgramDict, + setProgDict: Function +) { + const updatedProgDict = { ...progDict }; + + // Object.keys(updatedProgDict).forEach(progKey => { + // updatedProgDict[progKey].prog_degs = updatedProgDict[progKey].prog_degs.map(deg => ({ + // ...deg, + // deg_concs: deg.deg_concs.map(conc => fillConcentration(conc, studentCourses)) + // })); + // }); + + setProgDict(updatedProgDict); +} From e83411083f0c76448a226ce6408063d92b33b591 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Mon, 17 Mar 2025 15:42:04 -0700 Subject: [PATCH 32/38] options --- .../majors/course-icon/MajorsCourseIcon.tsx | 1 - .../app/majors/requirements/Requirements.tsx | 34 +- .../majors/requirements/RequirementsUtils.ts | 47 +- frontend/src/database/data-user.ts | 6 +- .../src/database/programs/concs/concs-cpsc.ts | 138 +++-- .../src/database/programs/concs/concs-econ.ts | 284 +++++------ .../src/database/programs/concs/concs-hist.ts | 470 +++++++++--------- .../src/database/programs/concs/concs-plsc.ts | 442 ++++++++-------- .../src/database/programs/data-program.ts | 96 ++-- frontend/src/types/type-program.ts | 30 +- frontend/src/utils/preprocessing/Fill.ts | 171 ++++++- 11 files changed, 950 insertions(+), 769 deletions(-) diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx index 1d5ae6d..5cd2675 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx @@ -164,7 +164,6 @@ function EmptyIcon(props: { ); } - export function MajorsIcon(props: { edit: boolean; contentCourse: Course | StudentCourse | null; diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index a4b5db2..39d7d55 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -7,27 +7,23 @@ import { useState, useEffect } from "react"; import Style from "./Requirements.module.css"; import { Course } from "@/types/type-user"; -import { ConcentrationSubrequirement, ConcentrationRequirement, MajorsIndex } from "@/types/type-program"; +import { ConcentrationSubrequirement, ConcentrationRequirement, MajorsIndex, SubreqCourseOption } from "@/types/type-program"; import { getStudentConcentration, removeCourseInSubreq, addCourseInSubreq, toggleSubreqSelection } from "./RequirementsUtils"; import { MajorsIcon } from "../course-icon/MajorsCourseIcon"; function RenderSubrequirementCourse(props: { edit?: boolean; - course: Course | null; + option: SubreqCourseOption; subreq: ConcentrationSubrequirement; onRemoveCourse: Function, onAddCourse: Function }){ - const matchingStudentCourse = props.subreq.student_courses_satisfying.find( - (studentCourse) => studentCourse.course === props.course - ); - return (
= props.subreq.courses_required - ? props.subreq.courses_options.filter((course) => - props.subreq.student_courses_satisfying.some((sc) => sc.course === course) - ) - : props.subreq.courses_options; + const satisfiedCount = props.subreq.subreq_options.filter(opt => opt.s !== null).length; + const filteredCourses = satisfiedCount >= props.subreq.subreq_courses_req_count + ? props.subreq.subreq_options.filter(opt => opt.s !== null) + : props.subreq.subreq_options; return(
- {props.subreq.student_courses_satisfying.length}|{props.subreq.courses_required} {props.subreq.subreq_name} + {satisfiedCount}|{props.subreq.subreq_courses_req_count} {props.subreq.subreq_name}
{props.subreq.subreq_desc}
- {filteredCourses.map((course, course_index) => ( + {filteredCourses.map((option, option_index) => ( @@ -113,12 +107,12 @@ function RenderRequirement(props: { let dynamicRequiredCount: number | string = props.req.courses_required_count; if(props.req.courses_required_count === -1){ dynamicRequiredCount = selectedSubreqs.length > 0 - ? selectedSubreqs.reduce((sum, idx) => sum + props.req.subreqs_list[idx].courses_required, 0) + ? selectedSubreqs.reduce((sum, idx) => sum + props.req.subreqs_list[idx].subreq_courses_req_count, 0) : "~"; } const dynamicSatisfiedCount = props.req.subreqs_list.reduce( - (sum, subreq) => sum + subreq.student_courses_satisfying.length, + (sum, subreq) => sum + subreq.subreq_options.filter(option => option.s !== null).length, 0 ); diff --git a/frontend/src/app/majors/requirements/RequirementsUtils.ts b/frontend/src/app/majors/requirements/RequirementsUtils.ts index 4eeb2be..9794b41 100644 --- a/frontend/src/app/majors/requirements/RequirementsUtils.ts +++ b/frontend/src/app/majors/requirements/RequirementsUtils.ts @@ -59,7 +59,7 @@ export function addCourseInSubreq( reqIndex: number, subreqIndex: number, courseCode: string -){ +) { setUser((prevUser: User) => { const userConc = getStudentConcentration(prevUser, majorsIndex); if (!userConc) return prevUser; @@ -73,16 +73,17 @@ export function addCourseInSubreq( if (!matchingStudentCourse) return prevUser; - const updatedCoursesOptions = [...subreq.courses_options]; - const firstNullIndex = updatedCoursesOptions.indexOf(null); - if (firstNullIndex === -1) return prevUser; - - updatedCoursesOptions[firstNullIndex] = matchingStudentCourse.course; + // ✅ Find first empty slot in subreq_options + const updatedSubreqOptions = subreq.subreq_options.map((opt) => { + if (opt.o === null && opt.s === null) { + return { ...opt, o: matchingStudentCourse.course, s: matchingStudentCourse }; + } + return opt; + }); return updateUserWithNewSubreq(prevUser, majorsIndex, reqIndex, subreqIndex, { ...subreq, - courses_options: updatedCoursesOptions, - student_courses_satisfying: [...subreq.student_courses_satisfying, matchingStudentCourse], + subreq_options: updatedSubreqOptions }); }); } @@ -102,28 +103,32 @@ export function removeCourseInSubreq( const requirement = userConc.user_conc.conc_reqs[reqIndex]; const subreq = requirement.subreqs_list[subreqIndex]; - // ✅ Remove student course + // ✅ Remove student course from `student_courses_satisfying` const updatedStudentCourses = isStudentCourse - ? subreq.student_courses_satisfying.filter((sc) => sc.course !== course) - : subreq.student_courses_satisfying; - - // ✅ Remove the course & ensure nulls don't exceed `courses_required` - let updatedCoursesOptions = subreq.courses_options.map((c) => (c === course ? null : c)); - const nullCount = updatedCoursesOptions.filter((c) => c === null).length; - - if (nullCount > subreq.courses_required) { - updatedCoursesOptions = updatedCoursesOptions.filter((c) => c !== null); - updatedCoursesOptions = [...updatedCoursesOptions, ...Array(subreq.courses_required).fill(null)]; + ? subreq.subreq_options.filter((opt) => opt.s?.course !== course).map(opt => opt.s) + : subreq.subreq_options.map(opt => opt.s); + + // ✅ Remove the course from `subreq_options` + let updatedSubreqOptions = subreq.subreq_options.map(opt => + opt.o === course ? { ...opt, o: null, s: null } : opt + ); + + // ✅ Ensure nulls don't exceed `subreq_courses_req_count` + const nullCount = updatedSubreqOptions.filter(opt => opt.o === null).length; + if (nullCount > subreq.subreq_courses_req_count) { + updatedSubreqOptions = updatedSubreqOptions + .filter(opt => opt.o !== null) + .concat(Array(subreq.subreq_courses_req_count).fill({ o: null, s: null })); } return updateUserWithNewSubreq(prevUser, majorsIndex, reqIndex, subreqIndex, { ...subreq, - courses_options: updatedCoursesOptions, - student_courses_satisfying: updatedStudentCourses, + subreq_options: updatedSubreqOptions, }); }); } + export function toggleSubreqSelection( setUser: Function, majorsIndex: MajorsIndex, diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index aa2f8ae..a6cbbcc 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -1,14 +1,14 @@ import { User } from "./../types/type-user"; -import { SC_CPSC_201, SC_CPSC_202, SC_CPSC_381, SC_ECON_110 } from "./data-courses"; -import { CONC_HIST_BA_GLOB } from "./programs/concs/concs-hist"; +import { SC_CPSC_201, SC_CPSC_202, SC_CPSC_223, SC_CPSC_323, SC_CPSC_381, SC_ECON_110 } from "./data-courses"; +// import { CONC_HIST_BA_GLOB } from "./programs/concs/concs-hist"; export const Ryan: User = { name: "Ryan", netID: "rgg32", onboard: false, FYP: { - studentCourses: [SC_CPSC_201, SC_CPSC_202, SC_CPSC_381, SC_ECON_110], + studentCourses: [SC_CPSC_201, SC_CPSC_223, SC_CPSC_323, SC_CPSC_381], studentTermArrangement: { first_year: [0, 202403, 202501], sophomore: [0, 202503, 202601], diff --git a/frontend/src/database/programs/concs/concs-cpsc.ts b/frontend/src/database/programs/concs/concs-cpsc.ts index 73770be..8e47b40 100644 --- a/frontend/src/database/programs/concs/concs-cpsc.ts +++ b/frontend/src/database/programs/concs/concs-cpsc.ts @@ -5,62 +5,84 @@ import { CPSC_201, CPSC_202, MATH_244, CPSC_223, CPSC_323, CPSC_365, CPSC_366, C // CORE -const CORE_INTRO: ConcentrationSubrequirement = { +const CORE_1: ConcentrationSubrequirement = { subreq_name: "INTRO", subreq_desc: "", - courses_required: 1, - courses_options: [CPSC_201], - courses_elective_range: null, - courses_any_bool: true, - student_courses_satisfying: [], + subreq_flex: false, + subreq_courses_req_count: 1, + subreq_options: [ + { + o: CPSC_201, + s: null, + } + ] } -const CORE_MATH: ConcentrationSubrequirement = { +const CORE_2: ConcentrationSubrequirement = { subreq_name: "DISCRETE MATH", subreq_desc: "", - courses_required: 1, - courses_options: [CPSC_202, MATH_244], - courses_elective_range: null, - courses_any_bool: true, - student_courses_satisfying: [], + subreq_flex: false, + subreq_courses_req_count: 1, + subreq_options: [ + { + o: CPSC_202, + s: null, + }, + { + o: MATH_244, + s: null, + }, + ] } -const CORE_DATA: ConcentrationSubrequirement = { +const CORE_3: ConcentrationSubrequirement = { subreq_name: "DATA STRUCTURES", subreq_desc: "", - courses_required: 1, - courses_options: [CPSC_223], - courses_elective_range: null, - courses_any_bool: true, - student_courses_satisfying: [], + subreq_flex: false, + subreq_courses_req_count: 1, + subreq_options: [ + { + o: CPSC_223, + s: null, + } + ] } -const CORE_SYS: ConcentrationSubrequirement = { +const CORE_4: ConcentrationSubrequirement = { subreq_name: "SYSTEMS", subreq_desc: "", - courses_required: 1, - courses_options: [CPSC_323], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], + subreq_flex: false, + subreq_courses_req_count: 1, + subreq_options: [ + { + o: CPSC_323, + s: null, + } + ] } -const CORE_ALGO: ConcentrationSubrequirement = { +const CORE_5: ConcentrationSubrequirement = { subreq_name: "ALGORITHMS", subreq_desc: "", - courses_required: 1, - courses_options: [CPSC_365, CPSC_366], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], + subreq_flex: false, + subreq_courses_req_count: 1, + subreq_options: [ + { + o: CPSC_365, + s: null, + }, + { + o: CPSC_366, + s: null, + }, + ] } const CPSC_CORE: ConcentrationRequirement = { req_name: "CORE", req_desc: "", courses_required_count: 5, - courses_satisfied_count: 2, - subreqs_list: [CORE_INTRO, CORE_MATH, CORE_DATA, CORE_SYS, CORE_ALGO] + subreqs_list: [CORE_1, CORE_2, CORE_3, CORE_4, CORE_5] } // ELECTIVE @@ -68,38 +90,43 @@ const CPSC_CORE: ConcentrationRequirement = { const ELEC_MULT_BA: ConcentrationSubrequirement = { subreq_name: "", subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", - courses_required: 3, - courses_options: [null, null, null], - courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, - courses_any_bool: false, - student_courses_satisfying: [] + subreq_flex: false, + subreq_courses_req_count: 3, + subreq_options: [ + { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, + { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, + { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, + ] } const ELEC_MULT_BS: ConcentrationSubrequirement = { subreq_name: "", subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", - courses_required: 5, - courses_options: [null, null, null, null, null], - courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, - courses_any_bool: false, - student_courses_satisfying: [] + subreq_flex: false, + subreq_courses_req_count: 5, + subreq_options: [ + { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, + { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, + { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, + { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, + { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, + ] } const ELEC_SUB: ConcentrationSubrequirement = { subreq_name: "", subreq_desc: "Standard elective or DUS approved extra-department substitution.", - courses_required: 1, - courses_options: [null], - courses_elective_range: { dept: "CPSC", min_code: 300, max_code: 999 }, - courses_any_bool: true, - student_courses_satisfying: [] + subreq_flex: true, + subreq_courses_req_count: 1, + subreq_options: [ + { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 }, a: true } }, + ] } const CPSC_BA_ELEC: ConcentrationRequirement = { req_name: "ELECTIVE", req_desc: "", courses_required_count: 4, - courses_satisfied_count: 1, subreqs_list: [ELEC_SUB, ELEC_MULT_BA] } @@ -107,7 +134,6 @@ const CPSC_BS_ELEC: ConcentrationRequirement = { req_name: "ELECTIVE", req_desc: "", courses_required_count: 6, - courses_satisfied_count: 1, subreqs_list: [ELEC_SUB, ELEC_MULT_BS] } @@ -116,18 +142,20 @@ const CPSC_BS_ELEC: ConcentrationRequirement = { const SEN_PROJ: ConcentrationSubrequirement = { subreq_name: "SENIOR PROJECT", subreq_desc: "", - courses_required: 1, - courses_options: [CPSC_490], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] + subreq_flex: false, + subreq_courses_req_count: 1, + subreq_options: [ + { + o: CPSC_490, + s: null, + } + ] } const CPSC_SENIOR: ConcentrationRequirement = { req_name: "SENIOR", req_desc: "", courses_required_count: 1, - courses_satisfied_count: 0, subreqs_list: [SEN_PROJ] } diff --git a/frontend/src/database/programs/concs/concs-econ.ts b/frontend/src/database/programs/concs/concs-econ.ts index b832a93..98e6c7f 100644 --- a/frontend/src/database/programs/concs/concs-econ.ts +++ b/frontend/src/database/programs/concs/concs-econ.ts @@ -1,143 +1,143 @@ -import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; - -import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151, SC_ECON_110 } from "./../../data-courses"; - -// INTRO - -const INTRO_MATH: ConcentrationSubrequirement = { - subreq_name: "MATH", - subreq_desc: "118 or 120 recommended. Any MATH 200+ satisfies.", - courses_required: 1, - courses_options: [MATH_118, MATH_120], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const INTRO_MICRO: ConcentrationSubrequirement = { - subreq_name: "INTRO MICRO", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_108, ECON_110, ECON_115], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const INTRO_MACRO: ConcentrationSubrequirement = { - subreq_name: "INTRO MACRO", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_111, ECON_116], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const ECON_INTRO: ConcentrationRequirement = { - req_name: "INTRO", - req_desc: "", - courses_required_count: 3, - courses_satisfied_count: 1, - subreqs_list: [INTRO_MATH, INTRO_MICRO, INTRO_MACRO] -} - -// CORE - -const CORE_MICRO: ConcentrationSubrequirement = { - subreq_name: "INTERMEDIATE MICRO", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_121, ECON_125], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const CORE_MACRO: ConcentrationSubrequirement = { - subreq_name: "INTERMEDIATE MACRO", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_122, ECON_126], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const CORE_METRICS: ConcentrationSubrequirement = { - subreq_name: "ECONOMETRICS", - subreq_desc: "", - courses_required: 1, - courses_options: [ECON_117, ECON_123, ECON_136], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const ECON_CORE: ConcentrationRequirement = { - req_name: "CORE", - req_desc: "", - courses_required_count: 3, - courses_satisfied_count: 0, - subreqs_list: [CORE_MICRO, CORE_MACRO, CORE_METRICS] -} - -// ELECTIVE - -const ELEC_STAN: ConcentrationSubrequirement = { - subreq_name: "", - subreq_desc: "Standard elective or DUS approved extra-department substitution.", - courses_required: 1, - courses_options: [null], - courses_elective_range: { dept: "ECON", min_code: 123, max_code: 999 }, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const ELEC_SUB: ConcentrationSubrequirement = { - subreq_name: "", - subreq_desc: "Intermediate or advanced ECON courses, traditionally numbered 123+.", - courses_required: 3, - courses_options: [null, null, null], - courses_elective_range: { dept: "ECON", min_code: 123, max_code: 999 }, - courses_any_bool: true, - student_courses_satisfying: [] -} - -const ECON_ELECTIVE: ConcentrationRequirement = { - req_name: "ELECTIVE", - req_desc: "", - courses_required_count: 4, - courses_satisfied_count: 0, - subreqs_list: [ELEC_STAN, ELEC_SUB] -} - -// SENIOR - -const SEN_REQ: ConcentrationSubrequirement = { - subreq_name: "SENIOR REQUIREMENT", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: { dept: "ECON", min_code: 400, max_code: 491 }, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const ECON_SEN: ConcentrationRequirement = { - req_name: "SENIOR", - req_desc: "", - courses_required_count: 2, - courses_satisfied_count: 0, - subreqs_list: [SEN_REQ] -} - -// // FINAL - -export const CONC_ECON_BA_I: DegreeConcentration = { - user_status: 0, - conc_name: "", - conc_desc: "", - conc_reqs: [ECON_INTRO, ECON_CORE, ECON_ELECTIVE, ECON_SEN] -} +// import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; + +// import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151, SC_ECON_110 } from "./../../data-courses"; + +// // INTRO + +// const INTRO_MATH: ConcentrationSubrequirement = { +// subreq_name: "MATH", +// subreq_desc: "118 or 120 recommended. Any MATH 200+ satisfies.", +// courses_required: 1, +// courses_options: [MATH_118, MATH_120], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const INTRO_MICRO: ConcentrationSubrequirement = { +// subreq_name: "INTRO MICRO", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [ECON_108, ECON_110, ECON_115], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const INTRO_MACRO: ConcentrationSubrequirement = { +// subreq_name: "INTRO MACRO", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [ECON_111, ECON_116], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const ECON_INTRO: ConcentrationRequirement = { +// req_name: "INTRO", +// req_desc: "", +// courses_required_count: 3, +// courses_satisfied_count: 1, +// subreqs_list: [INTRO_MATH, INTRO_MICRO, INTRO_MACRO] +// } + +// // CORE + +// const CORE_MICRO: ConcentrationSubrequirement = { +// subreq_name: "INTERMEDIATE MICRO", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [ECON_121, ECON_125], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const CORE_MACRO: ConcentrationSubrequirement = { +// subreq_name: "INTERMEDIATE MACRO", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [ECON_122, ECON_126], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const CORE_METRICS: ConcentrationSubrequirement = { +// subreq_name: "ECONOMETRICS", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [ECON_117, ECON_123, ECON_136], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const ECON_CORE: ConcentrationRequirement = { +// req_name: "CORE", +// req_desc: "", +// courses_required_count: 3, +// courses_satisfied_count: 0, +// subreqs_list: [CORE_MICRO, CORE_MACRO, CORE_METRICS] +// } + +// // ELECTIVE + +// const ELEC_STAN: ConcentrationSubrequirement = { +// subreq_name: "", +// subreq_desc: "Standard elective or DUS approved extra-department substitution.", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: { dept: "ECON", min_code: 123, max_code: 999 }, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const ELEC_SUB: ConcentrationSubrequirement = { +// subreq_name: "", +// subreq_desc: "Intermediate or advanced ECON courses, traditionally numbered 123+.", +// courses_required: 3, +// courses_options: [null, null, null], +// courses_elective_range: { dept: "ECON", min_code: 123, max_code: 999 }, +// courses_any_bool: true, +// student_courses_satisfying: [] +// } + +// const ECON_ELECTIVE: ConcentrationRequirement = { +// req_name: "ELECTIVE", +// req_desc: "", +// courses_required_count: 4, +// courses_satisfied_count: 0, +// subreqs_list: [ELEC_STAN, ELEC_SUB] +// } + +// // SENIOR + +// const SEN_REQ: ConcentrationSubrequirement = { +// subreq_name: "SENIOR REQUIREMENT", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: { dept: "ECON", min_code: 400, max_code: 491 }, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const ECON_SEN: ConcentrationRequirement = { +// req_name: "SENIOR", +// req_desc: "", +// courses_required_count: 2, +// courses_satisfied_count: 0, +// subreqs_list: [SEN_REQ] +// } + +// // // FINAL + +// export const CONC_ECON_BA_I: DegreeConcentration = { +// user_status: 0, +// conc_name: "", +// conc_desc: "", +// conc_reqs: [ECON_INTRO, ECON_CORE, ECON_ELECTIVE, ECON_SEN] +// } diff --git a/frontend/src/database/programs/concs/concs-hist.ts b/frontend/src/database/programs/concs/concs-hist.ts index 960e095..5d4bbb6 100644 --- a/frontend/src/database/programs/concs/concs-hist.ts +++ b/frontend/src/database/programs/concs/concs-hist.ts @@ -1,235 +1,235 @@ -import { ConcentrationRequirement, ConcentrationSubrequirement, DegreeConcentration } from "@/types/type-program"; - -// PRE - -const PRE_REQ: ConcentrationSubrequirement = { - subreq_name: "PRE 1800", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: null, - courses_any_bool: true, - student_courses_satisfying: [], -} - -const HIST_PRE: ConcentrationRequirement = { - req_name: "PREINDUSTRIAL", - req_desc: "", - courses_required_count: 2, - courses_satisfied_count: 0, - checkbox: true, - subreqs_list: [PRE_REQ] -} - -// GLOB CORE - -const GLOB_CORE_AFRICA: ConcentrationSubrequirement = { - subreq_name: "AFRICA", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: true, - student_courses_satisfying: [], -} - -const GLOB_CORE_ASIA: ConcentrationSubrequirement = { - subreq_name: "ASIA", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: true, - student_courses_satisfying: [], -} - -const GLOB_CORE_EURO: ConcentrationSubrequirement = { - subreq_name: "EUROPE", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: true, - student_courses_satisfying: [], -} - -const GLOB_CORE_LA: ConcentrationSubrequirement = { - subreq_name: "LATIN AMERICA", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: true, - student_courses_satisfying: [], -} - -const GLOB_CORE_ME: ConcentrationSubrequirement = { - subreq_name: "MIDDLE EAST", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: true, - student_courses_satisfying: [], -} - -const GLOB_CORE_US: ConcentrationSubrequirement = { - subreq_name: "U.S.", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: true, - student_courses_satisfying: [], -} - -const HIST_GLOB_CORE: ConcentrationRequirement = { - req_name: "GLOBAL", - req_desc: "", - courses_required_count: 5, - courses_satisfied_count: 0, - subreqs_required_count: 5, - subreqs_satisfied_count: 0, - subreqs_list: [GLOB_CORE_AFRICA, GLOB_CORE_ASIA, GLOB_CORE_EURO, GLOB_CORE_LA, GLOB_CORE_ME, GLOB_CORE_US] -} - -// SPEC CORE - -const SPEC_CORE_IN: ConcentrationSubrequirement = { - subreq_name: "REGION OR PATHWAY", - subreq_desc: "", - courses_required: 5, - courses_options: [null, null, null, null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const SPEC_CORE_OUT: ConcentrationSubrequirement = { - subreq_name: "OUTSIDE", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const HIST_SPEC_CORE: ConcentrationRequirement = { - req_name: "SPECIALIST", - req_desc: "", - courses_required_count: 7, - courses_satisfied_count: 0, - subreqs_required_count: 2, - subreqs_satisfied_count: 0, - subreqs_list: [SPEC_CORE_IN, SPEC_CORE_OUT] -} - -// SEMINAR - -const SEM_REQ: ConcentrationSubrequirement = { - subreq_name: "DEPARTMENTAL", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const HIST_SEM: ConcentrationRequirement = { - req_name: "SEMINAR", - req_desc: "", - courses_required_count: 2, - courses_satisfied_count: 0, - checkbox: true, - subreqs_list: [SEM_REQ] -} - -// GLOB ELEC - -const GLOB_ELEC_REQ: ConcentrationSubrequirement = { - subreq_name: "", - subreq_desc: "", - courses_required: 5, - courses_options: [null, null, null, null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const HIST_GLOB_ELEC: ConcentrationRequirement = { - req_name: "ELECTIVE", - req_desc: "", - courses_required_count: 5, - courses_satisfied_count: 0, - subreqs_list: [GLOB_ELEC_REQ] -} - -// SPEC ELEC - -const SPEC_ELEC_REQ: ConcentrationSubrequirement = { - subreq_name: "", - subreq_desc: "", - courses_required: 3, - courses_options: [null, null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const HIST_SPEC_ELEC: ConcentrationRequirement = { - req_name: "ELECTIVE", - req_desc: "", - courses_required_count: 3, - courses_satisfied_count: 0, - subreqs_list: [SPEC_ELEC_REQ] -} - -// SENIOR - -const SEN_ONE: ConcentrationSubrequirement = { - subreq_name: "ONE TERM", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const SEN_TWO: ConcentrationSubrequirement = { - subreq_name: "TWO TERM", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const HIST_SEN: ConcentrationRequirement = { - req_name: "SENIOR", - req_desc: "", - courses_required_count: -1, - courses_satisfied_count: 0, - subreqs_required_count: 1, - subreqs_satisfied_count: 0, - subreqs_list: [SEN_ONE, SEN_TWO] -} - -// EXPORT - -export const CONC_HIST_BA_GLOB: DegreeConcentration = { - user_status: 0, - conc_name: "GLOBALIST", - conc_desc: "", - conc_reqs: [HIST_PRE, HIST_GLOB_CORE, HIST_SEM, HIST_GLOB_ELEC, HIST_SEN] -} - -export const CONC_HIST_BA_SPEC: DegreeConcentration = { - user_status: 0, - conc_name: "SPECIALIST", - conc_desc: "", - conc_reqs: [HIST_PRE, HIST_SPEC_CORE, HIST_SEM, HIST_SPEC_ELEC, HIST_SEN] -} +// import { ConcentrationRequirement, ConcentrationSubrequirement, DegreeConcentration } from "@/types/type-program"; + +// // PRE + +// const PRE_REQ: ConcentrationSubrequirement = { +// subreq_name: "PRE 1800", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: true, +// student_courses_satisfying: [], +// } + +// const HIST_PRE: ConcentrationRequirement = { +// req_name: "PREINDUSTRIAL", +// req_desc: "", +// courses_required_count: 2, +// courses_satisfied_count: 0, +// checkbox: true, +// subreqs_list: [PRE_REQ] +// } + +// // GLOB CORE + +// const GLOB_CORE_AFRICA: ConcentrationSubrequirement = { +// subreq_name: "AFRICA", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: true, +// student_courses_satisfying: [], +// } + +// const GLOB_CORE_ASIA: ConcentrationSubrequirement = { +// subreq_name: "ASIA", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: true, +// student_courses_satisfying: [], +// } + +// const GLOB_CORE_EURO: ConcentrationSubrequirement = { +// subreq_name: "EUROPE", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: true, +// student_courses_satisfying: [], +// } + +// const GLOB_CORE_LA: ConcentrationSubrequirement = { +// subreq_name: "LATIN AMERICA", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: true, +// student_courses_satisfying: [], +// } + +// const GLOB_CORE_ME: ConcentrationSubrequirement = { +// subreq_name: "MIDDLE EAST", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: true, +// student_courses_satisfying: [], +// } + +// const GLOB_CORE_US: ConcentrationSubrequirement = { +// subreq_name: "U.S.", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: true, +// student_courses_satisfying: [], +// } + +// const HIST_GLOB_CORE: ConcentrationRequirement = { +// req_name: "GLOBAL", +// req_desc: "", +// courses_required_count: 5, +// courses_satisfied_count: 0, +// subreqs_required_count: 5, +// subreqs_satisfied_count: 0, +// subreqs_list: [GLOB_CORE_AFRICA, GLOB_CORE_ASIA, GLOB_CORE_EURO, GLOB_CORE_LA, GLOB_CORE_ME, GLOB_CORE_US] +// } + +// // SPEC CORE + +// const SPEC_CORE_IN: ConcentrationSubrequirement = { +// subreq_name: "REGION OR PATHWAY", +// subreq_desc: "", +// courses_required: 5, +// courses_options: [null, null, null, null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const SPEC_CORE_OUT: ConcentrationSubrequirement = { +// subreq_name: "OUTSIDE", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const HIST_SPEC_CORE: ConcentrationRequirement = { +// req_name: "SPECIALIST", +// req_desc: "", +// courses_required_count: 7, +// courses_satisfied_count: 0, +// subreqs_required_count: 2, +// subreqs_satisfied_count: 0, +// subreqs_list: [SPEC_CORE_IN, SPEC_CORE_OUT] +// } + +// // SEMINAR + +// const SEM_REQ: ConcentrationSubrequirement = { +// subreq_name: "DEPARTMENTAL", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const HIST_SEM: ConcentrationRequirement = { +// req_name: "SEMINAR", +// req_desc: "", +// courses_required_count: 2, +// courses_satisfied_count: 0, +// checkbox: true, +// subreqs_list: [SEM_REQ] +// } + +// // GLOB ELEC + +// const GLOB_ELEC_REQ: ConcentrationSubrequirement = { +// subreq_name: "", +// subreq_desc: "", +// courses_required: 5, +// courses_options: [null, null, null, null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const HIST_GLOB_ELEC: ConcentrationRequirement = { +// req_name: "ELECTIVE", +// req_desc: "", +// courses_required_count: 5, +// courses_satisfied_count: 0, +// subreqs_list: [GLOB_ELEC_REQ] +// } + +// // SPEC ELEC + +// const SPEC_ELEC_REQ: ConcentrationSubrequirement = { +// subreq_name: "", +// subreq_desc: "", +// courses_required: 3, +// courses_options: [null, null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const HIST_SPEC_ELEC: ConcentrationRequirement = { +// req_name: "ELECTIVE", +// req_desc: "", +// courses_required_count: 3, +// courses_satisfied_count: 0, +// subreqs_list: [SPEC_ELEC_REQ] +// } + +// // SENIOR + +// const SEN_ONE: ConcentrationSubrequirement = { +// subreq_name: "ONE TERM", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const SEN_TWO: ConcentrationSubrequirement = { +// subreq_name: "TWO TERM", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const HIST_SEN: ConcentrationRequirement = { +// req_name: "SENIOR", +// req_desc: "", +// courses_required_count: -1, +// courses_satisfied_count: 0, +// subreqs_required_count: 1, +// subreqs_satisfied_count: 0, +// subreqs_list: [SEN_ONE, SEN_TWO] +// } + +// // EXPORT + +// export const CONC_HIST_BA_GLOB: DegreeConcentration = { +// user_status: 0, +// conc_name: "GLOBALIST", +// conc_desc: "", +// conc_reqs: [HIST_PRE, HIST_GLOB_CORE, HIST_SEM, HIST_GLOB_ELEC, HIST_SEN] +// } + +// export const CONC_HIST_BA_SPEC: DegreeConcentration = { +// user_status: 0, +// conc_name: "SPECIALIST", +// conc_desc: "", +// conc_reqs: [HIST_PRE, HIST_SPEC_CORE, HIST_SEM, HIST_SPEC_ELEC, HIST_SEN] +// } diff --git a/frontend/src/database/programs/concs/concs-plsc.ts b/frontend/src/database/programs/concs/concs-plsc.ts index e3bee47..993068d 100644 --- a/frontend/src/database/programs/concs/concs-plsc.ts +++ b/frontend/src/database/programs/concs/concs-plsc.ts @@ -1,222 +1,222 @@ -import { DegreeConcentration, ConcentrationRequirement, ConcentrationSubrequirement } from "@/types/type-program"; -import { PLSC_474, PLSC_490, PLSC_493 } from "@/database/data-courses"; - -// INTRO - -const INTRO_REQ: ConcentrationSubrequirement = { - subreq_name: "INTRO COURSES", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const PLSC_INTRO: ConcentrationRequirement = { - req_name: "INTRO", - req_desc: "", - courses_required_count: 3, - courses_satisfied_count: 1, - subreqs_list: [INTRO_REQ] -} - -// CORE - -const CORE_LECT: ConcentrationSubrequirement = { - subreq_name: "CORE LECTURES", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const CORE_METH: ConcentrationSubrequirement = { - subreq_name: "METHODS AND FORMAL THEORY", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const PLSC_CORE_STAN: ConcentrationRequirement = { - req_name: "CORE", - req_desc: "", - courses_required_count: 3, - courses_satisfied_count: 0, - subreqs_list: [CORE_LECT, CORE_METH] -} - -const CORE_RESE: ConcentrationSubrequirement = { - subreq_name: "RESEARCH", - subreq_desc: "", - courses_required: 1, - courses_options: [PLSC_474], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [], -} - -const PLSC_CORE_INTE: ConcentrationRequirement = { - req_name: "CORE", - req_desc: "", - courses_required_count: 4, - courses_satisfied_count: 0, - subreqs_list: [CORE_LECT, CORE_METH, CORE_RESE] -} - -// ELECTIVE - -const SUB_INTL: ConcentrationSubrequirement = { - subreq_name: "INTERNATIONAL RELATIONS", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const SUB_US: ConcentrationSubrequirement = { - subreq_name: "AMERICAN GOVERNMENT", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const SUB_PHIL: ConcentrationSubrequirement = { - subreq_name: "POLITICAL PHILOSOPHY", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const SUB_COMP: ConcentrationSubrequirement = { - subreq_name: "COMPARATIVE POLITICS", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const PLSC_SUB: ConcentrationRequirement = { - req_name: "SUBFIELDS", - req_desc: "", - courses_required_count: 4, - courses_satisfied_count: 0, - subreqs_required_count: 2, - subreqs_satisfied_count: 0, - subreqs_list: [SUB_INTL, SUB_US, SUB_PHIL, SUB_COMP] -} - -// SEMINAR - -const SEM_ANY: ConcentrationSubrequirement = { - subreq_name: "YEAR ANY", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const SEM_SEN: ConcentrationSubrequirement = { - subreq_name: "YEAR SENIOR", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const PLSC_SEMINAR: ConcentrationRequirement = { - req_name: "SEMINAR", - req_desc: "Seminar courses taught by PLSC faculty satisfy.", - courses_required_count: 2, - courses_satisfied_count: 0, - checkbox: true, - subreqs_list: [SEM_ANY, SEM_SEN] -} - -// SEN STANDARD - -const SEN_STAN_ONE: ConcentrationSubrequirement = { - subreq_name: "ONE TERM", - subreq_desc: "", - courses_required: 1, - courses_options: [null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const SEN_STAN_TWO: ConcentrationSubrequirement = { - subreq_name: "TWO TERM", - subreq_desc: "", - courses_required: 2, - courses_options: [null, null], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const PLSC_SEN_STAN: ConcentrationRequirement = { - req_name: "SENIOR", - req_desc: "", - courses_required_count: -1, - courses_satisfied_count: 0, - subreqs_required_count: 1, - subreqs_satisfied_count: 0, - subreqs_list: [SEN_STAN_ONE, SEN_STAN_TWO] -} - -// SEN INTENSIVE - -const SEN_INTE_REQ: ConcentrationSubrequirement = { - subreq_name: "TWO TERM", - subreq_desc: "", - courses_required: 2, - courses_options: [PLSC_490, PLSC_493], - courses_elective_range: null, - courses_any_bool: false, - student_courses_satisfying: [] -} - -const PLSC_SEN_INTE: ConcentrationRequirement = { - req_name: "SENIOR", - req_desc: "", - courses_required_count: 2, - courses_satisfied_count: 0, - subreqs_list: [SEN_INTE_REQ] -} - -// EXPORT - -export const CONC_PLSC_BA_STAN: DegreeConcentration = { - user_status: 0, - conc_name: "STANDARD", - conc_desc: "", - conc_reqs: [PLSC_INTRO, PLSC_CORE_STAN, PLSC_SUB, PLSC_SEMINAR, PLSC_SEN_STAN] -} - -export const CONC_PLSC_BA_INTE: DegreeConcentration = { - user_status: 0, - conc_name: "INTENSIVE", - conc_desc: "", - conc_reqs: [PLSC_INTRO, PLSC_CORE_INTE, PLSC_SUB, PLSC_SEMINAR, PLSC_SEN_INTE] -} +// import { DegreeConcentration, ConcentrationRequirement, ConcentrationSubrequirement } from "@/types/type-program"; +// import { PLSC_474, PLSC_490, PLSC_493 } from "@/database/data-courses"; + +// // INTRO + +// const INTRO_REQ: ConcentrationSubrequirement = { +// subreq_name: "INTRO COURSES", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const PLSC_INTRO: ConcentrationRequirement = { +// req_name: "INTRO", +// req_desc: "", +// courses_required_count: 3, +// courses_satisfied_count: 1, +// subreqs_list: [INTRO_REQ] +// } + +// // CORE + +// const CORE_LECT: ConcentrationSubrequirement = { +// subreq_name: "CORE LECTURES", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const CORE_METH: ConcentrationSubrequirement = { +// subreq_name: "METHODS AND FORMAL THEORY", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const PLSC_CORE_STAN: ConcentrationRequirement = { +// req_name: "CORE", +// req_desc: "", +// courses_required_count: 3, +// courses_satisfied_count: 0, +// subreqs_list: [CORE_LECT, CORE_METH] +// } + +// const CORE_RESE: ConcentrationSubrequirement = { +// subreq_name: "RESEARCH", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [PLSC_474], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [], +// } + +// const PLSC_CORE_INTE: ConcentrationRequirement = { +// req_name: "CORE", +// req_desc: "", +// courses_required_count: 4, +// courses_satisfied_count: 0, +// subreqs_list: [CORE_LECT, CORE_METH, CORE_RESE] +// } + +// // ELECTIVE + +// const SUB_INTL: ConcentrationSubrequirement = { +// subreq_name: "INTERNATIONAL RELATIONS", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const SUB_US: ConcentrationSubrequirement = { +// subreq_name: "AMERICAN GOVERNMENT", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const SUB_PHIL: ConcentrationSubrequirement = { +// subreq_name: "POLITICAL PHILOSOPHY", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const SUB_COMP: ConcentrationSubrequirement = { +// subreq_name: "COMPARATIVE POLITICS", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const PLSC_SUB: ConcentrationRequirement = { +// req_name: "SUBFIELDS", +// req_desc: "", +// courses_required_count: 4, +// courses_satisfied_count: 0, +// subreqs_required_count: 2, +// subreqs_satisfied_count: 0, +// subreqs_list: [SUB_INTL, SUB_US, SUB_PHIL, SUB_COMP] +// } + +// // SEMINAR + +// const SEM_ANY: ConcentrationSubrequirement = { +// subreq_name: "YEAR ANY", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const SEM_SEN: ConcentrationSubrequirement = { +// subreq_name: "YEAR SENIOR", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const PLSC_SEMINAR: ConcentrationRequirement = { +// req_name: "SEMINAR", +// req_desc: "Seminar courses taught by PLSC faculty satisfy.", +// courses_required_count: 2, +// courses_satisfied_count: 0, +// checkbox: true, +// subreqs_list: [SEM_ANY, SEM_SEN] +// } + +// // SEN STANDARD + +// const SEN_STAN_ONE: ConcentrationSubrequirement = { +// subreq_name: "ONE TERM", +// subreq_desc: "", +// courses_required: 1, +// courses_options: [null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const SEN_STAN_TWO: ConcentrationSubrequirement = { +// subreq_name: "TWO TERM", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [null, null], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const PLSC_SEN_STAN: ConcentrationRequirement = { +// req_name: "SENIOR", +// req_desc: "", +// courses_required_count: -1, +// courses_satisfied_count: 0, +// subreqs_required_count: 1, +// subreqs_satisfied_count: 0, +// subreqs_list: [SEN_STAN_ONE, SEN_STAN_TWO] +// } + +// // SEN INTENSIVE + +// const SEN_INTE_REQ: ConcentrationSubrequirement = { +// subreq_name: "TWO TERM", +// subreq_desc: "", +// courses_required: 2, +// courses_options: [PLSC_490, PLSC_493], +// courses_elective_range: null, +// courses_any_bool: false, +// student_courses_satisfying: [] +// } + +// const PLSC_SEN_INTE: ConcentrationRequirement = { +// req_name: "SENIOR", +// req_desc: "", +// courses_required_count: 2, +// courses_satisfied_count: 0, +// subreqs_list: [SEN_INTE_REQ] +// } + +// // EXPORT + +// export const CONC_PLSC_BA_STAN: DegreeConcentration = { +// user_status: 0, +// conc_name: "STANDARD", +// conc_desc: "", +// conc_reqs: [PLSC_INTRO, PLSC_CORE_STAN, PLSC_SUB, PLSC_SEMINAR, PLSC_SEN_STAN] +// } + +// export const CONC_PLSC_BA_INTE: DegreeConcentration = { +// user_status: 0, +// conc_name: "INTENSIVE", +// conc_desc: "", +// conc_reqs: [PLSC_INTRO, PLSC_CORE_INTE, PLSC_SUB, PLSC_SEMINAR, PLSC_SEN_INTE] +// } diff --git a/frontend/src/database/programs/data-program.ts b/frontend/src/database/programs/data-program.ts index 1c6dcb1..c5e6fba 100644 --- a/frontend/src/database/programs/data-program.ts +++ b/frontend/src/database/programs/data-program.ts @@ -1,9 +1,9 @@ import { Program, ProgramDict } from "@/types/type-program"; import { CONC_CPSC_BA_I, CONC_CPSC_BS_I } from "./concs/concs-cpsc"; -import { CONC_ECON_BA_I } from "./concs/concs-econ"; -import { CONC_HIST_BA_GLOB, CONC_HIST_BA_SPEC } from "./concs/concs-hist"; -import { CONC_PLSC_BA_INTE, CONC_PLSC_BA_STAN } from "./concs/concs-plsc"; +// import { CONC_ECON_BA_I } from "./concs/concs-econ"; +// import { CONC_HIST_BA_GLOB, CONC_HIST_BA_SPEC } from "./concs/concs-hist"; +// import { CONC_PLSC_BA_INTE, CONC_PLSC_BA_STAN } from "./concs/concs-plsc"; const PROG_CPSC: Program = { prog_data: { @@ -20,54 +20,54 @@ const PROG_CPSC: Program = { ] } -const PROG_ECON: Program = { - prog_data: { - prog_name: "Economics", - prog_abbr: "ECON", - prog_stud_count: 0, - prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, - prog_catolog: "", - prog_website: "" - }, - prog_degs: [ - { deg_type: "B.A.", deg_concs: [CONC_ECON_BA_I] } - ] -} +// const PROG_ECON: Program = { +// prog_data: { +// prog_name: "Economics", +// prog_abbr: "ECON", +// prog_stud_count: 0, +// prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, +// prog_catolog: "", +// prog_website: "" +// }, +// prog_degs: [ +// { deg_type: "B.A.", deg_concs: [CONC_ECON_BA_I] } +// ] +// } -const PROG_PLSC: Program = { - prog_data: { - prog_name: "Political Science", - prog_abbr: "PLSC", - prog_stud_count: 0, - prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, - prog_catolog: "", - prog_website: "" - }, - prog_degs: [ - { deg_type: "B.A.", deg_concs: [CONC_PLSC_BA_STAN, CONC_PLSC_BA_INTE] } - ] -} +// const PROG_PLSC: Program = { +// prog_data: { +// prog_name: "Political Science", +// prog_abbr: "PLSC", +// prog_stud_count: 0, +// prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, +// prog_catolog: "", +// prog_website: "" +// }, +// prog_degs: [ +// { deg_type: "B.A.", deg_concs: [CONC_PLSC_BA_STAN, CONC_PLSC_BA_INTE] } +// ] +// } -const PROG_HIST: Program = { - prog_data: { - prog_name: "History", - prog_abbr: "HIST", - prog_stud_count: 0, - prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, - prog_catolog: "", - prog_website: "" - }, - prog_degs: [ - { - deg_type: "B.A.", - deg_concs: [CONC_HIST_BA_GLOB, CONC_HIST_BA_SPEC] - } - ] -} +// const PROG_HIST: Program = { +// prog_data: { +// prog_name: "History", +// prog_abbr: "HIST", +// prog_stud_count: 0, +// prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, +// prog_catolog: "", +// prog_website: "" +// }, +// prog_degs: [ +// { +// deg_type: "B.A.", +// deg_concs: [CONC_HIST_BA_GLOB, CONC_HIST_BA_SPEC] +// } +// ] +// } export const PROG_DICT: ProgramDict = { "CPSC": PROG_CPSC, - "ECON": PROG_ECON, - "PLSC": PROG_PLSC, - "HIST": PROG_HIST, + // "ECON": PROG_ECON, + // "PLSC": PROG_PLSC, + // "HIST": PROG_HIST, }; diff --git a/frontend/src/types/type-program.ts b/frontend/src/types/type-program.ts index bc032e9..734b5d5 100644 --- a/frontend/src/types/type-program.ts +++ b/frontend/src/types/type-program.ts @@ -3,26 +3,29 @@ import { Course, StudentCourse } from "./type-user"; export interface ElectiveRange { dept: string; - min_code: number; - max_code: number; + min: number; + max: number; } -export type SubreqElectiveRange = ElectiveRange| null; +interface OptionNullHelp { + e?: ElectiveRange; + f?: string[]; + a?: boolean; +} + +export interface SubreqCourseOption { + o: Course | null; + s: StudentCourse | null; + n?: OptionNullHelp | null; +} export interface ConcentrationSubrequirement { subreq_name: string; subreq_desc: string; - courses_required: number; - courses_options: (Course | null)[]; - courses_elective_range: SubreqElectiveRange; - - courses_any_bool: boolean; - flags?: string[]; - - student_courses_satisfying: StudentCourse[]; - - // selected?: boolean; + subreq_flex: boolean; + subreq_courses_req_count: number; + subreq_options: SubreqCourseOption[] } export interface ConcentrationRequirement { @@ -30,7 +33,6 @@ export interface ConcentrationRequirement { req_desc: string; courses_required_count: number; - courses_satisfied_count: number; subreqs_required_count?: number; subreqs_satisfied_count?: number; diff --git a/frontend/src/utils/preprocessing/Fill.ts b/frontend/src/utils/preprocessing/Fill.ts index 2ad5563..11b83c4 100644 --- a/frontend/src/utils/preprocessing/Fill.ts +++ b/frontend/src/utils/preprocessing/Fill.ts @@ -1,27 +1,180 @@ import { StudentCourse } from "@/types/type-user"; -import { ProgramDict, DegreeConcentration, ConcentrationSubrequirement } from "@/types/type-program"; +import { ConcentrationSubrequirement, DegreeConcentration, ProgramDict } from "@/types/type-program"; +/** + * First Pass: Fills `s` in options where `o` is non-null for non-checkbox requirements. + */ +function fillDirectMatches( + concentration: DegreeConcentration, + studentCourses: StudentCourse[], + usedCourses: Set +): DegreeConcentration { + return { + ...concentration, + conc_reqs: concentration.conc_reqs.map(req => { + if (req.checkbox) return req; + return { + ...req, + subreqs_list: req.subreqs_list.map(subreq => ({ + ...subreq, + subreq_options: subreq.subreq_options.map(option => { + if (!option.o || option.s) return option; // Skip null `o` or already filled `s` + const matchingStudentCourse = studentCourses.find(sc => + sc.course.codes.includes(option.o!.codes[0]) && !usedCourses.has(sc.course.codes[0]) + ); + if (matchingStudentCourse) { + usedCourses.add(matchingStudentCourse.course.codes[0]); // Track usage + return { ...option, s: matchingStudentCourse }; + } + return option; + }), + })), + }; + }), + }; +} + +/** + * Second Pass: Fills `s` using elective ranges in non-checkbox requirements. + * Iterates through non-flex subreqs first, then flex. + */ +function fillElectiveRanges( + concentration: DegreeConcentration, + studentCourses: StudentCourse[], + usedCourses: Set +): DegreeConcentration { + return { + ...concentration, + conc_reqs: concentration.conc_reqs.map(req => { + if (req.checkbox) return req; // Skip checkbox reqs + + // First Pass: Fill non-flex subreqs, skipping flex ones + let updatedSubreqs = req.subreqs_list.map(subreq => { + if (subreq.subreq_flex) return subreq; // Skip flex subreqs in first pass + return fillSubreqElectives(subreq, studentCourses, usedCourses); + }); + + // Second Pass: Fill flex subreqs, keeping previous updates + updatedSubreqs = updatedSubreqs.map(subreq => { + if (!subreq.subreq_flex) return subreq; // Skip non-flex subreqs in second pass + return fillSubreqElectives(subreq, studentCourses, usedCourses); + }); + + return { ...req, subreqs_list: updatedSubreqs }; + }), + }; +} + +/** + * Helper function to fill elective ranges in a subreq. + */ +function fillSubreqElectives( + subreq: ConcentrationSubrequirement, + studentCourses: StudentCourse[], + usedCourses: Set +): ConcentrationSubrequirement { + return { + ...subreq, + subreq_options: subreq.subreq_options.map(option => { + if (option.o !== null || !option.n?.e || option.s) return option; // Skip already filled slots + + const { dept, min, max } = option.n.e; + const matchingStudentCourse = studentCourses.find(sc => + sc.course.codes.some(code => + code.startsWith(dept) && + parseInt(code.replace(dept, ""), 10) >= min && + parseInt(code.replace(dept, ""), 10) <= max && + !usedCourses.has(sc.course.codes[0]) // Ensure it's not used + ) + ); + + if (matchingStudentCourse) { + usedCourses.add(matchingStudentCourse.course.codes[0]); // Track usage + return { ...option, s: matchingStudentCourse }; + } + + return option; + }), + }; +} + +/** + * Third Pass: Fills `s` in checkbox requirements, allowing reuse but preventing duplicates within the req. + */ +function fillCheckboxReqs( + concentration: DegreeConcentration, + studentCourses: StudentCourse[] +): DegreeConcentration { + return { + ...concentration, + conc_reqs: concentration.conc_reqs.map(req => { + if (!req.checkbox) return req; // Skip non-checkbox reqs + + const usedWithinCheckboxReq = new Set(); // Reset per checkbox req + + return { + ...req, + subreqs_list: req.subreqs_list.map(subreq => ({ + ...subreq, + subreq_options: subreq.subreq_options.map(option => { + if (option.s) return option; // If already filled, keep it + + const matchingStudentCourse = studentCourses.find(sc => + (!option.o || sc.course.codes.includes(option.o.codes[0])) && + (!usedWithinCheckboxReq.has(sc.course.codes[0])) // Ensure unique within the req + ); + if (matchingStudentCourse) { + usedWithinCheckboxReq.add(matchingStudentCourse.course.codes[0]); // Track within checkbox req + return { ...option, s: matchingStudentCourse }; + } + return option; + }), + })), + }; + }), + }; +} + +/** + * Fills student courses for a given concentration by running the three passes. + */ +function fillStudentCourses( + concentration: DegreeConcentration, + studentCourses: StudentCourse[] +): DegreeConcentration { + const usedCourses = new Set(); // Tracks courses used in non-checkbox reqs + + // Apply filling in three passes + let updatedConcentration = fillDirectMatches(concentration, studentCourses, usedCourses); + updatedConcentration = fillElectiveRanges(updatedConcentration, studentCourses, usedCourses); + updatedConcentration = fillCheckboxReqs(updatedConcentration, studentCourses); + + return updatedConcentration; +} +/** + * Main function - updates entire `progDict` by filling student courses in each concentration. + */ export function fill( - studentCourses: StudentCourse[], - progDict: ProgramDict, + studentCourses: StudentCourse[], + progDict: ProgramDict, setProgDict: Function ) { const updatedProgDict = { ...progDict }; - // Object.keys(updatedProgDict).forEach(progKey => { - // updatedProgDict[progKey].prog_degs = updatedProgDict[progKey].prog_degs.map(deg => ({ - // ...deg, - // deg_concs: deg.deg_concs.map(conc => fillConcentration(conc, studentCourses)) - // })); - // }); + Object.keys(updatedProgDict).forEach(progKey => { + updatedProgDict[progKey].prog_degs = updatedProgDict[progKey].prog_degs.map(deg => ({ + ...deg, + deg_concs: deg.deg_concs.map(conc => fillStudentCourses(conc, studentCourses)), + })); + }); setProgDict(updatedProgDict); } From 7576230056efa73313568463dc4e995adf36c886 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Mon, 17 Mar 2025 17:43:03 -0700 Subject: [PATCH 33/38] autofill --- frontend/src/api/api.ts | 10 +- frontend/src/app/api/programs/route.ts | 7 + .../semester/add-course/AddCourseUtils.ts | 74 +--- frontend/src/app/layout.tsx | 8 +- frontend/src/context/AuthProvider.tsx | 54 ++- frontend/src/context/ProgramProvider.tsx | 63 +++- frontend/src/database/data-courses.ts | 1 + frontend/src/database/data-user.ts | 4 +- .../src/database/programs/concs/concs-econ.ts | 320 +++++++++++------- .../src/database/programs/data-program.ts | 5 +- frontend/src/utils/preprocessing/Fill.ts | 264 +++++++-------- 11 files changed, 432 insertions(+), 378 deletions(-) create mode 100644 frontend/src/app/api/programs/route.ts diff --git a/frontend/src/api/api.ts b/frontend/src/api/api.ts index bba7e6b..dd6cfab 100644 --- a/frontend/src/api/api.ts +++ b/frontend/src/api/api.ts @@ -1,8 +1,10 @@ import { Ryan } from "@/database/data-user"; +import { PROG_DICT } from "@/database/programs/data-program"; import { NextRequest, NextResponse } from "next/server"; -export function login(req: NextRequest) { +export function login(req: NextRequest) +{ const user = Ryan; const response = NextResponse.json(user, { status: 200 }); @@ -17,3 +19,9 @@ export function login(req: NextRequest) { return response; } + +export function programs(req: NextRequest) +{ + return NextResponse.json(PROG_DICT, { status: 200 });; +} + diff --git a/frontend/src/app/api/programs/route.ts b/frontend/src/app/api/programs/route.ts new file mode 100644 index 0000000..5164cde --- /dev/null +++ b/frontend/src/app/api/programs/route.ts @@ -0,0 +1,7 @@ + +import { NextRequest } from "next/server"; +import { programs } from "@/api/api"; + +export async function GET(req: NextRequest) { + return programs(req); +} diff --git a/frontend/src/app/courses/years/semester/add-course/AddCourseUtils.ts b/frontend/src/app/courses/years/semester/add-course/AddCourseUtils.ts index 50c8131..dd30e92 100644 --- a/frontend/src/app/courses/years/semester/add-course/AddCourseUtils.ts +++ b/frontend/src/app/courses/years/semester/add-course/AddCourseUtils.ts @@ -2,16 +2,7 @@ import { User, StudentCourse } from "@/types/type-user"; import { getCatalogCourse } from "@/database/data-catalog"; import { AddCourseDisplay } from "./AddCourseButton"; -import { usePrograms } from "@/context/ProgramProvider"; -import { ConcentrationSubrequirement, DegreeConcentration, ProgramDegree, ProgramDict } from "@/types/type-program"; - -// TODO: -// Two tasks. -// (1) Iterate through the entire progDict. If the new StudentCourse's course attribute -// corresponds to a course in a subreqs course_options array, add the StudentCourse -// to the user_courses_satisfying array. -// (2) Iterate through all studentconcs in user.FYP.decl list, updating their -// user_conc DegreeConcentrations in the same way. +import { ProgramDict } from "@/types/type-program"; export function executeAddCourse( term: number, @@ -34,73 +25,12 @@ export function executeAddCourse( const status = selectedTerm === term ? "DA" : "MA"; const newCourse: StudentCourse = { course: targetCourse, status, term, result: selectedResult }; - // ✅ Step 1: Update `user.FYP.studentCourses` const updatedCourses = [...user.FYP.studentCourses, newCourse]; - // ✅ Step 2: Function to update `student_courses_satisfying` in a given subreq - function updateSubreqCourses(subreq: ConcentrationSubrequirement) { - // Check if the course is in `courses_options` and if it's not already in `student_courses_satisfying` - if (subreq.courses_options.some((c) => c?.codes.includes(targetCode)) && - !subreq.student_courses_satisfying.some((sc) => sc.course.codes.includes(targetCode))) { - return { - ...subreq, - student_courses_satisfying: [...subreq.student_courses_satisfying, newCourse] - }; - } - return subreq; - } - - // ✅ Step 3: Iterate through `user.FYP.decl_list` (Pinned Concentrations) - const updatedDeclList = user.FYP.decl_list.map((studentConc) => { - return { - ...studentConc, - user_conc: { - ...studentConc.user_conc, - conc_reqs: studentConc.user_conc.conc_reqs.map((req) => ({ - ...req, - subreqs_list: req.subreqs_list.map(updateSubreqCourses) - })) - } - }; - }); - - // ✅ Step 4: Iterate through `progDict` to update **ALL** programs - const updatedProgDict = { ...progDict }; - - Object.keys(updatedProgDict).forEach((progKey) => { - updatedProgDict[progKey].prog_degs = updatedProgDict[progKey].prog_degs.map((deg: ProgramDegree) => ({ - ...deg, - deg_concs: deg.deg_concs.map((conc: DegreeConcentration) => ({ - ...conc, - conc_reqs: conc.conc_reqs.map((req) => ({ - ...req, - subreqs_list: req.subreqs_list.map(updateSubreqCourses) - })) - })) - })); - }); - - // ✅ Step 5: Update State - setUser({ - ...user, - FYP: { - ...user.FYP, - studentCourses: updatedCourses, - decl_list: updatedDeclList - } - }); - - setProgDict(updatedProgDict); - - // ✅ Step 6: Close Input Box + setUser({ ...user, FYP: { ...user.FYP, studentCourses: updatedCourses } }); setAddDisplay((prevState: AddCourseDisplay) => ({ ...prevState, active: false })); } - - - - - // export async function fetchAndCacheCourses( // selectedTerm: number, // setSearchData: Function diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 4f0bf4c..0aa3c89 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -12,11 +12,11 @@ export default function RootLayout({children}: {children: React.ReactNode}) return( - - + + {children} - - + + ) diff --git a/frontend/src/context/AuthProvider.tsx b/frontend/src/context/AuthProvider.tsx index 18c0e5a..77a0cfd 100644 --- a/frontend/src/context/AuthProvider.tsx +++ b/frontend/src/context/AuthProvider.tsx @@ -1,28 +1,62 @@ "use client"; -import { createContext, useContext, useState, useEffect } from "react"; - +import { createContext, useContext, useState, useEffect, useCallback } from "react"; import { User } from "@/types/type-user"; import { Ryan } from "@/database/data-user"; +import { usePrograms } from "@/context/ProgramProvider"; +import { fill } from "@/utils/preprocessing/Fill"; + +// Define context type +interface AuthContextType { + auth: { loggedIn: boolean }; + setAuth: (auth: { loggedIn: boolean }) => void; + user: User; + setUser: (user: User) => void; +} -const AuthContext = createContext(null); +const AuthContext = createContext(null); -export function AuthProvider({ children }: { children: React.ReactNode }) -{ +export function AuthProvider({ children }: { children: React.ReactNode }) { + const { setProgDict, baseProgDict } = usePrograms(); const [auth, setAuth] = useState({ loggedIn: false }); const [user, setUser] = useState(Ryan); - useEffect(() => { - setUser(Ryan); + // Create a stable reference to the combined reset and fill function + const resetAndFill = useCallback(() => { + const studentCourses = user.FYP.studentCourses; + + if (studentCourses.length > 0) { + // Create a deep clone of baseProgDict + const freshCopy = JSON.parse(JSON.stringify(baseProgDict)); + + // Pass the fresh copy to fill - the fill function will handle the state update + fill(studentCourses, freshCopy, setProgDict); + } + }, [user.FYP.studentCourses, baseProgDict, setProgDict]); + + // Set initial user data + useEffect(() => { + setUser(Ryan); }, []); - return( + // Update program data when courses change + useEffect(() => { + if (user.FYP.studentCourses.length > 0) { + resetAndFill(); + } + }, [user.FYP.studentCourses, resetAndFill]); + + return ( {children} ); } -export function useAuth(){ - return useContext(AuthContext); +export function useAuth(): AuthContextType { + const context = useContext(AuthContext); + if (!context) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; } diff --git a/frontend/src/context/ProgramProvider.tsx b/frontend/src/context/ProgramProvider.tsx index 2d1366c..a89595a 100644 --- a/frontend/src/context/ProgramProvider.tsx +++ b/frontend/src/context/ProgramProvider.tsx @@ -1,27 +1,70 @@ "use client"; -import { createContext, useContext, useState, useEffect } from "react"; +import { createContext, useContext, useState, useEffect, useCallback } from "react"; import { ProgramDict } from "@/types/type-program"; -import { PROG_DICT } from "@/database/programs/data-program"; -const ProgramContext = createContext(null); +// Define context type +interface ProgramContextType { + progDict: ProgramDict; + setProgDict: (dict: ProgramDict) => void; + baseProgDict: ProgramDict; + isLoading: boolean; + error: string | null; + resetToBase: () => void; +} + +const ProgramContext = createContext(null); export function ProgramProvider({ children }: { children: React.ReactNode }) { - + const [isLoading, setIsLoading] = useState(true); + const [baseProgDict, setBaseProgDict] = useState({}); const [progDict, setProgDict] = useState({}); + const [error, setError] = useState(null); useEffect(() => { - setProgDict(PROG_DICT); - }, []); + const fetchPrograms = async () => { + setIsLoading(true); + try { + const response = await fetch('/api/programs'); + if (!response.ok) throw new Error('Failed to fetch programs'); + + const fetchedData = await response.json(); + // Store as separate objects to prevent reference issues + setBaseProgDict(JSON.parse(JSON.stringify(fetchedData))); + setProgDict(JSON.parse(JSON.stringify(fetchedData))); + } catch (err) { + setError(err instanceof Error ? err.message : 'Unknown error'); + } finally { + setIsLoading(false); + } + }; + + fetchPrograms(); + }, []); + + // Deep clone when resetting to base + const resetToBase = useCallback(() => { + setProgDict(JSON.parse(JSON.stringify(baseProgDict))); + }, [baseProgDict]); return ( - + {children} ); } -export function usePrograms() { - return useContext(ProgramContext); +export function usePrograms(): ProgramContextType { + const context = useContext(ProgramContext); + if (!context) { + throw new Error('usePrograms must be used within a ProgramProvider'); + } + return context; } - diff --git a/frontend/src/database/data-courses.ts b/frontend/src/database/data-courses.ts index bb85299..0714e29 100644 --- a/frontend/src/database/data-courses.ts +++ b/frontend/src/database/data-courses.ts @@ -52,6 +52,7 @@ export const SC_CPSC_202: StudentCourse = { term: 202403, status: "DA", result: export const SC_CPSC_223: StudentCourse = { term: 202501, status: "DA", result: "GRADE_PASS", course: CPSC_223 } export const SC_CPSC_323: StudentCourse = { term: 202503, status: "DA", result: "GRADE_PASS", course: CPSC_323 } export const SC_CPSC_381: StudentCourse = { term: 202503, status: "DA", result: "GRADE_PASS", course: CPSC_381 } +export const SC_CPSC_490: StudentCourse = { term: 202503, status: "DA", result: "GRADE_PASS", course: CPSC_490 } // ECON COURSES export const SC_ECON_110: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: ECON_110 } diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index a6cbbcc..1d06ea8 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -1,6 +1,6 @@ import { User } from "./../types/type-user"; -import { SC_CPSC_201, SC_CPSC_202, SC_CPSC_223, SC_CPSC_323, SC_CPSC_381, SC_ECON_110 } from "./data-courses"; +import { SC_CPSC_201, SC_CPSC_223, SC_CPSC_323, SC_CPSC_381, SC_CPSC_490, SC_ECON_110 } from "./data-courses"; // import { CONC_HIST_BA_GLOB } from "./programs/concs/concs-hist"; export const Ryan: User = { @@ -8,7 +8,7 @@ export const Ryan: User = { netID: "rgg32", onboard: false, FYP: { - studentCourses: [SC_CPSC_201, SC_CPSC_223, SC_CPSC_323, SC_CPSC_381], + studentCourses: [SC_CPSC_201, SC_CPSC_223, SC_CPSC_323, SC_CPSC_381, SC_CPSC_490], studentTermArrangement: { first_year: [0, 202403, 202501], sophomore: [0, 202503, 202601], diff --git a/frontend/src/database/programs/concs/concs-econ.ts b/frontend/src/database/programs/concs/concs-econ.ts index 98e6c7f..a1a0b5e 100644 --- a/frontend/src/database/programs/concs/concs-econ.ts +++ b/frontend/src/database/programs/concs/concs-econ.ts @@ -1,143 +1,203 @@ -// import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; - -// import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151, SC_ECON_110 } from "./../../data-courses"; +import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; +import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151, SC_ECON_110 } from "./../../data-courses"; // // INTRO -// const INTRO_MATH: ConcentrationSubrequirement = { -// subreq_name: "MATH", -// subreq_desc: "118 or 120 recommended. Any MATH 200+ satisfies.", -// courses_required: 1, -// courses_options: [MATH_118, MATH_120], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const INTRO_MICRO: ConcentrationSubrequirement = { -// subreq_name: "INTRO MICRO", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [ECON_108, ECON_110, ECON_115], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const INTRO_MACRO: ConcentrationSubrequirement = { -// subreq_name: "INTRO MACRO", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [ECON_111, ECON_116], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const ECON_INTRO: ConcentrationRequirement = { -// req_name: "INTRO", -// req_desc: "", -// courses_required_count: 3, -// courses_satisfied_count: 1, -// subreqs_list: [INTRO_MATH, INTRO_MICRO, INTRO_MACRO] -// } +const INTRO_1: ConcentrationSubrequirement = { + subreq_name: "MATH", + subreq_desc: "118 or 120 recommended. Any MATH 200+ satisfies.", + subreq_flex: true, + subreq_courses_req_count: 1, + subreq_options: [ + { + o: MATH_118, + s: null, + }, + { + o: MATH_120, + s: null, + }, + ] +} + +const INTRO_2: ConcentrationSubrequirement = { + subreq_name: "INTRO MICRO", + subreq_desc: "", + subreq_flex: true, + subreq_courses_req_count: 1, + subreq_options: [ + { + o: ECON_108, + s: null, + }, + { + o: ECON_110, + s: null, + }, + { + o: ECON_115, + s: null, + }, + ] +} + +const INTRO_3: ConcentrationSubrequirement = { + subreq_name: "INTRO MACRO", + subreq_desc: "", + subreq_flex: true, + subreq_courses_req_count: 1, + subreq_options: [ + { + o: ECON_111, + s: null, + }, + { + o: ECON_116, + s: null, + }, + ] +} + +const ECON_INTRO: ConcentrationRequirement = { + req_name: "INTRO", + req_desc: "", + courses_required_count: 3, + subreqs_list: [INTRO_1, INTRO_2, INTRO_3] +} // // CORE -// const CORE_MICRO: ConcentrationSubrequirement = { -// subreq_name: "INTERMEDIATE MICRO", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [ECON_121, ECON_125], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const CORE_MACRO: ConcentrationSubrequirement = { -// subreq_name: "INTERMEDIATE MACRO", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [ECON_122, ECON_126], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const CORE_METRICS: ConcentrationSubrequirement = { -// subreq_name: "ECONOMETRICS", -// subreq_desc: "", -// courses_required: 1, -// courses_options: [ECON_117, ECON_123, ECON_136], -// courses_elective_range: null, -// courses_any_bool: false, -// student_courses_satisfying: [], -// } - -// const ECON_CORE: ConcentrationRequirement = { -// req_name: "CORE", -// req_desc: "", -// courses_required_count: 3, -// courses_satisfied_count: 0, -// subreqs_list: [CORE_MICRO, CORE_MACRO, CORE_METRICS] -// } +const CORE_MICRO: ConcentrationSubrequirement = { + subreq_name: "INTERMEDIATE MICRO", + subreq_desc: "", + subreq_flex: false, + subreq_courses_req_count: 1, + subreq_options: [ + { + o: ECON_121, + s: null, + }, + { + o: ECON_125, + s: null, + }, + ] +} + +const CORE_MACRO: ConcentrationSubrequirement = { + subreq_name: "INTERMEDIATE MACRO", + subreq_desc: "", + subreq_flex: false, + subreq_courses_req_count: 1, + subreq_options: [ + { + o: ECON_122, + s: null, + }, + { + o: ECON_126, + s: null, + }, + ] +} + +const CORE_METRICS: ConcentrationSubrequirement = { + subreq_name: "ECONOMETRICS", + subreq_desc: "", + subreq_flex: false, + subreq_courses_req_count: 1, + subreq_options: [ + { + o: ECON_117, + s: null, + }, + { + o: ECON_123, + s: null, + }, + { + o: ECON_136, + s: null, + }, + ] +} + +const ECON_CORE: ConcentrationRequirement = { + req_name: "CORE", + req_desc: "", + courses_required_count: 3, + subreqs_list: [CORE_MICRO, CORE_MACRO, CORE_METRICS] +} // // ELECTIVE -// const ELEC_STAN: ConcentrationSubrequirement = { -// subreq_name: "", -// subreq_desc: "Standard elective or DUS approved extra-department substitution.", -// courses_required: 1, -// courses_options: [null], -// courses_elective_range: { dept: "ECON", min_code: 123, max_code: 999 }, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const ELEC_SUB: ConcentrationSubrequirement = { -// subreq_name: "", -// subreq_desc: "Intermediate or advanced ECON courses, traditionally numbered 123+.", -// courses_required: 3, -// courses_options: [null, null, null], -// courses_elective_range: { dept: "ECON", min_code: 123, max_code: 999 }, -// courses_any_bool: true, -// student_courses_satisfying: [] -// } - -// const ECON_ELECTIVE: ConcentrationRequirement = { -// req_name: "ELECTIVE", -// req_desc: "", -// courses_required_count: 4, -// courses_satisfied_count: 0, -// subreqs_list: [ELEC_STAN, ELEC_SUB] -// } - -// // SENIOR - -// const SEN_REQ: ConcentrationSubrequirement = { -// subreq_name: "SENIOR REQUIREMENT", -// subreq_desc: "", -// courses_required: 2, -// courses_options: [null, null], -// courses_elective_range: { dept: "ECON", min_code: 400, max_code: 491 }, -// courses_any_bool: false, -// student_courses_satisfying: [] -// } - -// const ECON_SEN: ConcentrationRequirement = { -// req_name: "SENIOR", -// req_desc: "", -// courses_required_count: 2, -// courses_satisfied_count: 0, -// subreqs_list: [SEN_REQ] -// } +const ELEC_SUB: ConcentrationSubrequirement = { + subreq_name: "", + subreq_desc: "Standard elective or DUS approved extra-department substitution.", + subreq_flex: true, + subreq_courses_req_count: 1, + subreq_options: [ + { o: null, s: null, n: { e: { dept: "ECON", min: 123, max: 399 }, a: true } }, + ] +} + +const ELEC_STAN: ConcentrationSubrequirement = { + subreq_name: "", + subreq_desc: "Intermediate or advanced ECON courses, traditionally numbered 123+.", + subreq_flex: true, + subreq_courses_req_count: 3, + subreq_options: [ + { o: null, s: null, n: { e: { dept: "ECON", min: 123, max: 399 } } }, + { o: null, s: null, n: { e: { dept: "ECON", min: 123, max: 399 } } }, + { o: null, s: null, n: { e: { dept: "ECON", min: 123, max: 399 } } }, + ] +} + +const ECON_ELECTIVE: ConcentrationRequirement = { + req_name: "ELECTIVE", + req_desc: "", + courses_required_count: 4, + subreqs_list: [ELEC_STAN, ELEC_SUB] +} + +// SENIOR + +const SEN_REQ: ConcentrationSubrequirement = { + subreq_name: "SENIOR REQUIREMENT", + subreq_desc: "", + subreq_flex: true, + subreq_courses_req_count: 2, + subreq_options: [ + { + o: null, + s: null, + n: { + e: { dept: "ECON", min: 400, max: 491 } + } + }, + { + o: null, + s: null, + n: { + e: { dept: "ECON", min: 400, max: 491 } + } + }, + ] +} + +const ECON_SEN: ConcentrationRequirement = { + req_name: "SENIOR", + req_desc: "", + courses_required_count: 2, + subreqs_list: [SEN_REQ] +} // // // FINAL -// export const CONC_ECON_BA_I: DegreeConcentration = { -// user_status: 0, -// conc_name: "", -// conc_desc: "", -// conc_reqs: [ECON_INTRO, ECON_CORE, ECON_ELECTIVE, ECON_SEN] -// } +export const CONC_ECON_BA_I: DegreeConcentration = { + user_status: 0, + conc_name: "", + conc_desc: "", + conc_reqs: [ECON_INTRO, ECON_CORE, ECON_ELECTIVE, ECON_SEN] +} diff --git a/frontend/src/database/programs/data-program.ts b/frontend/src/database/programs/data-program.ts index c5e6fba..54ba109 100644 --- a/frontend/src/database/programs/data-program.ts +++ b/frontend/src/database/programs/data-program.ts @@ -1,7 +1,7 @@ import { Program, ProgramDict } from "@/types/type-program"; import { CONC_CPSC_BA_I, CONC_CPSC_BS_I } from "./concs/concs-cpsc"; -// import { CONC_ECON_BA_I } from "./concs/concs-econ"; +import { CONC_ECON_BA_I } from "./concs/concs-econ"; // import { CONC_HIST_BA_GLOB, CONC_HIST_BA_SPEC } from "./concs/concs-hist"; // import { CONC_PLSC_BA_INTE, CONC_PLSC_BA_STAN } from "./concs/concs-plsc"; @@ -16,9 +16,10 @@ const PROG_CPSC: Program = { }, prog_degs: [ { deg_type: "B.A.", deg_concs: [CONC_CPSC_BA_I] }, - { deg_type: "B.S.", deg_concs: [CONC_CPSC_BS_I] } + ] } +// { deg_type: "B.S.", deg_concs: [CONC_CPSC_BS_I] } // const PROG_ECON: Program = { // prog_data: { diff --git a/frontend/src/utils/preprocessing/Fill.ts b/frontend/src/utils/preprocessing/Fill.ts index 11b83c4..9da99c8 100644 --- a/frontend/src/utils/preprocessing/Fill.ts +++ b/frontend/src/utils/preprocessing/Fill.ts @@ -1,180 +1,150 @@ import { StudentCourse } from "@/types/type-user"; -import { ConcentrationSubrequirement, DegreeConcentration, ProgramDict } from "@/types/type-program"; +import { + DegreeConcentration, + ProgramDict, + ConcentrationRequirement +} from "@/types/type-program"; /** - * First Pass: Fills `s` in options where `o` is non-null for non-checkbox requirements. + * Main function - updates entire `progDict` by filling student courses in each concentration. */ -function fillDirectMatches( - concentration: DegreeConcentration, +export function fill( studentCourses: StudentCourse[], - usedCourses: Set -): DegreeConcentration { - return { - ...concentration, - conc_reqs: concentration.conc_reqs.map(req => { - if (req.checkbox) return req; + progDict: ProgramDict, + setProgDict: Function +): void { + // Create a true deep copy to avoid mutations to the original + const updatedProgDict: ProgramDict = JSON.parse(JSON.stringify(progDict)); - return { - ...req, - subreqs_list: req.subreqs_list.map(subreq => ({ - ...subreq, - subreq_options: subreq.subreq_options.map(option => { - if (!option.o || option.s) return option; // Skip null `o` or already filled `s` - - const matchingStudentCourse = studentCourses.find(sc => - sc.course.codes.includes(option.o!.codes[0]) && !usedCourses.has(sc.course.codes[0]) - ); - - if (matchingStudentCourse) { - usedCourses.add(matchingStudentCourse.course.codes[0]); // Track usage - return { ...option, s: matchingStudentCourse }; - } - - return option; - }), - })), - }; - }), - }; + // Process each program + Object.keys(updatedProgDict).forEach(progKey => { + const program = updatedProgDict[progKey]; + + program.prog_degs = program.prog_degs.map(deg => { + deg.deg_concs = deg.deg_concs.map(conc => { + return processConcentration(conc, studentCourses); + }); + return deg; + }); + }); + + // Update state with the completely new object + setProgDict(updatedProgDict); } /** - * Second Pass: Fills `s` using elective ranges in non-checkbox requirements. - * Iterates through non-flex subreqs first, then flex. + * Process a single concentration by handling all its requirements */ -function fillElectiveRanges( +function processConcentration( concentration: DegreeConcentration, - studentCourses: StudentCourse[], - usedCourses: Set + studentCourses: StudentCourse[] ): DegreeConcentration { - return { - ...concentration, - conc_reqs: concentration.conc_reqs.map(req => { - if (req.checkbox) return req; // Skip checkbox reqs - - // First Pass: Fill non-flex subreqs, skipping flex ones - let updatedSubreqs = req.subreqs_list.map(subreq => { - if (subreq.subreq_flex) return subreq; // Skip flex subreqs in first pass - return fillSubreqElectives(subreq, studentCourses, usedCourses); - }); + const usedCourses = new Set(); + const requiredCourses = new Set(); // Track courses explicitly required by `o` + + // Clone to avoid mutations + const processedConc = JSON.parse(JSON.stringify(concentration)) as DegreeConcentration; + + // ** Pass 1: Direct matches for `o` (Claim required courses) ** + processedConc.conc_reqs = processedConc.conc_reqs.map(req => { + const updatedReq = processDirectMatches(req, studentCourses, usedCourses, requiredCourses); + return updatedReq; + }); - // Second Pass: Fill flex subreqs, keeping previous updates - updatedSubreqs = updatedSubreqs.map(subreq => { - if (!subreq.subreq_flex) return subreq; // Skip non-flex subreqs in second pass - return fillSubreqElectives(subreq, studentCourses, usedCourses); - }); + // ** Pass 2: Non-Flex elective ranges (Only assign courses NOT in requiredCourses) ** + processedConc.conc_reqs = processedConc.conc_reqs.map(req => { + return processElectiveRanges(req, studentCourses, usedCourses, requiredCourses, false); + }); - return { ...req, subreqs_list: updatedSubreqs }; - }), - }; + // ** Pass 3: Flex elective ranges (Only assign courses NOT in requiredCourses) ** + processedConc.conc_reqs = processedConc.conc_reqs.map(req => { + return processElectiveRanges(req, studentCourses, usedCourses, requiredCourses, true); + }); + + return processedConc; } /** - * Helper function to fill elective ranges in a subreq. + * **Pass 1: Fill `s` where `o` is non-null (Claim required courses first).** */ -function fillSubreqElectives( - subreq: ConcentrationSubrequirement, +function processDirectMatches( + req: ConcentrationRequirement, studentCourses: StudentCourse[], - usedCourses: Set -): ConcentrationSubrequirement { + usedCourses: Set, + requiredCourses: Set +): ConcentrationRequirement { return { - ...subreq, - subreq_options: subreq.subreq_options.map(option => { - if (option.o !== null || !option.n?.e || option.s) return option; // Skip already filled slots - - const { dept, min, max } = option.n.e; - const matchingStudentCourse = studentCourses.find(sc => - sc.course.codes.some(code => - code.startsWith(dept) && - parseInt(code.replace(dept, ""), 10) >= min && - parseInt(code.replace(dept, ""), 10) <= max && - !usedCourses.has(sc.course.codes[0]) // Ensure it's not used - ) - ); - - if (matchingStudentCourse) { - usedCourses.add(matchingStudentCourse.course.codes[0]); // Track usage - return { ...option, s: matchingStudentCourse }; - } - - return option; - }), + ...req, + subreqs_list: req.subreqs_list.map(subreq => ({ + ...subreq, + subreq_options: subreq.subreq_options.map(option => { + if (!option.o || option.s) return option; // Skip null `o` or already filled `s` + + const courseCode = option.o.codes[0]; + const matchingStudentCourse = studentCourses.find(sc => + sc.course.codes.includes(courseCode) && !usedCourses.has(courseCode) + ); + + // ✅ Ensure required courses are claimed FIRST before electives + if (matchingStudentCourse) { + usedCourses.add(courseCode); // Track as used + requiredCourses.add(courseCode); // Mark as REQUIRED + return { ...option, s: matchingStudentCourse }; + } + + return option; + }), + })), }; } /** - * Third Pass: Fills `s` in checkbox requirements, allowing reuse but preventing duplicates within the req. + * **Pass 2 & 3: Fill elective ranges (Non-Flex first, then Flex).** + * - **Ensures required courses (`requiredCourses`) are NOT used for electives.** */ -function fillCheckboxReqs( - concentration: DegreeConcentration, - studentCourses: StudentCourse[] -): DegreeConcentration { +function processElectiveRanges( + req: ConcentrationRequirement, + studentCourses: StudentCourse[], + usedCourses: Set, + requiredCourses: Set, // 🔥 Courses reserved by direct matches + flex: boolean +): ConcentrationRequirement { return { - ...concentration, - conc_reqs: concentration.conc_reqs.map(req => { - if (!req.checkbox) return req; // Skip non-checkbox reqs - - const usedWithinCheckboxReq = new Set(); // Reset per checkbox req + ...req, + subreqs_list: req.subreqs_list.map(subreq => { + if (subreq.subreq_flex !== flex) return subreq; // Skip subreqs that don't match the flex condition return { - ...req, - subreqs_list: req.subreqs_list.map(subreq => ({ - ...subreq, - subreq_options: subreq.subreq_options.map(option => { - if (option.s) return option; // If already filled, keep it - - const matchingStudentCourse = studentCourses.find(sc => - (!option.o || sc.course.codes.includes(option.o.codes[0])) && - (!usedWithinCheckboxReq.has(sc.course.codes[0])) // Ensure unique within the req - ); - - if (matchingStudentCourse) { - usedWithinCheckboxReq.add(matchingStudentCourse.course.codes[0]); // Track within checkbox req - return { ...option, s: matchingStudentCourse }; - } - - return option; - }), - })), + ...subreq, + subreq_options: subreq.subreq_options.map(option => { + if (option.o !== null || !option.n?.e || option.s) return option; // Skip non-null `o` or already filled `s` + + const { dept, min, max } = option.n.e; + + // ✅ Filter student courses: + // - Ensure the course is not in `usedCourses` + // - Ensure the course is not already **reserved by a required subreq** + const availableCourses = studentCourses.filter(sc => + !usedCourses.has(sc.course.codes[0]) && + !requiredCourses.has(sc.course.codes[0]) && // 🔥 PROTECT REQUIRED COURSES + sc.course.codes.some(code => { + if (!code.startsWith(dept)) return false; + const courseNum = parseInt(code.replace(dept, ""), 10); + return courseNum >= min && courseNum <= max; + }) + ); + + const matchingStudentCourse = availableCourses.find(sc => true); // ✅ Find first valid course + + if (matchingStudentCourse) { + usedCourses.add(matchingStudentCourse.course.codes[0]); // ✅ Mark as used + return { ...option, s: matchingStudentCourse }; + } + + return option; + }), }; }), }; } - -/** - * Fills student courses for a given concentration by running the three passes. - */ -function fillStudentCourses( - concentration: DegreeConcentration, - studentCourses: StudentCourse[] -): DegreeConcentration { - const usedCourses = new Set(); // Tracks courses used in non-checkbox reqs - - // Apply filling in three passes - let updatedConcentration = fillDirectMatches(concentration, studentCourses, usedCourses); - updatedConcentration = fillElectiveRanges(updatedConcentration, studentCourses, usedCourses); - updatedConcentration = fillCheckboxReqs(updatedConcentration, studentCourses); - - return updatedConcentration; -} - -/** - * Main function - updates entire `progDict` by filling student courses in each concentration. - */ -export function fill( - studentCourses: StudentCourse[], - progDict: ProgramDict, - setProgDict: Function -) { - const updatedProgDict = { ...progDict }; - - Object.keys(updatedProgDict).forEach(progKey => { - updatedProgDict[progKey].prog_degs = updatedProgDict[progKey].prog_degs.map(deg => ({ - ...deg, - deg_concs: deg.deg_concs.map(conc => fillStudentCourses(conc, studentCourses)), - })); - }); - - setProgDict(updatedProgDict); -} From 3c743c49a5571a352728c2f881f4172a96154f39 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Mon, 17 Mar 2025 20:37:23 -0700 Subject: [PATCH 34/38] refined fill --- frontend/src/utils/preprocessing/Fill.ts | 108 +++++++++++++++++++++-- 1 file changed, 99 insertions(+), 9 deletions(-) diff --git a/frontend/src/utils/preprocessing/Fill.ts b/frontend/src/utils/preprocessing/Fill.ts index 9da99c8..1359f1f 100644 --- a/frontend/src/utils/preprocessing/Fill.ts +++ b/frontend/src/utils/preprocessing/Fill.ts @@ -46,22 +46,35 @@ function processConcentration( // Clone to avoid mutations const processedConc = JSON.parse(JSON.stringify(concentration)) as DegreeConcentration; - // ** Pass 1: Direct matches for `o` (Claim required courses) ** - processedConc.conc_reqs = processedConc.conc_reqs.map(req => { - const updatedReq = processDirectMatches(req, studentCourses, usedCourses, requiredCourses); - return updatedReq; + // Split requirements by type + const checkboxReqs = processedConc.conc_reqs.filter(req => req.checkbox); + const nonCheckboxReqs = processedConc.conc_reqs.filter(req => !req.checkbox); + + // Process non-checkbox requirements in three passes + + // ** Pass 1: Direct matches for non-checkbox `o` (Claim required courses) ** + const updatedNonCheckboxReqs = nonCheckboxReqs.map(req => { + return processDirectMatches(req, studentCourses, usedCourses, requiredCourses); }); - // ** Pass 2: Non-Flex elective ranges (Only assign courses NOT in requiredCourses) ** - processedConc.conc_reqs = processedConc.conc_reqs.map(req => { + // ** Pass 2: Non-Flex elective ranges for non-checkbox (Only assign courses NOT in requiredCourses) ** + const updatedNonCheckboxReqs2 = updatedNonCheckboxReqs.map(req => { return processElectiveRanges(req, studentCourses, usedCourses, requiredCourses, false); }); - // ** Pass 3: Flex elective ranges (Only assign courses NOT in requiredCourses) ** - processedConc.conc_reqs = processedConc.conc_reqs.map(req => { + // ** Pass 3: Flex elective ranges for non-checkbox (Only assign courses NOT in requiredCourses) ** + const updatedNonCheckboxReqs3 = updatedNonCheckboxReqs2.map(req => { return processElectiveRanges(req, studentCourses, usedCourses, requiredCourses, true); }); - + + // Process checkbox requirements + const updatedCheckboxReqs = checkboxReqs.map(req => { + return processCheckboxReq(req, studentCourses, usedCourses); + }); + + // Combine the results + processedConc.conc_reqs = [...updatedNonCheckboxReqs3, ...updatedCheckboxReqs]; + return processedConc; } @@ -148,3 +161,80 @@ function processElectiveRanges( }), }; } + +/** + * Process a checkbox requirement - allowing reuse within global pool + * but preventing duplicates within the checkbox requirement + */ +function processCheckboxReq( + req: ConcentrationRequirement, + studentCourses: StudentCourse[], + globalUsedCourses: Set +): ConcentrationRequirement { + const usedWithinCheckboxReq = new Set(); // Track courses used within this checkbox req + + // ** Phase 1: Direct matches for checkbox requirements ** + let updatedReq = { + ...req, + subreqs_list: req.subreqs_list.map(subreq => ({ + ...subreq, + subreq_options: subreq.subreq_options.map(option => { + if (!option.o || option.s) return option; // Skip null `o` or already filled `s` + + const courseCode = option.o.codes[0]; + // For checkbox reqs, we only check if it's used within this same checkbox req + const matchingStudentCourse = studentCourses.find(sc => + sc.course.codes.includes(courseCode) && !usedWithinCheckboxReq.has(courseCode) + ); + + if (matchingStudentCourse) { + usedWithinCheckboxReq.add(courseCode); // Track within checkbox req + // Don't add to globalUsedCourses - intentionally allow reuse outside this req + return { ...option, s: matchingStudentCourse }; + } + + return option; + }), + })), + }; + + // ** Phase 2: Elective ranges and null options for checkbox requirements ** + updatedReq = { + ...updatedReq, + subreqs_list: updatedReq.subreqs_list.map(subreq => ({ + ...subreq, + subreq_options: subreq.subreq_options.map(option => { + if (option.s) return option; // Skip already filled slots + + let matchingStudentCourse: StudentCourse | undefined; + + if (option.n?.e) { + // Handle elective range + const { dept, min, max } = option.n.e; + matchingStudentCourse = studentCourses.find(sc => + !usedWithinCheckboxReq.has(sc.course.codes[0]) && // Only check within this checkbox + sc.course.codes.some(code => { + if (!code.startsWith(dept)) return false; + const courseNum = parseInt(code.replace(dept, ""), 10); + return courseNum >= min && courseNum <= max; + }) + ); + } else if (!option.o) { + // For null option.o, find any course not used within this checkbox req + matchingStudentCourse = studentCourses.find(sc => + !usedWithinCheckboxReq.has(sc.course.codes[0]) // Only check within this checkbox + ); + } + + if (matchingStudentCourse) { + usedWithinCheckboxReq.add(matchingStudentCourse.course.codes[0]); // Track within checkbox + return { ...option, s: matchingStudentCourse }; + } + + return option; + }), + })), + }; + + return updatedReq; +} From bd0e4cbe1fe5ddc331a17ad2c862a67d7c8291a0 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Tue, 18 Mar 2025 00:44:14 -0700 Subject: [PATCH 35/38] supabase api --- frontend/package-lock.json | 124 +++- frontend/package.json | 1 + frontend/src/api/api.ts | 27 - .../app/api/courses/[term]/[code]/route.ts | 30 + frontend/src/app/api/courses/route.ts | 21 + frontend/src/app/api/login/route.ts | 20 +- frontend/src/app/api/programs/route.ts | 8 +- frontend/src/app/courses/CoursesUtils.tsx | 3 +- frontend/src/app/courses/page.tsx | 11 +- .../semester/add-course/AddCourseButton.tsx | 13 +- .../semester/add-course/AddCourseUtils.ts | 50 +- .../years/semester/course/CourseBox.tsx | 2 +- .../src/app/graduation/Graduation.module.css | 14 + frontend/src/app/graduation/page.tsx | 48 +- frontend/src/database/data-catalog.ts | 46 -- frontend/src/database/data-courses.ts | 105 ++-- frontend/src/database/data-user.ts | 12 +- .../src/database/programs/concs/concs-cpsc.ts | 18 +- frontend/src/types/supabase.ts | 3 + frontend/src/types/type-user.ts | 18 +- frontend/src/utils/preprocessing/Fill.ts | 3 + frontend/src/utils/supabase.ts | 24 + package-lock.json | 144 +++++ package.json | 5 + scrapers/coursetableScraper/coursetable.py | 115 ++++ .../coursetableScraper/coursetable_scraper.py | 38 -- .../coursetableScraper/display_courses.py | 58 -- scrapers/coursetableScraper/results.json | 535 ++++++++++++++++++ 28 files changed, 1198 insertions(+), 298 deletions(-) delete mode 100644 frontend/src/api/api.ts create mode 100644 frontend/src/app/api/courses/[term]/[code]/route.ts create mode 100644 frontend/src/app/api/courses/route.ts create mode 100644 frontend/src/app/graduation/Graduation.module.css delete mode 100644 frontend/src/database/data-catalog.ts create mode 100644 frontend/src/types/supabase.ts create mode 100644 frontend/src/utils/supabase.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scrapers/coursetableScraper/coursetable.py delete mode 100644 scrapers/coursetableScraper/coursetable_scraper.py delete mode 100644 scrapers/coursetableScraper/display_courses.py create mode 100644 scrapers/coursetableScraper/results.json diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d189040..1c08021 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "majoraudit", "version": "0.1.0", "dependencies": { + "@supabase/supabase-js": "^2.49.1", "d3": "^7.9.0", "next": "15.1.6", "react": "^19.0.0", @@ -745,6 +746,73 @@ "integrity": "sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A==", "dev": true }, + "node_modules/@supabase/auth-js": { + "version": "2.68.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.68.0.tgz", + "integrity": "sha512-odG7nb7aOmZPUXk6SwL2JchSsn36Ppx11i2yWMIc/meUO2B2HK9YwZHPK06utD9Ql9ke7JKDbwGin/8prHKxxQ==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.4.tgz", + "integrity": "sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.2.tgz", + "integrity": "sha512-MXRbk4wpwhWl9IN6rIY1mR8uZCCG4MZAEji942ve6nMwIqnBgBnZhZlON6zTTs6fgveMnoCILpZv1+K91jN+ow==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.2.tgz", + "integrity": "sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.18.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.49.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.49.1.tgz", + "integrity": "sha512-lKaptKQB5/juEF5+jzmBeZlz69MdHZuxf+0f50NwhL+IE//m4ZnOeWlsKRjjsM0fVayZiQKqLvYdBn0RLkhGiQ==", + "dependencies": { + "@supabase/auth-js": "2.68.0", + "@supabase/functions-js": "2.4.4", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.19.2", + "@supabase/realtime-js": "2.11.2", + "@supabase/storage-js": "2.7.1" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -1039,11 +1107,15 @@ "version": "20.17.16", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.16.tgz", "integrity": "sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw==", - "dev": true, "dependencies": { "undici-types": "~6.19.2" } }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==" + }, "node_modules/@types/react": { "version": "19.0.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz", @@ -1062,6 +1134,14 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.22.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.22.0.tgz", @@ -4974,6 +5054,11 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/ts-api-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", @@ -5123,8 +5208,7 @@ "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "node_modules/uri-js": { "version": "4.4.1", @@ -5135,6 +5219,20 @@ "punycode": "^2.1.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5243,6 +5341,26 @@ "node": ">=0.10.0" } }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7c391cf..1801baf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@supabase/supabase-js": "^2.49.1", "d3": "^7.9.0", "next": "15.1.6", "react": "^19.0.0", diff --git a/frontend/src/api/api.ts b/frontend/src/api/api.ts deleted file mode 100644 index dd6cfab..0000000 --- a/frontend/src/api/api.ts +++ /dev/null @@ -1,27 +0,0 @@ - -import { Ryan } from "@/database/data-user"; -import { PROG_DICT } from "@/database/programs/data-program"; -import { NextRequest, NextResponse } from "next/server"; - -export function login(req: NextRequest) -{ - const user = Ryan; - - const response = NextResponse.json(user, { status: 200 }); - - response.cookies.set("session", "true", { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - maxAge: 60 * 60, - path: "/", - }); - - return response; -} - -export function programs(req: NextRequest) -{ - return NextResponse.json(PROG_DICT, { status: 200 });; -} - diff --git a/frontend/src/app/api/courses/[term]/[code]/route.ts b/frontend/src/app/api/courses/[term]/[code]/route.ts new file mode 100644 index 0000000..14820be --- /dev/null +++ b/frontend/src/app/api/courses/[term]/[code]/route.ts @@ -0,0 +1,30 @@ + +import { NextResponse } from "next/server"; +import { supabaseAdmin } from "@/utils/supabase"; + +// API Route: /api/courses/[season]/[code] +export async function GET( + request: Request, + { params }: { params: { term: number; code: string } } +) { + const { term, code } = params; // Extract URL parameters + + try { + // Query Supabase: match season_code & check if code exists in codes array + const { data, error } = await supabaseAdmin + .from("courses") + .select("*") + .eq("term", term) + .contains("codes", [code]); // Assuming "codes" is stored as an array + + if (error) throw error; + + return NextResponse.json(data); + } catch (error) { + console.error("Error fetching course:", error); + return NextResponse.json( + { error: "Failed to fetch course" }, + { status: 500 } + ); + } +} diff --git a/frontend/src/app/api/courses/route.ts b/frontend/src/app/api/courses/route.ts new file mode 100644 index 0000000..bb45f43 --- /dev/null +++ b/frontend/src/app/api/courses/route.ts @@ -0,0 +1,21 @@ + +import { NextResponse } from 'next/server'; +import { supabaseAdmin } from '@/utils/supabase'; + +export async function GET() { + try { + const { data, error } = await supabaseAdmin + .from('courses') + .select('*'); + + if (error) throw error; + + return NextResponse.json(data); + } catch (error) { + console.error('Error fetching courses:', error); + return NextResponse.json( + { error: 'Failed to fetch courses' }, + { status: 500 } + ); + } +} diff --git a/frontend/src/app/api/login/route.ts b/frontend/src/app/api/login/route.ts index 7f9e03a..7f7f826 100644 --- a/frontend/src/app/api/login/route.ts +++ b/frontend/src/app/api/login/route.ts @@ -1,7 +1,19 @@ -import { NextRequest } from "next/server"; -import { login } from "@/api/api"; +import { NextResponse } from "next/server"; +import { Ryan } from "@/database/data-user"; -export async function GET(req: NextRequest) { - return login(req); +export async function GET() { + const user = Ryan; + + const response = NextResponse.json(user, { status: 200 }); + + response.cookies.set("session", "true", { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 60 * 60, + path: "/", + }); + + return response; } diff --git a/frontend/src/app/api/programs/route.ts b/frontend/src/app/api/programs/route.ts index 5164cde..5b069d9 100644 --- a/frontend/src/app/api/programs/route.ts +++ b/frontend/src/app/api/programs/route.ts @@ -1,7 +1,7 @@ -import { NextRequest } from "next/server"; -import { programs } from "@/api/api"; +import { NextResponse } from "next/server"; +import { PROG_DICT } from "@/database/programs/data-program"; -export async function GET(req: NextRequest) { - return programs(req); +export async function GET() { + return NextResponse.json(PROG_DICT, { status: 200 }); } diff --git a/frontend/src/app/courses/CoursesUtils.tsx b/frontend/src/app/courses/CoursesUtils.tsx index 0a341f4..e7b7514 100644 --- a/frontend/src/app/courses/CoursesUtils.tsx +++ b/frontend/src/app/courses/CoursesUtils.tsx @@ -1,3 +1,4 @@ + import { User, StudentSemester, StudentYear } from "@/types/type-user"; export function BuildStudentYears(user: User): StudentYear[] @@ -12,7 +13,7 @@ export function BuildStudentYears(user: User): StudentYear[] const buildSemesters = (terms: number[]): StudentSemester[] => { return terms.map(term => ({ term, - studentCourses: studentCourses.filter(course => course.term === term), + studentCourses: studentCourses.filter(studentCourses => studentCourses.term === term), })); }; diff --git a/frontend/src/app/courses/page.tsx b/frontend/src/app/courses/page.tsx index 2fe260d..e60de5d 100644 --- a/frontend/src/app/courses/page.tsx +++ b/frontend/src/app/courses/page.tsx @@ -43,8 +43,15 @@ function Courses(){
-
- +
); } diff --git a/frontend/src/app/graduation/Graduation.module.css b/frontend/src/app/graduation/Graduation.module.css new file mode 100644 index 0000000..85a846c --- /dev/null +++ b/frontend/src/app/graduation/Graduation.module.css @@ -0,0 +1,14 @@ + +.GradPage { + position: absolute; + top: 75px; + + display: flex; + flex-direction: row; + justify-content: center; + + width: 100%; + + padding-top: 50px; + padding-bottom: 200px; +} diff --git a/frontend/src/app/graduation/page.tsx b/frontend/src/app/graduation/page.tsx index 15de9bc..82ed6ba 100644 --- a/frontend/src/app/graduation/page.tsx +++ b/frontend/src/app/graduation/page.tsx @@ -1,10 +1,48 @@ -import NavBar from "@/components/navbar/NavBar" +"use client"; +import { useState } from "react"; +import Style from "./Graduation.module.css"; +import NavBar from "@/components/navbar/NavBar"; -export default function Graduation(){ - return( +type Course = { + course_id: number; + codes: string; + title: string; + description: string; + requirements?: string; + professors?: string[]; + distributions?: string; + flags?: string; + credits: number; + season_code?: string; + colsem?: string; + fysem?: string; + sysem?: string; +}; + +export default function Graduation() { + const [courses, setCourses] = useState([]); // Explicitly type courses + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + function fetchCourses() { + setLoading(true); + setError(null); + + fetch("/api/courses") + .then((res) => res.json()) + .then((data: Course[]) => { + setCourses(data); + }) + .catch((err) => setError(err.message)) + .finally(() => setLoading(false)); + } + + return (
- + +
+
- ) + ); } diff --git a/frontend/src/database/data-catalog.ts b/frontend/src/database/data-catalog.ts deleted file mode 100644 index 2ec6f10..0000000 --- a/frontend/src/database/data-catalog.ts +++ /dev/null @@ -1,46 +0,0 @@ - -import { Course } from "@/types/type-user"; -import { CPSC_490, HSAR_401 } from "./data-courses"; - -interface Catalog { - number: number; - courses: Course[]; -} - -export const Catalogs: Catalog[] = [ - { number: 202203, courses: [HSAR_401] }, - { number: 202301, courses: [HSAR_401] }, - { number: 202302, courses: [HSAR_401] }, - { number: 202303, courses: [HSAR_401] }, - { number: 202401, courses: [HSAR_401] }, - { number: 202402, courses: [HSAR_401] }, - { number: 202403, courses: [HSAR_401] }, - { number: 202501, courses: [HSAR_401, CPSC_490] }, - { number: 202502, courses: [HSAR_401] }, - { number: 202503, courses: [HSAR_401] }, - { number: 202601, courses: [HSAR_401] }, - { number: 202602, courses: [HSAR_401] }, - { number: 202603, courses: [HSAR_401] }, - { number: 202701, courses: [HSAR_401] }, - { number: 202702, courses: [HSAR_401] }, - { number: 202703, courses: [HSAR_401] }, - { number: 202801, courses: [HSAR_401] }, - { number: 202802, courses: [HSAR_401] }, - { number: 202803, courses: [HSAR_401] }, - { number: 202901, courses: [HSAR_401] }, - { number: 202902, courses: [HSAR_401] }, - { number: 202903, courses: [HSAR_401] }, -] - -export const getCatalogCourse = (catalogNumber: number, courseCode: string): Course | null => { - - const catalog = Catalogs.find((cat) => cat.number === catalogNumber); - if (!catalog) return null; - - const course = catalog.courses.find((course) => course.codes.includes(courseCode)); - return course || null; -}; - -export const getCatalogTerms = (): number[] => { - return Catalogs.map((catalog) => catalog.number).sort((a, b) => a - b); -} diff --git a/frontend/src/database/data-courses.ts b/frontend/src/database/data-courses.ts index 0714e29..60a119e 100644 --- a/frontend/src/database/data-courses.ts +++ b/frontend/src/database/data-courses.ts @@ -1,58 +1,53 @@ -import { Course, StudentCourse } from "@/types/type-user" - // COURSES -// HSAR PROGRAM -export const HSAR_401: Course = { codes: ["HSAR 401"], title: "Critical Approaches To Art History", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } - -// PLSC PROGRAM -export const PLSC_474: Course = { codes: ["PSLC 474"], title: "", credit: 1, dist: ["So"], seasons: ["Spring"]} -export const PLSC_490: Course = { codes: ["PSLC 490"], title: "", credit: 1, dist: ["So"], seasons: ["Fall", "Spring"]} -export const PLSC_493: Course = { codes: ["PSLC 493"], title: "", credit: 1, dist: ["So"], seasons: ["Fall", "Spring"]} - -// CPSC PROGRAM -export const CPSC_201: Course = { codes: ["CPSC 201"], title: "Introduction To Computer Science", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_202: Course = { codes: ["CPSC 202"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const MATH_244: Course = { codes: ["MATH 244"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_223: Course = { codes: ["CPSC 223"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_323: Course = { codes: ["CPSC 323"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_365: Course = { codes: ["CPSC 365"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_366: Course = { codes: ["CPSC 366"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_381: Course = { codes: ["CPSC 381"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const CPSC_490: Course = { codes: ["CPSC 490"], title: "Senior Project", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } - -// ECON PROGRAM -export const MATH_110: Course = { codes: ["MATH 110"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const MATH_111: Course = { codes: ["MATH 111"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const MATH_112: Course = { codes: ["MATH 112"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const MATH_115: Course = { codes: ["MATH 115"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const MATH_116: Course = { codes: ["MATH 116"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ENAS_151: Course = { codes: ["ENAS 151"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const MATH_118: Course = { codes: ["MATH 118"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const MATH_120: Course = { codes: ["MATH 120"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_108: Course = { codes: ["ECON 108"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_110: Course = { codes: ["ECON 110"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_115: Course = { codes: ["ECON 115"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_111: Course = { codes: ["ECON 111"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_116: Course = { codes: ["ECON 116"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_121: Course = { codes: ["ECON 121"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_125: Course = { codes: ["ECON 125"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_122: Course = { codes: ["ECON 122"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_126: Course = { codes: ["ECON 126"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_117: Course = { codes: ["ECON 117"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_123: Course = { codes: ["ECON 123"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } -export const ECON_136: Course = { codes: ["ECON 136"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } - -// STUDENT COURSES - -// CPSC COURSES -export const SC_CPSC_201: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_201 } -export const SC_CPSC_202: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_202 } -export const SC_CPSC_223: StudentCourse = { term: 202501, status: "DA", result: "GRADE_PASS", course: CPSC_223 } -export const SC_CPSC_323: StudentCourse = { term: 202503, status: "DA", result: "GRADE_PASS", course: CPSC_323 } -export const SC_CPSC_381: StudentCourse = { term: 202503, status: "DA", result: "GRADE_PASS", course: CPSC_381 } -export const SC_CPSC_490: StudentCourse = { term: 202503, status: "DA", result: "GRADE_PASS", course: CPSC_490 } - -// ECON COURSES -export const SC_ECON_110: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: ECON_110 } +// // PLSC PROGRAM +// export const PLSC_474: Course = { codes: ["PSLC 474"], title: "", credit: 1, dist: ["So"], seasons: ["Spring"]} +// export const PLSC_490: Course = { codes: ["PSLC 490"], title: "", credit: 1, dist: ["So"], seasons: ["Fall", "Spring"]} +// export const PLSC_493: Course = { codes: ["PSLC 493"], title: "", credit: 1, dist: ["So"], seasons: ["Fall", "Spring"]} + +// // CPSC PROGRAM +// export const CPSC_201: Course = { codes: ["CPSC 201"], title: "Introduction To Computer Science", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const CPSC_202: Course = { codes: ["CPSC 202"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const MATH_244: Course = { codes: ["MATH 244"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const CPSC_223: Course = { codes: ["CPSC 223"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const CPSC_323: Course = { codes: ["CPSC 323"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const CPSC_365: Course = { codes: ["CPSC 365"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const CPSC_366: Course = { codes: ["CPSC 366"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const CPSC_381: Course = { codes: ["CPSC 381"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const CPSC_490: Course = { codes: ["CPSC 490"], title: "Senior Project", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } + +// // ECON PROGRAM +// export const MATH_110: Course = { codes: ["MATH 110"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const MATH_111: Course = { codes: ["MATH 111"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const MATH_112: Course = { codes: ["MATH 112"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const MATH_115: Course = { codes: ["MATH 115"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const MATH_116: Course = { codes: ["MATH 116"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const ENAS_151: Course = { codes: ["ENAS 151"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const MATH_118: Course = { codes: ["MATH 118"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const MATH_120: Course = { codes: ["MATH 120"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const ECON_108: Course = { codes: ["ECON 108"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const ECON_110: Course = { codes: ["ECON 110"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const ECON_115: Course = { codes: ["ECON 115"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const ECON_111: Course = { codes: ["ECON 111"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const ECON_116: Course = { codes: ["ECON 116"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const ECON_121: Course = { codes: ["ECON 121"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const ECON_125: Course = { codes: ["ECON 125"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const ECON_122: Course = { codes: ["ECON 122"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const ECON_126: Course = { codes: ["ECON 126"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const ECON_117: Course = { codes: ["ECON 117"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const ECON_123: Course = { codes: ["ECON 123"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } +// export const ECON_136: Course = { codes: ["ECON 136"], title: "", credit: 1, dist: ["QR"], seasons: ["Fall", "Spring"] } + +// // STUDENT COURSES + +// // CPSC COURSES +// export const SC_CPSC_201: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_201 } +// export const SC_CPSC_202: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: CPSC_202 } +// export const SC_CPSC_223: StudentCourse = { term: 202501, status: "DA", result: "GRADE_PASS", course: CPSC_223 } +// export const SC_CPSC_323: StudentCourse = { term: 202503, status: "DA", result: "GRADE_PASS", course: CPSC_323 } +// export const SC_CPSC_381: StudentCourse = { term: 202503, status: "DA", result: "GRADE_PASS", course: CPSC_381 } +// export const SC_CPSC_490: StudentCourse = { term: 202503, status: "DA", result: "GRADE_PASS", course: CPSC_490 } + +// // ECON COURSES +// export const SC_ECON_110: StudentCourse = { term: 202403, status: "DA", result: "GRADE_PASS", course: ECON_110 } diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/data-user.ts index 1d06ea8..6db59b5 100644 --- a/frontend/src/database/data-user.ts +++ b/frontend/src/database/data-user.ts @@ -1,14 +1,12 @@ import { User } from "./../types/type-user"; -import { SC_CPSC_201, SC_CPSC_223, SC_CPSC_323, SC_CPSC_381, SC_CPSC_490, SC_ECON_110 } from "./data-courses"; -// import { CONC_HIST_BA_GLOB } from "./programs/concs/concs-hist"; export const Ryan: User = { name: "Ryan", netID: "rgg32", onboard: false, FYP: { - studentCourses: [SC_CPSC_201, SC_CPSC_223, SC_CPSC_323, SC_CPSC_381, SC_CPSC_490], + studentCourses: [], studentTermArrangement: { first_year: [0, 202403, 202501], sophomore: [0, 202503, 202601], @@ -19,11 +17,3 @@ export const Ryan: User = { decl_list: [], } } - -// { -// conc_majors_index: { prog: "HIST", deg: 0, conc: 0 }, -// user_status: 1, -// user_conc: CONC_HIST_BA_GLOB, -// user_conc_name: "", -// selected_subreqs: {}, -// } diff --git a/frontend/src/database/programs/concs/concs-cpsc.ts b/frontend/src/database/programs/concs/concs-cpsc.ts index 8e47b40..199bcbe 100644 --- a/frontend/src/database/programs/concs/concs-cpsc.ts +++ b/frontend/src/database/programs/concs/concs-cpsc.ts @@ -1,8 +1,6 @@ import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; -import { CPSC_201, CPSC_202, MATH_244, CPSC_223, CPSC_323, CPSC_365, CPSC_366, CPSC_381, CPSC_490, SC_CPSC_201, SC_CPSC_223, SC_CPSC_323, SC_CPSC_381 } from "../../data-courses"; - // CORE const CORE_1: ConcentrationSubrequirement = { @@ -12,7 +10,7 @@ const CORE_1: ConcentrationSubrequirement = { subreq_courses_req_count: 1, subreq_options: [ { - o: CPSC_201, + o: null, s: null, } ] @@ -25,11 +23,11 @@ const CORE_2: ConcentrationSubrequirement = { subreq_courses_req_count: 1, subreq_options: [ { - o: CPSC_202, + o: null, s: null, }, { - o: MATH_244, + o: null, s: null, }, ] @@ -42,7 +40,7 @@ const CORE_3: ConcentrationSubrequirement = { subreq_courses_req_count: 1, subreq_options: [ { - o: CPSC_223, + o: null, s: null, } ] @@ -55,7 +53,7 @@ const CORE_4: ConcentrationSubrequirement = { subreq_courses_req_count: 1, subreq_options: [ { - o: CPSC_323, + o: null, s: null, } ] @@ -68,11 +66,11 @@ const CORE_5: ConcentrationSubrequirement = { subreq_courses_req_count: 1, subreq_options: [ { - o: CPSC_365, + o: null, s: null, }, { - o: CPSC_366, + o: null, s: null, }, ] @@ -146,7 +144,7 @@ const SEN_PROJ: ConcentrationSubrequirement = { subreq_courses_req_count: 1, subreq_options: [ { - o: CPSC_490, + o: null, s: null, } ] diff --git a/frontend/src/types/supabase.ts b/frontend/src/types/supabase.ts new file mode 100644 index 0000000..7235096 --- /dev/null +++ b/frontend/src/types/supabase.ts @@ -0,0 +1,3 @@ +Need to install the following packages: +supabase@2.19.7 +Ok to proceed? (y) \ No newline at end of file diff --git a/frontend/src/types/type-user.ts b/frontend/src/types/type-user.ts index cdd89ac..f9db475 100644 --- a/frontend/src/types/type-user.ts +++ b/frontend/src/types/type-user.ts @@ -7,11 +7,19 @@ export interface LanguagePlacement { } export interface Course { - codes: string[]; // ["FREN 403", "HUMS 409"] - title: string; // "Proust Interpretations: Reading Remembrance of Things Past" - credit: number; // 1 - dist: string[]; // ["Hu"] - seasons: string[]; // ["Spring"] + id: string; + codes: string[]; + title: string; + description: string; + prereqs: string; + professors: string[]; + distributions: string[]; + seasons: string[]; + flags: string[]; + credits: number; + colsem: boolean; + fysem: boolean; + sysem: boolean; } export interface StudentCourse { diff --git a/frontend/src/utils/preprocessing/Fill.ts b/frontend/src/utils/preprocessing/Fill.ts index 1359f1f..dd52d91 100644 --- a/frontend/src/utils/preprocessing/Fill.ts +++ b/frontend/src/utils/preprocessing/Fill.ts @@ -14,6 +14,9 @@ export function fill( progDict: ProgramDict, setProgDict: Function ): void { + return; + + // Create a true deep copy to avoid mutations to the original const updatedProgDict: ProgramDict = JSON.parse(JSON.stringify(progDict)); diff --git a/frontend/src/utils/supabase.ts b/frontend/src/utils/supabase.ts new file mode 100644 index 0000000..87d5f28 --- /dev/null +++ b/frontend/src/utils/supabase.ts @@ -0,0 +1,24 @@ + +import { createClient } from '@supabase/supabase-js'; + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; +const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; +const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + +export const supabase = createClient(supabaseUrl, supabaseAnonKey); + +export const supabaseAdmin = createClient(supabaseUrl, supabaseServiceKey, { + auth: { + autoRefreshToken: false, + persistSession: false + } +}); + +export async function getCourses() { + const { data, error } = await supabase + .from('courses') + .select('*'); + + if (error) throw error; + return data; +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0f1c751 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,144 @@ +{ + "name": "MajorAudit", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@supabase/supabase-js": "^2.49.1" + } + }, + "node_modules/@supabase/auth-js": { + "version": "2.68.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.68.0.tgz", + "integrity": "sha512-odG7nb7aOmZPUXk6SwL2JchSsn36Ppx11i2yWMIc/meUO2B2HK9YwZHPK06utD9Ql9ke7JKDbwGin/8prHKxxQ==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.4.tgz", + "integrity": "sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.2.tgz", + "integrity": "sha512-MXRbk4wpwhWl9IN6rIY1mR8uZCCG4MZAEji942ve6nMwIqnBgBnZhZlON6zTTs6fgveMnoCILpZv1+K91jN+ow==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.2.tgz", + "integrity": "sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.18.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.49.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.49.1.tgz", + "integrity": "sha512-lKaptKQB5/juEF5+jzmBeZlz69MdHZuxf+0f50NwhL+IE//m4ZnOeWlsKRjjsM0fVayZiQKqLvYdBn0RLkhGiQ==", + "dependencies": { + "@supabase/auth-js": "2.68.0", + "@supabase/functions-js": "2.4.4", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.19.2", + "@supabase/realtime-js": "2.11.2", + "@supabase/storage-js": "2.7.1" + } + }, + "node_modules/@types/node": { + "version": "22.13.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==" + }, + "node_modules/@types/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9b6d9a4 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@supabase/supabase-js": "^2.49.1" + } +} diff --git a/scrapers/coursetableScraper/coursetable.py b/scrapers/coursetableScraper/coursetable.py new file mode 100644 index 0000000..1d2bb07 --- /dev/null +++ b/scrapers/coursetableScraper/coursetable.py @@ -0,0 +1,115 @@ + +import requests +import json +import os +from supabase import create_client + +def fetch_process_and_upload(season_code="202501"): + # Fetch data from API + url = f"https://api.coursetable.com/api/catalog/public/{season_code}" + print(f"Fetching course data from {url}...") + response = requests.get(url) + response.raise_for_status() + courses_data = response.json() + + # Process courses + print("Processing course data...") + processed_courses = [] + for course in courses_data: + processed_course = transform_course(course) + processed_courses.append(processed_course) + + processed_courses = processed_courses[:25] + + # Save locally (backup) + with open("results.json", "w", encoding="utf-8") as file: + json.dump(processed_courses, file, indent=2) + print(f"Saved {len(processed_courses)} courses to results.json") + + # Upload to Supabase + print("Connecting to Supabase...") + supabase_url = "https://cqonuujfvpucligwwgtq.supabase.co" + supabase_key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNxb251dWpmdnB1Y2xpZ3d3Z3RxIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTczODUyMjg1MywiZXhwIjoyMDU0MDk4ODUzfQ.OtS4JpoFfW-T4YjksMW7SOeBZ1zSaf2EIBbevd09oaI" + + supabase = create_client(supabase_url, supabase_key) + + # Upload in batches + batch_size = 50 + total_batches = (len(processed_courses) + batch_size - 1) // batch_size + + print(f"Uploading {len(processed_courses)} courses in {total_batches} batches...") + + for i in range(0, len(processed_courses), batch_size): + batch = processed_courses[i:i+batch_size] + batch_num = i // batch_size + 1 + + print(f"Uploading batch {batch_num}/{total_batches}...") + + try: + result = supabase.table("courses").insert(batch).execute() + print(f"✓ Batch {batch_num} uploaded successfully") + except Exception as e: + print(f"✗ Error with batch {batch_num}: {e}") + + print("Process complete!") + +def transform_course(course): + # Extract course flags + course_flags = [] + if "course_flags" in course and course["course_flags"]: + course_flags = [flag["flag"]["flag_text"] for flag in course["course_flags"]] + + # Extract professors + professors = [] + if "course_professors" in course and course["course_professors"]: + professors = [prof["professor"]["name"] for prof in course["course_professors"]] + + # Extract course codes + course_codes = [] + if "listings" in course and course["listings"]: + course_codes = [listing["course_code"] for listing in course["listings"]] + + # Combine areas and skills into distributions + distributions = [] + if "areas" in course and course["areas"]: + distributions.extend(course["areas"]) + if "skills" in course and course["skills"]: + distributions.extend(course["skills"]) + + # Handle credits - ensure we're preserving the decimal value if present + credits = course.get("credits") + # If credits is a string, convert it to a float (preserving decimal places) + if isinstance(credits, str): + try: + credits = float(credits) + except (ValueError, TypeError): + credits = None + + # Create transformed course object + return { + "course_id": course.get("course_id"), + "title": course.get("title"), + "description": course.get("description"), + "professors": professors, + "codes": course_codes, + "flags": course_flags, + "distributions": distributions, + "credits": credits, # This will now preserve decimal values + "requirements": course.get("requirements"), + "season_code": course.get("season_code"), + "colsem": course.get("colsem", False), + "fysem": course.get("fysem", False), + "sysem": course.get("sysem", False) + } + +if __name__ == "__main__": + try: + print("Course Data to Supabase Uploader") + print("--------------------------------") + season = input("Enter season code (default 202501 for Spring 2025): ") or "202501" + fetch_process_and_upload(season) + except KeyboardInterrupt: + print("\nProcess cancelled by user") + except Exception as e: + print(f"An error occurred: {e}") + \ No newline at end of file diff --git a/scrapers/coursetableScraper/coursetable_scraper.py b/scrapers/coursetableScraper/coursetable_scraper.py deleted file mode 100644 index 2e8c327..0000000 --- a/scrapers/coursetableScraper/coursetable_scraper.py +++ /dev/null @@ -1,38 +0,0 @@ -import requests -import json -import datetime -import time - -def scrape_courses(): - #TODO change these to env variables - cookies = { - 'session': 'enter session', - 'session.sig': 'etner session.sig', - } - - # response = requests.get('https://api.coursetable.com/api/static/catalogs/202301.json', cookies=cookies) - - course_dic = {} - for year in range(datetime.datetime.now().year-6, datetime.datetime.now().year + 6 + 1): - for season in range(1, 4): - if year not in course_dic: - course_dic[year]={} - - data_url = f'https://api.coursetable.com/api/static/catalogs/{year}0{season}.json' - response = requests.get(data_url, cookies=cookies) - - if response.status_code == 404: - print(f'unable to access {year} {season}') - continue - else: - print(f'scraping {year} {season}') - - course_dic[year][season] = json.loads(response.text) - time.sleep(1) - - with open('courses.json', 'w') as infile: - json.dump(course_dic, infile) - - -if __name__=='__main__': - scrape_courses() diff --git a/scrapers/coursetableScraper/display_courses.py b/scrapers/coursetableScraper/display_courses.py deleted file mode 100644 index ab6d8a3..0000000 --- a/scrapers/coursetableScraper/display_courses.py +++ /dev/null @@ -1,58 +0,0 @@ -import json -import argparse - - -def display_courses(): - with open('courses.json', 'r') as infile: - courses=json.load(infile) - new_courses={} - for year in courses: - if year not in new_courses: - new_courses[year] = {} - - for season in courses[year]: - if season not in new_courses[year]: - new_courses[year][season] = {} - - for course in courses[year][season]: - for code in course["all_course_codes"]: - dep=code.split(' ')[0] - num = str(code.split(' ')[1]) - if dep not in new_courses[year][season]: - new_courses[year][season][dep]={} - - new_courses[year][season][dep][num]=course - - courses=new_courses - - while True: - request=input('enter course:\n') - request=request.split(' ') - if len(request)==2: - year='2023' - season='3' - dep = str(request[0]).upper() - c_num = str(request[1]) - else: - year=str(request[0]) - season=str(request[1]) - dep=str(request[2]).upper() - c_num=str(request[3]) - - try: - course=courses[year][season][dep][c_num] - print(f'{course["title"]}:') - print(f'{course["description"]}\n') - print(f'rating: {course["average_rating"]}') - print(f'difficulty: {course["average_workload"]}\n') - - except: - print('invalid course') - - -if __name__=='__main__': - display_courses() - - - - diff --git a/scrapers/coursetableScraper/results.json b/scrapers/coursetableScraper/results.json new file mode 100644 index 0000000..96a277a --- /dev/null +++ b/scrapers/coursetableScraper/results.json @@ -0,0 +1,535 @@ +[ + { + "course_id": 250121861, + "title": "Social and Cultural Factors in Mental Health and Illness", + "description": "This course provides an introduction to mental health and illness with a focus on the complex interplay between risk and protective factors and social and cultural influences on mental health status. We examine the role of social and cultural factors in the etiology, course, and treatment of substance misuse; depressive, anxiety, and psychotic disorders; and some of the severe behavioral disorders of childhood. The social consequences of mental illness such as stigma, isolation, and barriers to care are explored, and their impact on access to care and recovery considered. The effectiveness of the current system of services and the role of public health and public health professionals in mental health promotion are discussed.", + "professors": [], + "codes": [ + "PSYC 576" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250125120, + "title": "Research", + "description": "Individual research for Ph.D. degree candidates in the Department of Chemistry, under the direct supervision of one or more faculty members.", + "professors": [ + "Tianyu Zhu" + ], + "codes": [ + "CHEM 990" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250121135, + "title": "Junior Seminar", + "description": "Ongoing visual projects addressed in relation to historical and contemporary issues. Readings, slide presentations, critiques by School of Art faculty, and gallery and museum visits. Critiques address all four areas of study in the Art major.", + "professors": [ + "Elle Perez" + ], + "codes": [ + "ART 395" + ], + "flags": [], + "distributions": [ + "Hu" + ], + "credits": 1, + "requirements": "Prerequisite: at least four courses in Art.", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250121016, + "title": "Topics: Events, Distributivity, Durational Modifiers", + "description": "This course bridges introductory courses (LING 263, LING 264) and advanced seminars in semantics. It explores selected topics in some detail, allowing students to appreciate the nuances of semantic argumentation while at the same time emphasizing the foundational issues involved.\u00a0The goal of this course is to allow students, within a structured format, to become comfortable engaging with open-ended problems and to gain confidence in proposing original solutions to such problems.\u00a0Topics vary across semesters.", + "professors": [ + "Veneeta Dayal", + "Simon Charlow" + ], + "codes": [ + "LING 291", + "LING 691" + ], + "flags": [ + "YC LING Depth Semntcs/Pragmat", + "YC LING Elective", + "YC LING Intermediate Courses" + ], + "distributions": [ + "So" + ], + "credits": 1, + "requirements": "Prerequisite: LING 263 / LING 663 or permission of Instructor", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250123229, + "title": "Yale Concert Band", + "description": "The Yale Concert Band, a group of 45-60 wind, brass, and percussion players, embraces the aesthetics of the traditional wind band and the contemporary experimental ensemble. Our repertoire consists of a panoply of wind band classics; premieres by and commissions of Yale students, faculty and established world-class composers; and the newest wind band literature that incorporates electro-acoustic sounds, folk/rock/hip hop music, soloists, and theatrical trappings. The Yale Concert Band regularly presents concerts to benefit causes and organizations, ranging from benefit concerts to support the work of New Haven\u2019s IRIS (Integrated Refugee and Immigrant Services (2017, 2018, 2019); to provide aid to the relief efforts after Hurricane Katrina (2005), floods in Myanmar (2007), tornadoes in the American midwest (2007), the earthquake in Haiti (2010), the tsunami in Japan (2011), and West African Ebola recovery efforts (2016).\u00a0 In 1959, the Yale Concert Band became the first university band to produce an international concert tour, and, since then, has appeared in concerts in Japan, South Africa, Swaziland, Mexico, Brazil, Bermuda, Russia, Finland, the Czech Republic, Austria, Ireland, England, France, Italy, Denmark, Germany, Holland, Belgium, Lithuania, Latvia, Estonia, Ghana, Haiti, Greece, Australia, and Spain. This course cannot be applied toward the 36-course-credit requirement for the Yale bachelor's degree.", + "professors": [ + "Thomas Duffy" + ], + "codes": [ + "MUSI 190" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "By audition at the beginning of the academic year or by permission of instructor.", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250124916, + "title": "Exploring and Understanding White Collar Crime", + "description": "This course examines the many aspects of white collar crime; perjury, obstruction of justice, corporate crimes, Ponzi Schemes, insider trading, money laundering bribery and political corruption. The course explores how white collar crime, once virtually ignored by law enforcement has become a major focus of federal and state investigative agencies with massive resources allocated toward combatting it. The seminar examines the root causes of white collar crime as well as its pervasiveness in every day life.\u00a0 Specific cases of white collar defendants, both individuals and corporations that have profoundly impacted business, law, science, healthcare and other disciplines are examined.", + "professors": [ + "Bradley Simon" + ], + "codes": [ + "CSMY 220" + ], + "flags": [ + "YC College Seminar" + ], + "distributions": [], + "credits": 1, + "requirements": "Students may enroll in no more than 1 RCS for credit in a given term.", + "season_code": "202501", + "colsem": true, + "fysem": false, + "sysem": false + }, + { + "course_id": 250126581, + "title": "EL IntMedHematology2WK", + "description": "", + "professors": [], + "codes": [ + "MD 3090" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250126701, + "title": "Global Health Elective Ghana", + "description": "", + "professors": [], + "codes": [ + "MD 301" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250120660, + "title": "Methods in Gender and Sexuality Studies", + "description": "This seminar explores\u00a0the dynamics of power and knowledge, the ethics of representation and accountability, and the nexus between disciplinarity and interdisciplinarity. It is designed for graduate students developing research projects that engage feminist, queer, postcolonial, and critical race methodologies, among others. The course adopts an epistemological approach that centers \"encounter\" across geopolitical scales and multiple disciplinary fronts in the humanities and social sciences. It posits that research methods, regardless of their origin, can adopt feminist, queer, decolonial/postcolonial, and critical race perspectives and potentially serve counter-disciplinary purposes. Although we cover a broad spectrum of methods\u2014ranging from ethnographic, historiographic/archival, and geographic, to literary, media, and textual analysis, cultural studies, and political theory\u2014our work does not unfold as a practicum. Instead of experimenting with a predefined \"toolkit,\" students critically engage book-length works that demonstrate counter-disciplinary methodologies, reflecting hermeneutically on how method and theory relate in these texts by drawing on Foucault's framework of \"the archaeology of knowledge.\"", + "professors": [ + "Eda Pepi" + ], + "codes": [ + "AMST 798", + "WGSS 800" + ], + "flags": [ + "YC Ethnography Methods" + ], + "distributions": [], + "credits": 1, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250121028, + "title": "Special Investigations", + "description": "Directed research by arrangement with individual faculty members and approved by the DGS. Students are expected to propose and complete a term-long research project. The culmination of the project is a presentation that fulfills the departmental requirement for the research qualifying event.", + "professors": [ + "Daisuke Nagai", + "Rona Ramos" + ], + "codes": [ + "PHYS 990" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250121423, + "title": "Topics in Biomedical Informatics and Data Science", + "description": "The course focuses on providing an introduction to common unifying themes that serve as the foundation for different areas of biomedical informatics, including clinical, neuro-, and genome informatics. The course is designed for students with basic computer experience and course work who plan to build databases and computational tools for use in biomedical research. Emphasis is on understanding basic principles underlying informatics approaches to interoperation among biomedical databases and software tools, standardized biomedical vocabularies and ontologies, biomedical natural language processing, modeling of biological systems, high-performance computation in biomedicine, and other related topics.", + "professors": [ + "Samah Jarad" + ], + "codes": [ + "BIS 550", + "CB&B 750", + "HSCI 5500" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250121441, + "title": "Topics in Biomedical Informatics and Data Science", + "description": "The course focuses on providing an introduction to common unifying themes that serve as the foundation for different areas of biomedical informatics, including clinical, neuro-, and genome informatics. The course is designed for students with significant computer experience and course work who plan to build databases and computational tools for use in biomedical research. Emphasis is on understanding basic principles underlying informatics approaches to interoperation among biomedical databases and software tools, standardized biomedical vocabularies and ontologies, biomedical natural language processing, modeling of biological systems, high-performance computation in biomedicine, and other related topics.", + "professors": [ + "Samah Jarad", + "Kei-Hoi Cheung" + ], + "codes": [ + "BIS 543E" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "Open only to students enrolled in the Executive Online M.P.H. Program. Not open to auditors.", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250122238, + "title": "Corporate Sustainability: Strategy and Management", + "description": "This survey course focuses on the policy and business logic for making environmental issues and sustainability a core focus of corporate strategy and management. Students are asked to analyze when and how sustainability leadership can translate into competitive advantage by helping to cut costs, reduce risk, drive growth, and promote brand identity and intangible value. The course seeks to provide students with an introduction to the range of sustainability issues and challenges that companies face in today\u2019s fast-changing marketplace. It introduces key corporate sustainability terms, concepts, tools, strategies, and frameworks based on the overarching theory that the traditional profit-maximizing mission of business (often called shareholder primacy) is giving way to a new vision of stakeholder responsibility that still seeks to provide good returns to the enterprise\u2019s owners but also acknowledges obligations to employees, suppliers, customers, communities, and society more broadly. The course combines lectures, case studies, and class discussions on management theory and tools, the legal and regulatory frameworks that shape the business-environment interface, and the evolving role of business in society. It explores how to deal with a world of diverse stakeholders, increasing transparency, and rising expectations related to corporate environmental, social, and governance (ESG) performance. Self-scheduled examination.", + "professors": [ + "Daniel Esty" + ], + "codes": [ + "ENV 807", + "MGT 688" + ], + "flags": [ + "YC ENRG Energy & Environment", + "YSE MEM B&E Core", + "YSE MEM IEGC Add'l Electives", + "YSE MEM IEGC Primary Electives" + ], + "distributions": [], + "credits": 0, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250122406, + "title": "What was Latinx Literature", + "description": "With the arrival of \"Latinx,\" the last decade was defined as a moment of rupture and break with traditional notions of latinidad. Artists and activists asserted refusal and historical reckoning as the mode of doing politics and aesthetics. Now, pessimistic about Latinx as a signifier of a unified political project, the generational tides have shifted to \"Latine.\" This seminar asks what is \"Latinx literature\" and why are the methods of \"Latinx studies\" considered revolutionary or disruptive? What ideas were rooted in prior generations of feminist and queer collectives that sustained life when the arrival of a decolonial future seemed forever deferred and withheld from reach? We examine contemporary artists alongside historical antecedents to reevaluate what literary and social forms can help us challenge a racialized, heteronormative conception of citizenship. One possibility is that Gloria Anzald\u00faa\u2014rightly critiqued for her relation to mestizaje \u2014might be helpful in this moment of growing nationalism and hostility towards migrants to think about other ways of organizing life aside borders and the nation. We read across a long and varied arc of creative expression to consider forms that endure amidst colonial duress. For example: the serial, montage, anthology, performance collective, and inter-linked storytelling. Artists up for discussion may include Natalie Diaz, John Rechy, and Jes\u00fas Col\u00f3n. Students will engage these works alongside theorists like Jos\u00e9 Esteban Mu\u00f1oz and Juana Mar\u00eda Rodr\u00edguez. Previously ENGL 331.", + "professors": [ + "Joseph Miranda" + ], + "codes": [ + "ENGL 4831", + "ER&M 268" + ], + "flags": [ + "YC ENGL 20th/21st Century", + "YC ENGL Senior Seminar" + ], + "distributions": [ + "Hu", + "WR" + ], + "credits": 1, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250122770, + "title": "Machine Learning for Economic Analysis", + "description": "Machine learning algorithms and their applications to economic analysis, specifically causal inference, learning, and game theory. Curse of dimensionality, model selection, and choice of tuning parameters from a computational and econometric perspective.", + "professors": [ + "Max Cytrynbaum" + ], + "codes": [ + "ECON 566" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250122954, + "title": "Machine Learning for Economic Analysis", + "description": "Machine learning algorithms and their applications to economic analysis, specifically causal inference, learning, and game theory. Curse of dimensionality, model selection, and choice of tuning parameters from a computational and econometric perspective.", + "professors": [ + "Max Cytrynbaum" + ], + "codes": [ + "ECON 428" + ], + "flags": [], + "distributions": [ + "So" + ], + "credits": 1, + "requirements": "Prerequisites: CPSC 100 or CPSC 112; and ECON 117 or ECON 136.", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250123384, + "title": "Secndry Instrmnt--: Secndry Instrmnt--PIANO", + "description": "2 credits per term. P/F. All students enrolled in secondary lessons can receive instruction in either voice or piano. In addition, YSM keyboard majors may take secondary organ or harpsichord, and YSM violinists may take secondary viola. Any other students who wish to take secondary lessons in any other instruments must petition the director of secondary lessons, Kyung Yu, by email (kyung.yu@yale.edu) no later than Aug. 30, 2021, for the fall term and Jan. 14, 2022, for the spring term. Students who are not conducting majors may take only one secondary instrument per term. YSM students who wish to take secondary lessons must register for the course and request a teacher using the online form for graduate students found at http://music.yale.edu/study/music-lessons; the availability of a secondary lessons teacher is not guaranteed until the form is received and a teacher assigned by the director of lessons. Secondary instruction in choral conducting and orchestral conducting is only available with permission of the instructor and requires as prerequisites MUS 565 for secondary instruction in choral conducting, and both MUS 529 and MUS 530 for secondary instruction in orchestral conducting. Students of the Yale Divinity School, School of Drama, and School of Art may also register as above for secondary lessons and will be charged $200 per term for these lessons. Questions may be emailed to the director, Kyung Yu (kyung.yu@yale.edu).", + "professors": [ + "Kyung Yu" + ], + "codes": [ + "MUS 541" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250123877, + "title": "The Senior Essay I", + "description": "Students wishing to undertake an independent senior essay in English must submit a proposal to the DUS in the previous term; deadlines and instructions are posted at https://english.yale.edu/undergraduate/courses/independent-study-courses. For one-term senior essays, the essay itself is due in the office of the director of undergraduate studies according to the following schedule: (1) end of the fourth week of classes: five to ten pages of writing and/or an annotated bibliography; (2) end of the ninth week of classes: a rough draft of the complete essay; (3) end of the last week of classes (fall term) or end of the next-to-last week of classes (spring term): the completed essay. Consult the director of undergraduate studies regarding the schedule for submission of the yearlong senior essay.", + "professors": [ + "Marcel Elias", + "Stefanie Markovits" + ], + "codes": [ + "ENGL 4100" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250124362, + "title": "Styles of Professional Prose: Writing about Legal Affairs", + "description": "A seminar and workshop in the conventions of good writing in a specific field. Each section focuses on one professional kind of writing and explores its distinctive features through a variety of written and oral assignments, in which students both analyze and practice writing in the field. Section topics, which change yearly, are listed at the beginning of each term on the English department website. This course may be repeated for credit in a section that treats a different genre or style of writing; may not be repeated for credit toward the major. ENGL 121 and ENGL 421 may not be taken for credit on the same topic.", + "professors": [ + "Lincoln Caplan" + ], + "codes": [ + "ENGL 1021" + ], + "flags": [], + "distributions": [ + "WR" + ], + "credits": 1, + "requirements": "Prerequisite: ENGL 114, 115, 120, or another writing-intensive course at Yale.", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250124828, + "title": "Abolition in the Americas", + "description": "This seminar examines histories of slavery's abolition in the Americas. It situates the end of slavery in the United States within hemispheric and transatlantic contexts, touching upon processes of abolition\u00a0in Antigua, Brazil, Colombia, Cuba, the Dominican Republic, Haiti, Jamaica, and more. The course approaches abolition as a historiographical problem, considering debates about its definition, its causes and consequences, its primary agents, its periodization, and its relation to other historical processes. Questions include: How have historians defined abolition? How have they periodized it? How have scholars variously characterized the forms it took and who was responsible for it? How have they differently understood the social, cultural, economic, legal, and political conditions that gave rise to abolition? How have historians agreed and disagreed upon its effects and its aftermath? How have they framed the relation between freedom and formal emancipation? How have the communicated the stakes of their accounts of abolition? The organization of the course is topical and loosely chronological. Readings address the origins of abolition in the Atlantic world, the Haitian Revolution, processes of gradual emancipation, the historical significance of Black abolitionists, the activism of women and children, the formation and contributions of antislavery movements, the practice of moral suasion, the question of violence and antislavery militantism, antislavery discourses of rights and sexual morals, the circulation of racial and climate science in abolitionist circles, enslaved people\u2019s practices of fugitivity, self-purchase, and revolt, the relation between capitalism and abolitionism, the Civil War, and the so-called \"last abolition\" of slavery in Brazil.", + "professors": [ + "Caleb Knapp" + ], + "codes": [ + "HIST 124J" + ], + "flags": [ + "YC HIST Cultural History", + "YC HIST Departmental Seminars", + "YC HIST Empires & Colonialism", + "YC HIST Ideas & Intellectuals", + "YC HIST Pltcs, Law & Govt", + "YC HIST Race Gender&Sexuality", + "YC HIST Soc Chng&Social Mvmnt", + "YC HIST United States", + "YC HIST War & Society", + "YC HSHM Colonial Know & Power", + "YC HSHM Gender, Reprod & Body" + ], + "distributions": [ + "Hu", + "WR" + ], + "credits": 1, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250125902, + "title": "Clinical Practice II for Global Health Track", + "description": "This clinical application course for students in the global health track provides opportunities to develop advanced nursing skills with a range of global populations within the students\u2019 areas of specialization. While in clinical settings, students develop skills in assessment and management of acute and chronic conditions using evidence-based patient management strategies in accordance with the cultural beliefs and practices of populations of immigrants, refugees, American Indians, and Alaskan native and rural residents. These experiences may take place in YSN-approved U.S. or international settings. Additional experiences with local resettlement organizations such as Integrated Refugee and Immigrant Services (IRIS) and Connecticut Institute for Refugees and Immigrants (CIRI) are also available. These experiences may include developing and presenting education programs to groups of refugees, immigrants, or asylum seekers; creating training materials for the resettlement agencies; or serving as a cultural companion or health navigator for newly arrived families. Required of all students pursuing the global health track during the fall term of their second specialty year. Thirty hours of face-to-face interactions either in a health care setting or in an alternative setting, and one hour per week of clinical conference. Taken after NURS 6230.", + "professors": [ + "Sandy Cayo" + ], + "codes": [ + "NURS 6240" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250126137, + "title": "Independent Course Work", + "description": "Program to be determined with a faculty adviser of the student\u2019s choice and submitted, with the endorsement of the study area coordinators, to the Rules Committee for confirmation of the student\u2019s eligibility under the rules. (See the School\u2019s Academic Rules and Regulations.)", + "professors": [ + "Brennan Buck" + ], + "codes": [ + "ARCH 2299" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250120297, + "title": "American Political Institutions", + "description": "The origins and development of American political institutions, especially in relation to constitutional choice and the agency of persons seeking freedom, equality, and self-governing capabilities as a driver of constitutional change. Key concepts include: American federalism, compound republic, citizenship, social movements, racial justice, and nonviolence.", + "professors": [ + "Michael Fotos" + ], + "codes": [ + "PLSC 256", + "AFAM 177", + "EP&E 248" + ], + "flags": [ + "YC EP&E Politics Core" + ], + "distributions": [ + "So", + "WR" + ], + "credits": 1, + "requirements": "", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250120530, + "title": "Elementary Modern Standard Arabic II", + "description": "Continuation of ARBC 110.", + "professors": [ + "Muhammad Aziz" + ], + "codes": [ + "ARBC 120", + "ARBC 501" + ], + "flags": [], + "distributions": [ + "L2" + ], + "credits": 1.5, + "requirements": "Prerequisite: ARBC 110 or\u00a0requisite score on a\u00a0placement test.", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250121598, + "title": "Chinese for Current Affairs", + "description": "Advanced language course with a focus on speaking and writing in formal styles. Current affairs are used as a vehicle to help students learn advanced vocabulary, idiomatic expressions, complex sentence structures, news writing styles and formal stylistic register. Materials include texts and videos selected from news media worldwide to improve students\u2019 language proficiency for sophisticated communications on a wide range of topics.", + "professors": [ + "Jingjing Ao" + ], + "codes": [ + "CHNS 167" + ], + "flags": [], + "distributions": [ + "L5" + ], + "credits": 1, + "requirements": "After CHNS 153, or 157, or 159,\u00a0 or equivalent.", + "season_code": "202501", + "colsem": false, + "fysem": false, + "sysem": false + } +] \ No newline at end of file From bea35c8fffa980733bd7b9345f6e6c57d7d205a4 Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Thu, 20 Mar 2025 12:30:38 -0700 Subject: [PATCH 36/38] haha wow woah --- frontend/package-lock.json | 578 +++++++++++++++++- frontend/package.json | 2 + frontend/prisma/schema.prisma | 140 +++++ frontend/src/app/api/login/route.ts | 2 +- frontend/src/app/api/programs/db-service.ts | 99 +++ frontend/src/app/api/programs/route.ts | 26 +- frontend/src/app/api/programs/transformers.ts | 116 ++++ frontend/src/app/graduation/page.tsx | 74 ++- frontend/src/app/majors/page.tsx | 4 +- frontend/src/context/AuthProvider.tsx | 32 +- frontend/src/context/ProgramProvider.tsx | 64 +- frontend/src/database/client.ts | 11 + .../src/database/{ => mock}/data-courses.ts | 0 frontend/src/database/{ => mock}/data-user.ts | 0 .../mock/programs/concs/concs-cpsc.ts | 174 ++++++ .../mock/programs/concs/concs-econ.ts | 203 ++++++ .../{ => mock}/programs/concs/concs-hist.ts | 0 .../{ => mock}/programs/concs/concs-plsc.ts | 0 .../database/mock/programs/data-program.ts | 74 +++ .../src/database/programs/concs/concs-cpsc.ts | 174 ------ .../src/database/programs/concs/concs-econ.ts | 203 ------ .../src/database/programs/data-program.ts | 74 --- frontend/src/types/supabase.ts | 3 - frontend/src/types/type-program.ts | 105 ++-- frontend/src/types/type-user.ts | 35 +- scrapers/coursetableScraper/coursetable.py | 88 +-- .../coursetableScraper/results_202403.json | 544 +++++++++++++++++ .../coursetableScraper/results_202501.json | 535 ++++++++++++++++ 28 files changed, 2690 insertions(+), 670 deletions(-) create mode 100644 frontend/prisma/schema.prisma create mode 100644 frontend/src/app/api/programs/db-service.ts create mode 100644 frontend/src/app/api/programs/transformers.ts create mode 100644 frontend/src/database/client.ts rename frontend/src/database/{ => mock}/data-courses.ts (100%) rename frontend/src/database/{ => mock}/data-user.ts (100%) create mode 100644 frontend/src/database/mock/programs/concs/concs-cpsc.ts create mode 100644 frontend/src/database/mock/programs/concs/concs-econ.ts rename frontend/src/database/{ => mock}/programs/concs/concs-hist.ts (100%) rename frontend/src/database/{ => mock}/programs/concs/concs-plsc.ts (100%) create mode 100644 frontend/src/database/mock/programs/data-program.ts delete mode 100644 frontend/src/database/programs/concs/concs-cpsc.ts delete mode 100644 frontend/src/database/programs/concs/concs-econ.ts delete mode 100644 frontend/src/database/programs/data-program.ts delete mode 100644 frontend/src/types/supabase.ts create mode 100644 scrapers/coursetableScraper/results_202403.json create mode 100644 scrapers/coursetableScraper/results_202501.json diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1c08021..b7d9e31 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "majoraudit", "version": "0.1.0", "dependencies": { + "@prisma/client": "^6.5.0", "@supabase/supabase-js": "^2.49.1", "d3": "^7.9.0", "next": "15.1.6", @@ -22,6 +23,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.1.6", + "prisma": "^6.5.0", "typescript": "^5" } }, @@ -34,6 +36,406 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -734,6 +1136,82 @@ "node": ">=12.4.0" } }, + "node_modules/@prisma/client": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.5.0.tgz", + "integrity": "sha512-M6w1Ql/BeiGoZmhMdAZUXHu5sz5HubyVcKukbLs3l0ELcQb8hTUJxtGEChhv4SVJ0QJlwtLnwOLgIRQhpsm9dw==", + "hasInstallScript": true, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.5.0.tgz", + "integrity": "sha512-sOH/2Go9Zer67DNFLZk6pYOHj+rumSb0VILgltkoxOjYnlLqUpHPAN826vnx8HigqnOCxj9LRhT6U7uLiIIWgw==", + "devOptional": true, + "dependencies": { + "esbuild": ">=0.12 <1", + "esbuild-register": "3.6.0" + } + }, + "node_modules/@prisma/debug": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.5.0.tgz", + "integrity": "sha512-fc/nusYBlJMzDmDepdUtH9aBsJrda2JNErP9AzuHbgUEQY0/9zQYZdNlXmKoIWENtio+qarPNe/+DQtrX5kMcQ==", + "devOptional": true + }, + "node_modules/@prisma/engines": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.5.0.tgz", + "integrity": "sha512-FVPQYHgOllJklN9DUyujXvh3hFJCY0NX86sDmBErLvoZjy2OXGiZ5FNf3J/C4/RZZmCypZBYpBKEhx7b7rEsdw==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/debug": "6.5.0", + "@prisma/engines-version": "6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60", + "@prisma/fetch-engine": "6.5.0", + "@prisma/get-platform": "6.5.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60.tgz", + "integrity": "sha512-iK3EmiVGFDCmXjSpdsKGNqy9hOdLnvYBrJB61far/oP03hlIxrb04OWmDjNTwtmZ3UZdA5MCvI+f+3k2jPTflQ==", + "devOptional": true + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.5.0.tgz", + "integrity": "sha512-3LhYA+FXP6pqY8FLHCjewyE8pGXXJ7BxZw2rhPq+CZAhvflVzq4K8Qly3OrmOkn6wGlz79nyLQdknyCG2HBTuA==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "6.5.0", + "@prisma/engines-version": "6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60", + "@prisma/get-platform": "6.5.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.5.0.tgz", + "integrity": "sha512-xYcvyJwNMg2eDptBYFqFLUCfgi+wZLcj6HDMsj0Qw0irvauG4IKmkbywnqwok0B+k+W+p+jThM2DKTSmoPCkzw==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "6.5.0" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -2271,7 +2749,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, + "devOptional": true, "dependencies": { "ms": "^2.1.3" }, @@ -2549,6 +3027,58 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "devOptional": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" + } + }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "devOptional": true, + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3122,6 +3652,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4034,7 +4578,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "devOptional": true }, "node_modules/nanoid": { "version": "3.3.8", @@ -4388,6 +4932,34 @@ "node": ">= 0.8.0" } }, + "node_modules/prisma": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.5.0.tgz", + "integrity": "sha512-yUGXmWqv5F4PByMSNbYFxke/WbnyTLjnJ5bKr8fLkcnY7U5rU9rUTh/+Fja+gOrRxEgtCbCtca94IeITj4j/pg==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/config": "6.5.0", + "@prisma/engines": "6.5.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5178,7 +5750,7 @@ "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/frontend/package.json b/frontend/package.json index 1801baf..1887d5c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@prisma/client": "^6.5.0", "@supabase/supabase-js": "^2.49.1", "d3": "^7.9.0", "next": "15.1.6", @@ -23,6 +24,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.1.6", + "prisma": "^6.5.0", "typescript": "^5" } } diff --git a/frontend/prisma/schema.prisma b/frontend/prisma/schema.prisma new file mode 100644 index 0000000..2647720 --- /dev/null +++ b/frontend/prisma/schema.prisma @@ -0,0 +1,140 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Program { + id Int @id @default(autoincrement()) + name String + abbreviation String + + + student_count Int? + website_link String? + catalog_link String? + + degrees Degree[] + + @@map("programs") +} + +model Degree { + id Int @id @default(autoincrement()) + type String @db.Text + program_id Int + note String? @db.Text + program Program @relation(fields: [program_id], references: [id]) + concentrations Concentration[] + + @@map("degrees") +} + +model Concentration { + id Int @id @default(autoincrement()) + name String? @db.Text + note String? @db.Text + degree_id Int + description String? @db.Text + degree Degree @relation(fields: [degree_id], references: [id]) + concentration_requirements ConcentrationRequirement[] + + @@map("concentrations") +} + +model ConcentrationRequirement { + id Int @id @default(autoincrement()) + requirement_index Int + concentration_id Int + requirement_id Int + concentration Concentration @relation(fields: [concentration_id], references: [id]) + requirement Requirement @relation(fields: [requirement_id], references: [id]) + + @@map("concentration_requirements") +} + +model Requirement { + id Int @id @default(autoincrement()) + name String + description String? + courses_required_count Int? + subreqs_required_count Int? + checkbox Boolean? + note String? + concentration_requirements ConcentrationRequirement[] + requirement_subrequirements RequirementSubrequirement[] + + @@map("requirements") +} + +model Subrequirement { + id Int @id @default(autoincrement()) + name String? + description String? + courses_required_count Int? + requirement_subrequirements RequirementSubrequirement[] + subrequirement_options SubrequirementOption[] + + @@map("subrequirements") +} + +model RequirementSubrequirement { + id Int @id @default(autoincrement()) + subrequirement_index Int? + description String? + requirement_id Int + subrequirement_id Int + requirement Requirement @relation(fields: [requirement_id], references: [id]) + subrequirement Subrequirement @relation(fields: [subrequirement_id], references: [id]) + + @@map("requirement_subrequirements") +} + +model SubrequirementOption { + id Int @id @default(autoincrement()) + option_index Int? + note String? @db.Text + subrequirement_id Int + option_id Int? + subrequirement Subrequirement @relation(fields: [subrequirement_id], references: [id]) + option Option? @relation(fields: [option_id], references: [id]) + + @@map("subrequirement_options") +} + +model Option { + id Int @id @default(autoincrement()) + option_course_id String? @db.Text + elective_range String? @db.Text + is_any_okay Boolean? + flags String? @db.Text + subrequirement_options SubrequirementOption[] + course Course? @relation(fields: [option_course_id], references: [id]) + + @@map("options") +} + +model Course { + id String @id @db.Text + title String @db.Text + description String? @db.Text + requirements String? @db.Text + professors String? @db.Text + distributions String? @db.Text + flags String? @db.Text + credits Float @db.Real + term String @db.Text + is_colsem Boolean? @default(false) + is_fysem Boolean? @default(false) + is_sysem Boolean? @default(false) + codes String[] @db.Text + options Option[] + + @@map("courses") +} \ No newline at end of file diff --git a/frontend/src/app/api/login/route.ts b/frontend/src/app/api/login/route.ts index 7f7f826..9e7daea 100644 --- a/frontend/src/app/api/login/route.ts +++ b/frontend/src/app/api/login/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; -import { Ryan } from "@/database/data-user"; +import { Ryan } from "@/database/mock/data-user"; export async function GET() { const user = Ryan; diff --git a/frontend/src/app/api/programs/db-service.ts b/frontend/src/app/api/programs/db-service.ts new file mode 100644 index 0000000..75368c2 --- /dev/null +++ b/frontend/src/app/api/programs/db-service.ts @@ -0,0 +1,99 @@ + +// db-service.ts +import prisma from '@/database/client' + +export async function fetchProgramHierarchy() { + const programs = await prisma.program.findMany() + + return Promise.all(programs.map(async program => { + const degrees = await prisma.degree.findMany({ + where: { program_id: program.id } + }) + + const degreesWithRelations = await Promise.all(degrees.map(async degree => { + const concentrations = await prisma.concentration.findMany({ + where: { degree_id: degree.id } + }) + + const concentrationsWithRequirements = await Promise.all(concentrations.map(async concentration => { + const concentrationRequirements = await prisma.concentrationRequirement.findMany({ + where: { concentration_id: concentration.id }, + include: { requirement: true } + }) + + const requirementsWithSubreqs = await Promise.all( + concentrationRequirements.map(async cr => { + const requirementSubrequirements = await prisma.requirementSubrequirement.findMany({ + where: { requirement_id: cr.requirement_id }, + include: { subrequirement: true } + }) + + const subrequirementsWithOptions = await Promise.all( + requirementSubrequirements.map(async rs => { + const subrequirementOptions = await prisma.subrequirementOption.findMany({ + where: { subrequirement_id: rs.subrequirement_id } + }); + + const optionsWithDetails = await Promise.all( + subrequirementOptions.map(async so => { + if (so.option_id) { + const option = await prisma.option.findUnique({ + where: { id: so.option_id } + }); + + if (option && option.option_course_id) { + const course = await prisma.course.findUnique({ + where: { id: option.option_course_id } + }); + + return { + ...so, + option: { ...option, course } + }; + } + + return { ...so, option }; + } + + return so; + }) + ); + + return { + ...rs, + subrequirement: { + ...rs.subrequirement, + subrequirement_options: optionsWithDetails + } + } + }) + ) + + return { + ...cr, + requirement: { + ...cr.requirement, + requirement_subrequirements: subrequirementsWithOptions + } + } + }) + ) + + return { + ...concentration, + concentration_requirements: requirementsWithSubreqs + } + })) + + return { + ...degree, + concentrations: concentrationsWithRequirements + } + })) + + return { + ...program, + degrees: degreesWithRelations + } + })) +} diff --git a/frontend/src/app/api/programs/route.ts b/frontend/src/app/api/programs/route.ts index 5b069d9..18ba359 100644 --- a/frontend/src/app/api/programs/route.ts +++ b/frontend/src/app/api/programs/route.ts @@ -1,7 +1,25 @@ - -import { NextResponse } from "next/server"; -import { PROG_DICT } from "@/database/programs/data-program"; +// route.ts +import { NextResponse } from 'next/server'; +import { fetchProgramHierarchy } from './db-service'; +import { transformProgram, createProgramDict } from './transformers'; export async function GET() { - return NextResponse.json(PROG_DICT, { status: 200 }); + try { + // Fetch data + const enrichedPrograms = await fetchProgramHierarchy(); + + // Transform to frontend types + const transformedPrograms = enrichedPrograms.map(transformProgram); + + // Create program dictionary + const programDict = createProgramDict(transformedPrograms); + + console.log(programDict); + + return NextResponse.json(programDict); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + console.error('Error fetching programs:', errorMessage); + return NextResponse.json({ error: 'Failed to fetch programs' }, { status: 500 }); + } } diff --git a/frontend/src/app/api/programs/transformers.ts b/frontend/src/app/api/programs/transformers.ts new file mode 100644 index 0000000..9194905 --- /dev/null +++ b/frontend/src/app/api/programs/transformers.ts @@ -0,0 +1,116 @@ + +// transformers.ts +import { Option, Subrequirement, Requirement, Concentration, Degree, Program, ProgramDict } from "@/types/type-program"; +import { Course } from "@/types/type-user"; + +export function transformCourse(courseData: any): Course | null { + if (!courseData) return null; + + return { + id: courseData.id, + title: courseData.title, + description: courseData.description || "", + requirements: courseData.requirements || "", + professors: courseData.professors || "", + distributions: courseData.distributions || "", + flags: courseData.flags || "", + credits: courseData.credits, + term: courseData.term, + is_colsem: courseData.is_colsem || false, + is_fysem: courseData.is_fysem || false, + is_sysem: courseData.is_sysem || false, + codes: courseData.codes || [] + }; +} + +export function transformOption(optionData: any): Option { + return { + option: optionData.option?.course ? transformCourse(optionData.option.course) : null, + satisfier: null, // This is for student data + elective_range: optionData.option?.elective_range || undefined, + flags: optionData.option?.flags ? optionData.option.flags.split(',').filter(Boolean) : undefined, + is_any_okay: optionData.option?.is_any_okay + }; +} + +export function transformSubrequirement(subrequirementData: any, index: number): Subrequirement { + return { + name: subrequirementData.name || "", + description: subrequirementData.description || "", + courses_required_count: subrequirementData.courses_required_count || 0, + options: subrequirementData.subrequirement_options.map(transformOption), + index: index + }; +} + +export function transformRequirement(requirementData: any, index: number): Requirement { + // Get subrequirements with their indices + const subrequirements = requirementData.requirement_subrequirements.map( + (rs: any) => ({ + data: rs.subrequirement, + index: rs.subrequirement_index || 0 + }) + ); + + // Sort by index if available + subrequirements.sort((a: any, b: any) => a.index - b.index); + + return { + name: requirementData.name || "", + description: requirementData.description || "", + courses_required_count: requirementData.courses_required_count || 0, + subreqs_required_count: requirementData.subreqs_required_count || 0, + checkbox: requirementData.checkbox, + subrequirements: subrequirements.map( + (item: any, i: number) => transformSubrequirement(item.data, item.index || i) + ), + index: index + }; +} + +export function transformConcentration(concentrationData: any): Concentration { + // Get requirements with their indices + const requirements = concentrationData.concentration_requirements.map( + (cr: any) => ({ + data: cr.requirement, + index: cr.requirement_index || 0 + }) + ); + + // Sort by index if available + requirements.sort((a: any, b: any) => a.index - b.index); + + return { + name: concentrationData.name || "", + description: concentrationData.description || "", + requirements: requirements.map( + (item: any, i: number) => transformRequirement(item.data, item.index || i) + ) + }; +} + +export function transformDegree(degreeData: any): Degree { + return { + type: degreeData.type || "", + concentrations: degreeData.concentrations.map(transformConcentration) + }; +} + +export function transformProgram(programData: any): Program { + return { + name: programData.name || "", + abbreviation: programData.abbreviation || "", + student_count: programData.student_count || 0, + website_link: programData.website_link || "", + catolog_link: programData.catalog_link || "", // Note: Fixed typo + degrees: programData.degrees.map(transformDegree) + }; +} + +export function createProgramDict(programs: Program[]): ProgramDict { + const programDict: ProgramDict = {}; + programs.forEach(program => { + programDict[program.abbreviation] = program; + }); + return programDict; +} diff --git a/frontend/src/app/graduation/page.tsx b/frontend/src/app/graduation/page.tsx index 82ed6ba..ba6581d 100644 --- a/frontend/src/app/graduation/page.tsx +++ b/frontend/src/app/graduation/page.tsx @@ -1,47 +1,55 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import Style from "./Graduation.module.css"; import NavBar from "@/components/navbar/NavBar"; -type Course = { - course_id: number; - codes: string; - title: string; - description: string; - requirements?: string; - professors?: string[]; - distributions?: string; - flags?: string; - credits: number; - season_code?: string; - colsem?: string; - fysem?: string; - sysem?: string; -}; +import { Program } from "@prisma/client"; + +// type Course = { +// course_id: number; +// codes: string; +// title: string; +// description: string; +// requirements?: string; +// professors?: string[]; +// distributions?: string; +// flags?: string; +// credits: number; +// season_code?: string; +// colsem?: string; +// fysem?: string; +// sysem?: string; +// }; export default function Graduation() { - const [courses, setCourses] = useState([]); // Explicitly type courses - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - - function fetchCourses() { - setLoading(true); - setError(null); - - fetch("/api/courses") - .then((res) => res.json()) - .then((data: Course[]) => { - setCourses(data); - }) - .catch((err) => setError(err.message)) - .finally(() => setLoading(false)); - } + const [programs, setPrograms] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + async function fetchPrograms() { + try { + const response = await fetch('/api/programs') + const data = await response.json() + console.log(data) + setPrograms(data) + } catch (error) { + console.error('Error fetching programs:', error) + } finally { + setLoading(false) + } + } + + fetchPrograms() + }, []) + + if (loading) return
Loading programs...
return (
-
+
+
); diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index dc9ea91..de50e6c 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -53,7 +53,7 @@ function Majors() return(
- }/> + {/* }/>
setListView((prev) => !prev)}/>
fill(user.FYP.studentCourses, progDict, setProgDict)}/> @@ -64,7 +64,7 @@ function Majors() />
-
+
*/}
); } diff --git a/frontend/src/context/AuthProvider.tsx b/frontend/src/context/AuthProvider.tsx index 77a0cfd..52ab836 100644 --- a/frontend/src/context/AuthProvider.tsx +++ b/frontend/src/context/AuthProvider.tsx @@ -2,7 +2,7 @@ "use client"; import { createContext, useContext, useState, useEffect, useCallback } from "react"; import { User } from "@/types/type-user"; -import { Ryan } from "@/database/data-user"; +import { Ryan } from "@/database/mock/data-user"; import { usePrograms } from "@/context/ProgramProvider"; import { fill } from "@/utils/preprocessing/Fill"; @@ -17,22 +17,22 @@ interface AuthContextType { const AuthContext = createContext(null); export function AuthProvider({ children }: { children: React.ReactNode }) { - const { setProgDict, baseProgDict } = usePrograms(); + // const { setProgDict, baseProgDict } = usePrograms(); const [auth, setAuth] = useState({ loggedIn: false }); const [user, setUser] = useState(Ryan); // Create a stable reference to the combined reset and fill function - const resetAndFill = useCallback(() => { - const studentCourses = user.FYP.studentCourses; + // const resetAndFill = useCallback(() => { + // const studentCourses = user.FYP.studentCourses; - if (studentCourses.length > 0) { - // Create a deep clone of baseProgDict - const freshCopy = JSON.parse(JSON.stringify(baseProgDict)); + // if (studentCourses.length > 0) { + // // Create a deep clone of baseProgDict + // const freshCopy = JSON.parse(JSON.stringify(baseProgDict)); - // Pass the fresh copy to fill - the fill function will handle the state update - fill(studentCourses, freshCopy, setProgDict); - } - }, [user.FYP.studentCourses, baseProgDict, setProgDict]); + // // Pass the fresh copy to fill - the fill function will handle the state update + // fill(studentCourses, freshCopy, setProgDict); + // } + // }, [user.FYP.studentCourses, baseProgDict, setProgDict]); // Set initial user data useEffect(() => { @@ -40,11 +40,11 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { }, []); // Update program data when courses change - useEffect(() => { - if (user.FYP.studentCourses.length > 0) { - resetAndFill(); - } - }, [user.FYP.studentCourses, resetAndFill]); + // useEffect(() => { + // if (user.FYP.studentCourses.length > 0) { + // resetAndFill(); + // } + // }, [user.FYP.studentCourses, resetAndFill]); return ( diff --git a/frontend/src/context/ProgramProvider.tsx b/frontend/src/context/ProgramProvider.tsx index a89595a..0dd1fd6 100644 --- a/frontend/src/context/ProgramProvider.tsx +++ b/frontend/src/context/ProgramProvider.tsx @@ -5,12 +5,12 @@ import { ProgramDict } from "@/types/type-program"; // Define context type interface ProgramContextType { - progDict: ProgramDict; - setProgDict: (dict: ProgramDict) => void; - baseProgDict: ProgramDict; + // progDict: ProgramDict; + // setProgDict: (dict: ProgramDict) => void; + // baseProgDict: ProgramDict; isLoading: boolean; - error: string | null; - resetToBase: () => void; + // error: string | null; + // resetToBase: () => void; } const ProgramContext = createContext(null); @@ -21,40 +21,40 @@ export function ProgramProvider({ children }: { children: React.ReactNode }) { const [progDict, setProgDict] = useState({}); const [error, setError] = useState(null); - useEffect(() => { - const fetchPrograms = async () => { - setIsLoading(true); - try { - const response = await fetch('/api/programs'); - if (!response.ok) throw new Error('Failed to fetch programs'); + // useEffect(() => { + // const fetchPrograms = async () => { + // setIsLoading(true); + // try { + // const response = await fetch('/api/programs'); + // if (!response.ok) throw new Error('Failed to fetch programs'); - const fetchedData = await response.json(); - // Store as separate objects to prevent reference issues - setBaseProgDict(JSON.parse(JSON.stringify(fetchedData))); - setProgDict(JSON.parse(JSON.stringify(fetchedData))); - } catch (err) { - setError(err instanceof Error ? err.message : 'Unknown error'); - } finally { - setIsLoading(false); - } - }; + // const fetchedData = await response.json(); + // // Store as separate objects to prevent reference issues + // setBaseProgDict(JSON.parse(JSON.stringify(fetchedData))); + // setProgDict(JSON.parse(JSON.stringify(fetchedData))); + // } catch (err) { + // setError(err instanceof Error ? err.message : 'Unknown error'); + // } finally { + // setIsLoading(false); + // } + // }; - fetchPrograms(); - }, []); + // fetchPrograms(); + // }, []); - // Deep clone when resetting to base - const resetToBase = useCallback(() => { - setProgDict(JSON.parse(JSON.stringify(baseProgDict))); - }, [baseProgDict]); + // // Deep clone when resetting to base + // const resetToBase = useCallback(() => { + // setProgDict(JSON.parse(JSON.stringify(baseProgDict))); + // }, [baseProgDict]); return ( {children} diff --git a/frontend/src/database/client.ts b/frontend/src/database/client.ts new file mode 100644 index 0000000..d98b570 --- /dev/null +++ b/frontend/src/database/client.ts @@ -0,0 +1,11 @@ + +import { PrismaClient } from '@prisma/client' + +// Prevent multiple instances during dev hot reloading +const globalForPrisma = global as unknown as { prisma: PrismaClient } + +export const prisma = globalForPrisma.prisma || new PrismaClient() + +if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma + +export default prisma diff --git a/frontend/src/database/data-courses.ts b/frontend/src/database/mock/data-courses.ts similarity index 100% rename from frontend/src/database/data-courses.ts rename to frontend/src/database/mock/data-courses.ts diff --git a/frontend/src/database/data-user.ts b/frontend/src/database/mock/data-user.ts similarity index 100% rename from frontend/src/database/data-user.ts rename to frontend/src/database/mock/data-user.ts diff --git a/frontend/src/database/mock/programs/concs/concs-cpsc.ts b/frontend/src/database/mock/programs/concs/concs-cpsc.ts new file mode 100644 index 0000000..74dd885 --- /dev/null +++ b/frontend/src/database/mock/programs/concs/concs-cpsc.ts @@ -0,0 +1,174 @@ + +// import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; + +// // CORE + +// const CORE_1: ConcentrationSubrequirement = { +// subreq_name: "INTRO", +// subreq_desc: "", +// subreq_flex: false, +// subreq_courses_req_count: 1, +// subreq_options: [ +// { +// option: null, +// satisfier: null, +// } +// ] +// } + +// const CORE_2: ConcentrationSubrequirement = { +// subreq_name: "DISCRETE MATH", +// subreq_desc: "", +// subreq_flex: false, +// subreq_courses_req_count: 1, +// subreq_options: [ +// { +// o: null, +// s: null, +// }, +// { +// o: null, +// s: null, +// }, +// ] +// } + +// const CORE_3: ConcentrationSubrequirement = { +// subreq_name: "DATA STRUCTURES", +// subreq_desc: "", +// subreq_flex: false, +// subreq_courses_req_count: 1, +// subreq_options: [ +// { +// o: null, +// s: null, +// } +// ] +// } + +// const CORE_4: ConcentrationSubrequirement = { +// subreq_name: "SYSTEMS", +// subreq_desc: "", +// subreq_flex: false, +// subreq_courses_req_count: 1, +// subreq_options: [ +// { +// o: null, +// s: null, +// } +// ] +// } + +// const CORE_5: ConcentrationSubrequirement = { +// subreq_name: "ALGORITHMS", +// subreq_desc: "", +// subreq_flex: false, +// subreq_courses_req_count: 1, +// subreq_options: [ +// { +// o: null, +// s: null, +// }, +// { +// o: null, +// s: null, +// }, +// ] +// } + +// const CPSC_CORE: ConcentrationRequirement = { +// req_name: "CORE", +// req_desc: "", +// courses_required_count: 5, +// subreqs_list: [CORE_1, CORE_2, CORE_3, CORE_4, CORE_5] +// } + +// // ELECTIVE + +// const ELEC_MULT_BA: ConcentrationSubrequirement = { +// subreq_name: "", +// subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", +// subreq_flex: false, +// subreq_courses_req_count: 3, +// subreq_options: [ +// { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, +// { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, +// { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, +// ] +// } + +// const ELEC_MULT_BS: ConcentrationSubrequirement = { +// subreq_name: "", +// subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", +// subreq_flex: false, +// subreq_courses_req_count: 5, +// subreq_options: [ +// { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, +// { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, +// { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, +// { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, +// { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, +// ] +// } + +// const ELEC_SUB: ConcentrationSubrequirement = { +// subreq_name: "", +// subreq_desc: "Standard elective or DUS approved extra-department substitution.", +// subreq_flex: true, +// subreq_courses_req_count: 1, +// subreq_options: [ +// { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 }, a: true } }, +// ] +// } + +// const CPSC_BA_ELEC: ConcentrationRequirement = { +// req_name: "ELECTIVE", +// req_desc: "", +// courses_required_count: 4, +// subreqs_list: [ELEC_SUB, ELEC_MULT_BA] +// } + +// const CPSC_BS_ELEC: ConcentrationRequirement = { +// req_name: "ELECTIVE", +// req_desc: "", +// courses_required_count: 6, +// subreqs_list: [ELEC_SUB, ELEC_MULT_BS] +// } + +// // SENIOR + +// const SEN_PROJ: ConcentrationSubrequirement = { +// subreq_name: "SENIOR PROJECT", +// subreq_desc: "", +// subreq_flex: false, +// subreq_courses_req_count: 1, +// subreq_options: [ +// { +// o: null, +// s: null, +// } +// ] +// } + +// const CPSC_SENIOR: ConcentrationRequirement = { +// req_name: "SENIOR", +// req_desc: "", +// courses_required_count: 1, +// subreqs_list: [SEN_PROJ] +// } + +// // EXPORT + +// export const CONC_CPSC_BA_I: DegreeConcentration = { +// user_status: 1, +// conc_name: "", +// conc_desc: "", +// conc_reqs: [CPSC_CORE, CPSC_BA_ELEC, CPSC_SENIOR] +// } + +// export const CONC_CPSC_BS_I: DegreeConcentration = { +// user_status: 0, +// conc_name: "", +// conc_desc: "", +// conc_reqs: [CPSC_CORE, CPSC_BS_ELEC, CPSC_SENIOR] +// } diff --git a/frontend/src/database/mock/programs/concs/concs-econ.ts b/frontend/src/database/mock/programs/concs/concs-econ.ts new file mode 100644 index 0000000..8e25edd --- /dev/null +++ b/frontend/src/database/mock/programs/concs/concs-econ.ts @@ -0,0 +1,203 @@ + +// import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; +// import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151, SC_ECON_110 } from "../../data-courses"; + +// // // INTRO + +// const INTRO_1: ConcentrationSubrequirement = { +// subreq_name: "MATH", +// subreq_desc: "118 or 120 recommended. Any MATH 200+ satisfies.", +// subreq_flex: true, +// subreq_courses_req_count: 1, +// subreq_options: [ +// { +// o: MATH_118, +// s: null, +// }, +// { +// o: MATH_120, +// s: null, +// }, +// ] +// } + +// const INTRO_2: ConcentrationSubrequirement = { +// subreq_name: "INTRO MICRO", +// subreq_desc: "", +// subreq_flex: true, +// subreq_courses_req_count: 1, +// subreq_options: [ +// { +// o: ECON_108, +// s: null, +// }, +// { +// o: ECON_110, +// s: null, +// }, +// { +// o: ECON_115, +// s: null, +// }, +// ] +// } + +// const INTRO_3: ConcentrationSubrequirement = { +// subreq_name: "INTRO MACRO", +// subreq_desc: "", +// subreq_flex: true, +// subreq_courses_req_count: 1, +// subreq_options: [ +// { +// o: ECON_111, +// s: null, +// }, +// { +// o: ECON_116, +// s: null, +// }, +// ] +// } + +// const ECON_INTRO: ConcentrationRequirement = { +// req_name: "INTRO", +// req_desc: "", +// courses_required_count: 3, +// subreqs_list: [INTRO_1, INTRO_2, INTRO_3] +// } + +// // // CORE + +// const CORE_MICRO: ConcentrationSubrequirement = { +// subreq_name: "INTERMEDIATE MICRO", +// subreq_desc: "", +// subreq_flex: false, +// subreq_courses_req_count: 1, +// subreq_options: [ +// { +// o: ECON_121, +// s: null, +// }, +// { +// o: ECON_125, +// s: null, +// }, +// ] +// } + +// const CORE_MACRO: ConcentrationSubrequirement = { +// subreq_name: "INTERMEDIATE MACRO", +// subreq_desc: "", +// subreq_flex: false, +// subreq_courses_req_count: 1, +// subreq_options: [ +// { +// o: ECON_122, +// s: null, +// }, +// { +// o: ECON_126, +// s: null, +// }, +// ] +// } + +// const CORE_METRICS: ConcentrationSubrequirement = { +// subreq_name: "ECONOMETRICS", +// subreq_desc: "", +// subreq_flex: false, +// subreq_courses_req_count: 1, +// subreq_options: [ +// { +// o: ECON_117, +// s: null, +// }, +// { +// o: ECON_123, +// s: null, +// }, +// { +// o: ECON_136, +// s: null, +// }, +// ] +// } + +// const ECON_CORE: ConcentrationRequirement = { +// req_name: "CORE", +// req_desc: "", +// courses_required_count: 3, +// subreqs_list: [CORE_MICRO, CORE_MACRO, CORE_METRICS] +// } + +// // // ELECTIVE + +// const ELEC_SUB: ConcentrationSubrequirement = { +// subreq_name: "", +// subreq_desc: "Standard elective or DUS approved extra-department substitution.", +// subreq_flex: true, +// subreq_courses_req_count: 1, +// subreq_options: [ +// { o: null, s: null, n: { e: { dept: "ECON", min: 123, max: 399 }, a: true } }, +// ] +// } + +// const ELEC_STAN: ConcentrationSubrequirement = { +// subreq_name: "", +// subreq_desc: "Intermediate or advanced ECON courses, traditionally numbered 123+.", +// subreq_flex: true, +// subreq_courses_req_count: 3, +// subreq_options: [ +// { o: null, s: null, n: { e: { dept: "ECON", min: 123, max: 399 } } }, +// { o: null, s: null, n: { e: { dept: "ECON", min: 123, max: 399 } } }, +// { o: null, s: null, n: { e: { dept: "ECON", min: 123, max: 399 } } }, +// ] +// } + +// const ECON_ELECTIVE: ConcentrationRequirement = { +// req_name: "ELECTIVE", +// req_desc: "", +// courses_required_count: 4, +// subreqs_list: [ELEC_STAN, ELEC_SUB] +// } + +// // SENIOR + +// const SEN_REQ: ConcentrationSubrequirement = { +// subreq_name: "SENIOR REQUIREMENT", +// subreq_desc: "", +// subreq_flex: true, +// subreq_courses_req_count: 2, +// subreq_options: [ +// { +// o: null, +// s: null, +// n: { +// e: { dept: "ECON", min: 400, max: 491 } +// } +// }, +// { +// o: null, +// s: null, +// n: { +// e: { dept: "ECON", min: 400, max: 491 } +// } +// }, +// ] +// } + +// const ECON_SEN: ConcentrationRequirement = { +// req_name: "SENIOR", +// req_desc: "", +// courses_required_count: 2, +// subreqs_list: [SEN_REQ] +// } + +// // // // FINAL + +// export const CONC_ECON_BA_I: DegreeConcentration = { +// user_status: 0, +// conc_name: "", +// conc_desc: "", +// conc_reqs: [ECON_INTRO, ECON_CORE, ECON_ELECTIVE, ECON_SEN] +// } diff --git a/frontend/src/database/programs/concs/concs-hist.ts b/frontend/src/database/mock/programs/concs/concs-hist.ts similarity index 100% rename from frontend/src/database/programs/concs/concs-hist.ts rename to frontend/src/database/mock/programs/concs/concs-hist.ts diff --git a/frontend/src/database/programs/concs/concs-plsc.ts b/frontend/src/database/mock/programs/concs/concs-plsc.ts similarity index 100% rename from frontend/src/database/programs/concs/concs-plsc.ts rename to frontend/src/database/mock/programs/concs/concs-plsc.ts diff --git a/frontend/src/database/mock/programs/data-program.ts b/frontend/src/database/mock/programs/data-program.ts new file mode 100644 index 0000000..2a93a60 --- /dev/null +++ b/frontend/src/database/mock/programs/data-program.ts @@ -0,0 +1,74 @@ + +import { Program, ProgramDict } from "@/types/type-program"; +// import { CONC_CPSC_BA_I, CONC_CPSC_BS_I } from "./concs/concs-cpsc"; +// import { CONC_ECON_BA_I } from "./concs/concs-econ"; +// // import { CONC_HIST_BA_GLOB, CONC_HIST_BA_SPEC } from "./concs/concs-hist"; +// // import { CONC_PLSC_BA_INTE, CONC_PLSC_BA_STAN } from "./concs/concs-plsc"; + +// const PROG_CPSC: Program = { +// prog_data: { +// prog_name: "Computer Science", +// prog_abbr: "CPSC", +// prog_stud_count: 0, +// prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, +// prog_catolog: "", +// prog_website: "" +// }, +// prog_degs: [ +// { deg_type: "B.A.", deg_concs: [CONC_CPSC_BA_I] }, + +// ] +// } +// // { deg_type: "B.S.", deg_concs: [CONC_CPSC_BS_I] } + +// // const PROG_ECON: Program = { +// // prog_data: { +// // prog_name: "Economics", +// // prog_abbr: "ECON", +// // prog_stud_count: 0, +// // prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, +// // prog_catolog: "", +// // prog_website: "" +// // }, +// // prog_degs: [ +// // { deg_type: "B.A.", deg_concs: [CONC_ECON_BA_I] } +// // ] +// // } + +// // const PROG_PLSC: Program = { +// // prog_data: { +// // prog_name: "Political Science", +// // prog_abbr: "PLSC", +// // prog_stud_count: 0, +// // prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, +// // prog_catolog: "", +// // prog_website: "" +// // }, +// // prog_degs: [ +// // { deg_type: "B.A.", deg_concs: [CONC_PLSC_BA_STAN, CONC_PLSC_BA_INTE] } +// // ] +// // } + +// // const PROG_HIST: Program = { +// // prog_data: { +// // prog_name: "History", +// // prog_abbr: "HIST", +// // prog_stud_count: 0, +// // prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, +// // prog_catolog: "", +// // prog_website: "" +// // }, +// // prog_degs: [ +// // { +// // deg_type: "B.A.", +// // deg_concs: [CONC_HIST_BA_GLOB, CONC_HIST_BA_SPEC] +// // } +// // ] +// // } + +export const PROG_DICT: ProgramDict = { + // "CPSC": PROG_CPSC, + // "ECON": PROG_ECON, + // "PLSC": PROG_PLSC, + // "HIST": PROG_HIST, +}; diff --git a/frontend/src/database/programs/concs/concs-cpsc.ts b/frontend/src/database/programs/concs/concs-cpsc.ts deleted file mode 100644 index 199bcbe..0000000 --- a/frontend/src/database/programs/concs/concs-cpsc.ts +++ /dev/null @@ -1,174 +0,0 @@ - -import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; - -// CORE - -const CORE_1: ConcentrationSubrequirement = { - subreq_name: "INTRO", - subreq_desc: "", - subreq_flex: false, - subreq_courses_req_count: 1, - subreq_options: [ - { - o: null, - s: null, - } - ] -} - -const CORE_2: ConcentrationSubrequirement = { - subreq_name: "DISCRETE MATH", - subreq_desc: "", - subreq_flex: false, - subreq_courses_req_count: 1, - subreq_options: [ - { - o: null, - s: null, - }, - { - o: null, - s: null, - }, - ] -} - -const CORE_3: ConcentrationSubrequirement = { - subreq_name: "DATA STRUCTURES", - subreq_desc: "", - subreq_flex: false, - subreq_courses_req_count: 1, - subreq_options: [ - { - o: null, - s: null, - } - ] -} - -const CORE_4: ConcentrationSubrequirement = { - subreq_name: "SYSTEMS", - subreq_desc: "", - subreq_flex: false, - subreq_courses_req_count: 1, - subreq_options: [ - { - o: null, - s: null, - } - ] -} - -const CORE_5: ConcentrationSubrequirement = { - subreq_name: "ALGORITHMS", - subreq_desc: "", - subreq_flex: false, - subreq_courses_req_count: 1, - subreq_options: [ - { - o: null, - s: null, - }, - { - o: null, - s: null, - }, - ] -} - -const CPSC_CORE: ConcentrationRequirement = { - req_name: "CORE", - req_desc: "", - courses_required_count: 5, - subreqs_list: [CORE_1, CORE_2, CORE_3, CORE_4, CORE_5] -} - -// ELECTIVE - -const ELEC_MULT_BA: ConcentrationSubrequirement = { - subreq_name: "", - subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", - subreq_flex: false, - subreq_courses_req_count: 3, - subreq_options: [ - { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, - { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, - { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, - ] -} - -const ELEC_MULT_BS: ConcentrationSubrequirement = { - subreq_name: "", - subreq_desc: "Intermediate or advanced CPSC courses, traditionally numbered 300+.", - subreq_flex: false, - subreq_courses_req_count: 5, - subreq_options: [ - { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, - { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, - { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, - { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, - { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 } } }, - ] -} - -const ELEC_SUB: ConcentrationSubrequirement = { - subreq_name: "", - subreq_desc: "Standard elective or DUS approved extra-department substitution.", - subreq_flex: true, - subreq_courses_req_count: 1, - subreq_options: [ - { o: null, s: null, n: { e: { dept: "CPSC", min: 300, max: 999 }, a: true } }, - ] -} - -const CPSC_BA_ELEC: ConcentrationRequirement = { - req_name: "ELECTIVE", - req_desc: "", - courses_required_count: 4, - subreqs_list: [ELEC_SUB, ELEC_MULT_BA] -} - -const CPSC_BS_ELEC: ConcentrationRequirement = { - req_name: "ELECTIVE", - req_desc: "", - courses_required_count: 6, - subreqs_list: [ELEC_SUB, ELEC_MULT_BS] -} - -// SENIOR - -const SEN_PROJ: ConcentrationSubrequirement = { - subreq_name: "SENIOR PROJECT", - subreq_desc: "", - subreq_flex: false, - subreq_courses_req_count: 1, - subreq_options: [ - { - o: null, - s: null, - } - ] -} - -const CPSC_SENIOR: ConcentrationRequirement = { - req_name: "SENIOR", - req_desc: "", - courses_required_count: 1, - subreqs_list: [SEN_PROJ] -} - -// EXPORT - -export const CONC_CPSC_BA_I: DegreeConcentration = { - user_status: 1, - conc_name: "", - conc_desc: "", - conc_reqs: [CPSC_CORE, CPSC_BA_ELEC, CPSC_SENIOR] -} - -export const CONC_CPSC_BS_I: DegreeConcentration = { - user_status: 0, - conc_name: "", - conc_desc: "", - conc_reqs: [CPSC_CORE, CPSC_BS_ELEC, CPSC_SENIOR] -} diff --git a/frontend/src/database/programs/concs/concs-econ.ts b/frontend/src/database/programs/concs/concs-econ.ts deleted file mode 100644 index a1a0b5e..0000000 --- a/frontend/src/database/programs/concs/concs-econ.ts +++ /dev/null @@ -1,203 +0,0 @@ - -import { ConcentrationSubrequirement, ConcentrationRequirement, DegreeConcentration } from "@/types/type-program"; -import { ECON_108, ECON_110, ECON_111, ECON_115, ECON_116, ECON_117, ECON_121, ECON_122, ECON_123, ECON_125, ECON_126, ECON_136, MATH_110, MATH_111, MATH_112, MATH_115, MATH_116, MATH_118, MATH_120, ENAS_151, SC_ECON_110 } from "./../../data-courses"; - -// // INTRO - -const INTRO_1: ConcentrationSubrequirement = { - subreq_name: "MATH", - subreq_desc: "118 or 120 recommended. Any MATH 200+ satisfies.", - subreq_flex: true, - subreq_courses_req_count: 1, - subreq_options: [ - { - o: MATH_118, - s: null, - }, - { - o: MATH_120, - s: null, - }, - ] -} - -const INTRO_2: ConcentrationSubrequirement = { - subreq_name: "INTRO MICRO", - subreq_desc: "", - subreq_flex: true, - subreq_courses_req_count: 1, - subreq_options: [ - { - o: ECON_108, - s: null, - }, - { - o: ECON_110, - s: null, - }, - { - o: ECON_115, - s: null, - }, - ] -} - -const INTRO_3: ConcentrationSubrequirement = { - subreq_name: "INTRO MACRO", - subreq_desc: "", - subreq_flex: true, - subreq_courses_req_count: 1, - subreq_options: [ - { - o: ECON_111, - s: null, - }, - { - o: ECON_116, - s: null, - }, - ] -} - -const ECON_INTRO: ConcentrationRequirement = { - req_name: "INTRO", - req_desc: "", - courses_required_count: 3, - subreqs_list: [INTRO_1, INTRO_2, INTRO_3] -} - -// // CORE - -const CORE_MICRO: ConcentrationSubrequirement = { - subreq_name: "INTERMEDIATE MICRO", - subreq_desc: "", - subreq_flex: false, - subreq_courses_req_count: 1, - subreq_options: [ - { - o: ECON_121, - s: null, - }, - { - o: ECON_125, - s: null, - }, - ] -} - -const CORE_MACRO: ConcentrationSubrequirement = { - subreq_name: "INTERMEDIATE MACRO", - subreq_desc: "", - subreq_flex: false, - subreq_courses_req_count: 1, - subreq_options: [ - { - o: ECON_122, - s: null, - }, - { - o: ECON_126, - s: null, - }, - ] -} - -const CORE_METRICS: ConcentrationSubrequirement = { - subreq_name: "ECONOMETRICS", - subreq_desc: "", - subreq_flex: false, - subreq_courses_req_count: 1, - subreq_options: [ - { - o: ECON_117, - s: null, - }, - { - o: ECON_123, - s: null, - }, - { - o: ECON_136, - s: null, - }, - ] -} - -const ECON_CORE: ConcentrationRequirement = { - req_name: "CORE", - req_desc: "", - courses_required_count: 3, - subreqs_list: [CORE_MICRO, CORE_MACRO, CORE_METRICS] -} - -// // ELECTIVE - -const ELEC_SUB: ConcentrationSubrequirement = { - subreq_name: "", - subreq_desc: "Standard elective or DUS approved extra-department substitution.", - subreq_flex: true, - subreq_courses_req_count: 1, - subreq_options: [ - { o: null, s: null, n: { e: { dept: "ECON", min: 123, max: 399 }, a: true } }, - ] -} - -const ELEC_STAN: ConcentrationSubrequirement = { - subreq_name: "", - subreq_desc: "Intermediate or advanced ECON courses, traditionally numbered 123+.", - subreq_flex: true, - subreq_courses_req_count: 3, - subreq_options: [ - { o: null, s: null, n: { e: { dept: "ECON", min: 123, max: 399 } } }, - { o: null, s: null, n: { e: { dept: "ECON", min: 123, max: 399 } } }, - { o: null, s: null, n: { e: { dept: "ECON", min: 123, max: 399 } } }, - ] -} - -const ECON_ELECTIVE: ConcentrationRequirement = { - req_name: "ELECTIVE", - req_desc: "", - courses_required_count: 4, - subreqs_list: [ELEC_STAN, ELEC_SUB] -} - -// SENIOR - -const SEN_REQ: ConcentrationSubrequirement = { - subreq_name: "SENIOR REQUIREMENT", - subreq_desc: "", - subreq_flex: true, - subreq_courses_req_count: 2, - subreq_options: [ - { - o: null, - s: null, - n: { - e: { dept: "ECON", min: 400, max: 491 } - } - }, - { - o: null, - s: null, - n: { - e: { dept: "ECON", min: 400, max: 491 } - } - }, - ] -} - -const ECON_SEN: ConcentrationRequirement = { - req_name: "SENIOR", - req_desc: "", - courses_required_count: 2, - subreqs_list: [SEN_REQ] -} - -// // // FINAL - -export const CONC_ECON_BA_I: DegreeConcentration = { - user_status: 0, - conc_name: "", - conc_desc: "", - conc_reqs: [ECON_INTRO, ECON_CORE, ECON_ELECTIVE, ECON_SEN] -} diff --git a/frontend/src/database/programs/data-program.ts b/frontend/src/database/programs/data-program.ts deleted file mode 100644 index 54ba109..0000000 --- a/frontend/src/database/programs/data-program.ts +++ /dev/null @@ -1,74 +0,0 @@ - -import { Program, ProgramDict } from "@/types/type-program"; -import { CONC_CPSC_BA_I, CONC_CPSC_BS_I } from "./concs/concs-cpsc"; -import { CONC_ECON_BA_I } from "./concs/concs-econ"; -// import { CONC_HIST_BA_GLOB, CONC_HIST_BA_SPEC } from "./concs/concs-hist"; -// import { CONC_PLSC_BA_INTE, CONC_PLSC_BA_STAN } from "./concs/concs-plsc"; - -const PROG_CPSC: Program = { - prog_data: { - prog_name: "Computer Science", - prog_abbr: "CPSC", - prog_stud_count: 0, - prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, - prog_catolog: "", - prog_website: "" - }, - prog_degs: [ - { deg_type: "B.A.", deg_concs: [CONC_CPSC_BA_I] }, - - ] -} -// { deg_type: "B.S.", deg_concs: [CONC_CPSC_BS_I] } - -// const PROG_ECON: Program = { -// prog_data: { -// prog_name: "Economics", -// prog_abbr: "ECON", -// prog_stud_count: 0, -// prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, -// prog_catolog: "", -// prog_website: "" -// }, -// prog_degs: [ -// { deg_type: "B.A.", deg_concs: [CONC_ECON_BA_I] } -// ] -// } - -// const PROG_PLSC: Program = { -// prog_data: { -// prog_name: "Political Science", -// prog_abbr: "PLSC", -// prog_stud_count: 0, -// prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, -// prog_catolog: "", -// prog_website: "" -// }, -// prog_degs: [ -// { deg_type: "B.A.", deg_concs: [CONC_PLSC_BA_STAN, CONC_PLSC_BA_INTE] } -// ] -// } - -// const PROG_HIST: Program = { -// prog_data: { -// prog_name: "History", -// prog_abbr: "HIST", -// prog_stud_count: 0, -// prog_dus: { dus_name: "", dus_email: "", dus_address: "" }, -// prog_catolog: "", -// prog_website: "" -// }, -// prog_degs: [ -// { -// deg_type: "B.A.", -// deg_concs: [CONC_HIST_BA_GLOB, CONC_HIST_BA_SPEC] -// } -// ] -// } - -export const PROG_DICT: ProgramDict = { - "CPSC": PROG_CPSC, - // "ECON": PROG_ECON, - // "PLSC": PROG_PLSC, - // "HIST": PROG_HIST, -}; diff --git a/frontend/src/types/supabase.ts b/frontend/src/types/supabase.ts deleted file mode 100644 index 7235096..0000000 --- a/frontend/src/types/supabase.ts +++ /dev/null @@ -1,3 +0,0 @@ -Need to install the following packages: -supabase@2.19.7 -Ok to proceed? (y) \ No newline at end of file diff --git a/frontend/src/types/type-program.ts b/frontend/src/types/type-program.ts index 734b5d5..a564f17 100644 --- a/frontend/src/types/type-program.ts +++ b/frontend/src/types/type-program.ts @@ -1,82 +1,59 @@ import { Course, StudentCourse } from "./type-user"; -export interface ElectiveRange { - dept: string; - min: number; - max: number; -} - -interface OptionNullHelp { - e?: ElectiveRange; - f?: string[]; - a?: boolean; -} - -export interface SubreqCourseOption { - o: Course | null; - s: StudentCourse | null; - n?: OptionNullHelp | null; -} - -export interface ConcentrationSubrequirement { - subreq_name: string; - subreq_desc: string; - - subreq_flex: boolean; - subreq_courses_req_count: number; - subreq_options: SubreqCourseOption[] +export interface Option { + option: Course | null; + satisfier: StudentCourse | null; + elective_range?: string; + flags?: string[]; + is_any_okay?: boolean; +} + +export interface Subrequirement { + name: string; + description: string; + index: number; + courses_required_count: number; + options: Option[] } -export interface ConcentrationRequirement { - req_name: string; - req_desc: string; - +export interface Requirement { + name: string; + description: string; + index: number; courses_required_count: number; - - subreqs_required_count?: number; - subreqs_satisfied_count?: number; - + subreqs_required_count: number; checkbox?: boolean; - subreqs_list: ConcentrationSubrequirement[]; -} - -export interface DegreeConcentration { - user_status: number; - conc_name: string; - conc_desc: string; - conc_reqs: ConcentrationRequirement[]; + subrequirements: Subrequirement[]; } -export interface ProgramDegree { - deg_type: string; - deg_concs: DegreeConcentration[]; +export interface Concentration { + name: string; + description: string; + requirements: Requirement[]; } -interface ProgDUS { - dus_name: string; - dus_email: string; - dus_address: string; -} - -interface ProgramMetadata { - prog_name: string; - prog_abbr: string; - prog_stud_count: number; - prog_dus: ProgDUS; - prog_catolog: string; - prog_website: string; +export interface Degree { + type: string; + concentrations: Concentration[]; } export interface Program { - prog_data: ProgramMetadata; - prog_degs: ProgramDegree[]; + name: string; + abbreviation: string; + student_count: number; + catolog_link: string; + website_link: string; + degrees: Degree[]; } -export interface MajorsIndex { - prog: string; - deg: number; - conc: number; -} +// any null strings go to "", null numbers go to 0 +// string is program.abbreviation export type ProgramDict = Record; + +// export interface MajorsIndex { +// prog: string; +// deg: number; +// conc: number; +// } diff --git a/frontend/src/types/type-user.ts b/frontend/src/types/type-user.ts index f9db475..7a3b4a4 100644 --- a/frontend/src/types/type-user.ts +++ b/frontend/src/types/type-user.ts @@ -1,25 +1,20 @@ -import { DegreeConcentration, MajorsIndex, Program } from "./type-program"; - -export interface LanguagePlacement { - language: string; - level: number; -} +// import { DegreeConcentration } from "./type-program"; export interface Course { id: string; codes: string[]; title: string; description: string; - prereqs: string; + requirements: string; professors: string[]; distributions: string[]; - seasons: string[]; flags: string[]; credits: number; - colsem: boolean; - fysem: boolean; - sysem: boolean; + term: number; + is_colsem: boolean; + is_fysem: boolean; + is_sysem: boolean; } export interface StudentCourse { @@ -46,24 +41,28 @@ export interface StudentTermArrangement { senior: number[]; } +export interface MajorsIndex { + prog: string; // e.g. "CPSC" + deg: number; + conc: number; +} export interface StudentConc { - conc_majors_index: MajorsIndex; - user_status: number; - user_conc: DegreeConcentration; - user_conc_name: string; + name: string; + status: number; + // concentration: DegreeConcentration; + concentration_majors_index: MajorsIndex; selected_subreqs: Record; } export interface FYP { - languagePlacement: LanguagePlacement; + decl_list: StudentConc[]; studentCourses: StudentCourse[]; + languagePlacement: string; studentTermArrangement: StudentTermArrangement; - decl_list: StudentConc[]; } export interface User { name: string; netID: string; - onboard: boolean; FYP: FYP; } diff --git a/scrapers/coursetableScraper/coursetable.py b/scrapers/coursetableScraper/coursetable.py index 1d2bb07..1bde0dd 100644 --- a/scrapers/coursetableScraper/coursetable.py +++ b/scrapers/coursetableScraper/coursetable.py @@ -4,54 +4,56 @@ import os from supabase import create_client -def fetch_process_and_upload(season_code="202501"): - # Fetch data from API - url = f"https://api.coursetable.com/api/catalog/public/{season_code}" - print(f"Fetching course data from {url}...") - response = requests.get(url) - response.raise_for_status() - courses_data = response.json() - - # Process courses - print("Processing course data...") - processed_courses = [] - for course in courses_data: - processed_course = transform_course(course) - processed_courses.append(processed_course) - - processed_courses = processed_courses[:25] - - # Save locally (backup) - with open("results.json", "w", encoding="utf-8") as file: - json.dump(processed_courses, file, indent=2) - print(f"Saved {len(processed_courses)} courses to results.json") - - # Upload to Supabase - print("Connecting to Supabase...") + +def fetch_process_and_upload(terms: list[int]): + supabase_url = "https://cqonuujfvpucligwwgtq.supabase.co" supabase_key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNxb251dWpmdnB1Y2xpZ3d3Z3RxIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTczODUyMjg1MywiZXhwIjoyMDU0MDk4ODUzfQ.OtS4JpoFfW-T4YjksMW7SOeBZ1zSaf2EIBbevd09oaI" supabase = create_client(supabase_url, supabase_key) - # Upload in batches - batch_size = 50 - total_batches = (len(processed_courses) + batch_size - 1) // batch_size - - print(f"Uploading {len(processed_courses)} courses in {total_batches} batches...") - - for i in range(0, len(processed_courses), batch_size): - batch = processed_courses[i:i+batch_size] - batch_num = i // batch_size + 1 - - print(f"Uploading batch {batch_num}/{total_batches}...") + for term in terms: + term_str = str(term) + url = f"https://api.coursetable.com/api/catalog/public/{term_str}" + print(f"Fetching course data for term {term_str} from {url}...") try: - result = supabase.table("courses").insert(batch).execute() - print(f"✓ Batch {batch_num} uploaded successfully") - except Exception as e: - print(f"✗ Error with batch {batch_num}: {e}") + response = requests.get(url) + response.raise_for_status() + courses_data = response.json() + except requests.exceptions.RequestException as e: + print(f"✗ Failed to fetch data for term {term_str}: {e}") + continue + + print(f"Processing course data for term {term_str}...") + processed_courses = [transform_course(course) for course in courses_data[:25]] + + # Save locally (backup) + file_name = f"results_{term_str}.json" + with open(file_name, "w", encoding="utf-8") as file: + json.dump(processed_courses, file, indent=2) + print(f"✓ Saved {len(processed_courses)} courses to {file_name}") + + # Upload to Supabase + batch_size = 50 + total_batches = (len(processed_courses) + batch_size - 1) // batch_size + + print(f"Uploading {len(processed_courses)} courses for term {term_str} in {total_batches} batches...") + + for i in range(0, len(processed_courses), batch_size): + batch = processed_courses[i:i+batch_size] + batch_num = i // batch_size + 1 + + print(f"Uploading batch {batch_num}/{total_batches} for term {term_str}...") + + try: + result = supabase.table("courses").upsert(batch).execute() + print(f"✓ Batch {batch_num} for term {term_str} uploaded successfully") + except Exception as e: + print(f"✗ Error with batch {batch_num} for term {term_str}: {e}") - print("Process complete!") + print("✅ All terms processed successfully!") + def transform_course(course): # Extract course flags @@ -96,7 +98,7 @@ def transform_course(course): "distributions": distributions, "credits": credits, # This will now preserve decimal values "requirements": course.get("requirements"), - "season_code": course.get("season_code"), + "term": course.get("season_code"), "colsem": course.get("colsem", False), "fysem": course.get("fysem", False), "sysem": course.get("sysem", False) @@ -106,8 +108,8 @@ def transform_course(course): try: print("Course Data to Supabase Uploader") print("--------------------------------") - season = input("Enter season code (default 202501 for Spring 2025): ") or "202501" - fetch_process_and_upload(season) + terms = [202403, 202501] + fetch_process_and_upload(terms) except KeyboardInterrupt: print("\nProcess cancelled by user") except Exception as e: diff --git a/scrapers/coursetableScraper/results_202403.json b/scrapers/coursetableScraper/results_202403.json new file mode 100644 index 0000000..d6c3751 --- /dev/null +++ b/scrapers/coursetableScraper/results_202403.json @@ -0,0 +1,544 @@ +[ + { + "course_id": 240316045, + "title": "Project Course", + "description": "Project Course", + "professors": [ + "Daniel Esty" + ], + "codes": [ + "ENV 1180" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240311041, + "title": "The Archaeology of Trade and Exchange", + "description": "This seminar will focus on archaeological approaches to exchange and trade. As background, we will review some of the principal theories of exchange from anthropology and sociology, such as those of Mauss, Malinowski and Polanyi. The role of trade and exchange in different kinds of societies will examined by contextualizing these transactions within specific cultural configurations and considering the nature of production and consumption as they relate to movement of these goods. We will consider methods and models that have been used to analyze regions of interaction at different spatial scales and the theoretical arguments about the social impact of inter-regional and intra-regional interactions involving the transfer of goods, including approaches such as world systems, unequal development and globalization. In addition, we will examine the ways that have been utilized in archaeology to identify different kinds of exchange systems, often through analogies to well documented ethnographic and historic cases. Finally, we will consider the range of techniques that have been employed in order to track the movement of goods across space. These sourcing techniques will be evaluated in terms of their advantages and disadvantages from an archaeological perspective, and how the best technical analyses may vary according to the nature of natural or cultural materials under consideration (ceramics, volcanic stone, metals, etc.). The theme for this year\u2019s seminar is obsidian so students should select some aspect of obsidian research for their final paper and presentation.", + "professors": [ + "Richard Burger" + ], + "codes": [ + "ARCG 353", + "ANTH 353", + "ANTH 756", + "ARCG 756" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240311722, + "title": "Principles of Chemical Engineering and Process\u00a0Modeling", + "description": "Analysis of the transport and reactions of chemical species as applied to problems in chemical, biochemical, and environmental systems. Emphasis on the interpretation of laboratory experiments, mathematical modeling, and dimensional analysis. Lectures include classroom demonstrations.", + "professors": [ + "Peijun Guo" + ], + "codes": [ + "CENG 210", + "ENVE 210" + ], + "flags": [], + "distributions": [ + "Sc", + "QR" + ], + "credits": 1, + "requirements": "Prerequisite: MATH 115 or permission of instructor.", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240315412, + "title": "Global Korean Cinema", + "description": "In recent times, world cinema has witnessed the rise of South Korean cinema as an alternative to Hollywood and includes many distinguished directors such as Park Chan-wook, Lee Chang-dong, Kim Ki-duk, and Bong Joon-ho. This course explores the Korean film history and aesthetics from its colonial days (1910-1945) to the hallyu era (2001-present), and also analyzes several key texts that are critical for understanding this field of study. How is Korean cinema shaped by (re)interpretations of history and society? How do we understand Korean cinema vis-\u00e0-vis the public memories of the Korean War, industrialization, social movements, economic development, and globalization? And how do aesthetics and storytelling in Korean cinema contribute to its popularity among local spectators and to its globality in shaping the contours of world cinema? By deeply inquiring into such questions, students learn how to critically view, think about, and write about film. Primary texts include literature and film. All films are screened with English subtitles.", + "professors": [ + "Tian Li" + ], + "codes": [ + "EALL 297", + "EAST 300", + "FILM 342" + ], + "flags": [], + "distributions": [ + "Hu" + ], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240313325, + "title": "Shakespeare and Popular Culture", + "description": "How and why did Shakespeare become \"popular\"? Why is he still part of popular culture today? In this transhistorical and interdisciplinary course, we chart the history of Shakespeare\u2019s celebrity, from the first publication of his works to their first adaptations in the Restoration, from Garrick\u2019s Shakespeare Jubilee to the preservation of the Shakespeare Birthplace that he put on the map, from the recreation of the Globe Theatre to the role of Shakespeare in our contemporary cultural imagination. We read\u00a0Romeo and Juliet,\u00a0Hamlet, and\u00a0Macbeth\u00a0alongside a wide range of adaptations and cultural objects they inspire, using television, film, graphic novels, short stories, advertising, toys and souvenirs, and even tumblr poetry to consider how Shakespeare\u2019s legacy evolves to meet the needs of changing eras. By the end of the course, we curate a collection of contemporary Shakespeariana to consider what Shakespeare means to our popular imagination.", + "professors": [ + "Nicole Sheriko" + ], + "codes": [ + "ENGL 216" + ], + "flags": [ + "YC ENGL Junior Seminar", + "YC ENGL Renaissance", + "YC THST Histories" + ], + "distributions": [ + "Hu", + "WR" + ], + "credits": 1, + "requirements": "Not open to students who took ENGL 012.", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240312962, + "title": "Approaches to International Security", + "description": "Introduction to major approaches and central topics in the field of international security, with primary focus on the principal man-made threats to human security: the use of violence among and within states, both by state and non-state actors.", + "professors": [], + "codes": [ + "GLBL 275" + ], + "flags": [], + "distributions": [ + "So" + ], + "credits": 0, + "requirements": "Priority to Global Affairs majors. Non-majors require permission of the instructor.", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240310136, + "title": "Database Systems", + "description": "Introduction to database systems. Data modeling. The relational model and the SQL query language. Relational database design, integrity constraints, functional dependencies, and normal forms. Object-oriented databases. Database data structures: files, B-trees, hash indexes.", + "professors": [ + "Avi Silberschatz" + ], + "codes": [ + "CPSC 437", + "CPSC 537" + ], + "flags": [], + "distributions": [ + "QR" + ], + "credits": 1, + "requirements": "After CPSC 223.", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240312331, + "title": "What Was Reading?", + "description": "This course takes a long and curious view of the history of reading, using primary sources, material objects, historical records, and contemporary debates to unsettle our assumptions about what reading is and does. How have ideas about the meaning and purpose of reading changed over time? What methods or goals have fallen out of favor, and which continue to shape our ideologies of reading today? What relation is there between the reading we do in a Yale English class, and the reading we do on the beach, or at synagogue, or online\u2015and where do those different sorts of reading come from? The syllabus focuses on early modern English literature, but it also engages ongoing debates about reading in the present, seeking both to link them to and distinguish them from earlier controversies. For instance, a unit on reading as religion raises questions about the morally improving (or morally destabilizing) effects of scriptural interpretation that then haunt later debates about the merits and limitations of anti-racist reading, as James Baldwin argues; similarly, early arguments about the effeminating influence of certain books--especially those aimed at women or young readers--give rise to assumptions about gender and genre that still shape our ambivalence toward reading for pleasure. As we explore these older efforts to shape, inform, regulate, or liberate reading, we\u2019ll also experiment with our own readerly practices, using forgotten or neglected forms like the commonplace book, the moral commentary, or the meditation as foils to the more usual modes of academic writing.", + "professors": [ + "Catherine Nicholson" + ], + "codes": [ + "ENGL 229" + ], + "flags": [ + "YC CPLT Theory", + "YC ENGL Junior Seminar", + "YC ENGL Renaissance" + ], + "distributions": [ + "Hu", + "WR" + ], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240314316, + "title": "Independent Project I", + "description": "By arrangement with faculty.", + "professors": [ + "Lin Zhong" + ], + "codes": [ + "CPSC 690" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240310263, + "title": "Sound/Image Practice", + "description": "We start from the assumption that sound is actually the \u2018secret-sauce\u2019 in the film/videomaking process. Often overlooked\u2013or at least neglected, sound is a potent tool to advance the logic of a film or video and even more, to enhance the emotional patina and immersive engagement of a film or video. Sound becomes an accessible portal to the perhaps overlooked not-quite-conscious realm of the film/video experience. While we certainly read some theory/history of sound, this is primarily a class of making. The first 7 weeks include videomaking exercises designed to highlight specific challenges in sound for picture. The core concern is with conceptual development in the myriad ways that sound and picture work together. There is no genre or mode preference in this class. Fiction, non-fiction, experimental, animation, game, tiktok, anything is okay. For the second half of the semester, each student (or collaborative small group\u2013with permission) design, shoot, edit, and mix a short (3-5min) video of their own design\u2013a video that demonstrates attention and developing sophistication in the use of sound with picture, as well as in how to design visual shots and temporal structures (editing) with sound in mind. The visual and auditory aspects of any video are entangled in such a way that contribute (when blended with the audience\u2019s imagination and memory) to the formation of the Sound/Image in the audience member\u2019s minds.", + "professors": [ + "Leighton Pierce" + ], + "codes": [ + "FILM 460" + ], + "flags": [ + "YC FILM Production" + ], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240310267, + "title": "The Senior Essay", + "description": "An independent writing and research project. A prospectus signed by the student's adviser must be submitted to the director of undergraduate studies by the end of the second week of the term in which the essay project is to commence. A rough draft must be submitted to the adviser and the director of undergraduate studies approximately one month before the final draft is due. Essays are normally thirty-five pages long (one term) or fifty pages (two terms).", + "professors": [ + "John Peters" + ], + "codes": [ + "FILM 491" + ], + "flags": [ + "YC FILM Critical Studies" + ], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240310726, + "title": "Socialist '80s: Aesthetics of Reform in China and the Soviet Union", + "description": "This course offers an interdisciplinary introduction to the study of the complex cultural and political paradigms of late socialism from a transnational perspective by focusing on the literature, cinema, and popular culture of the Soviet Union and China in 1980s. How were intellectual and everyday life in the Soviet Union and China distinct from and similar to that of the West of the same era? How do we parse \"the cultural logic of late socialism?\" What can today\u2019s America learn from it? Examining two major socialist cultures together in a global context, this course queries the ethnographic, ideological, and socio-economic constituents of late socialism. Students analyze cultural materials in the context of Soviet and Chinese history. Along the way, we explore themes of identity, nationalism, globalization, capitalism, and the Cold War.\u00a0Students with knowledge of Russian and Chinese are encouraged to read in original languages. All readings are\u00a0available in English.", + "professors": [ + "Jinyi Chu" + ], + "codes": [ + "LITR 303", + "RUSS 605", + "RSEE 316", + "CPLT 612", + "EALL 288", + "RUSS 316", + "RSEE 605", + "EALL 588", + "EAST 316", + "EAST 616" + ], + "flags": [ + "YC GLBL Elective" + ], + "distributions": [ + "Hu", + "WR" + ], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240310839, + "title": "Statistical Case Studies", + "description": "Statistical analysis of a variety of statistical problems using real data. Emphasis on methods of choosing data, acquiring data, assessing data quality, and the issues posed by extremely large data sets. Extensive computations using R.", + "professors": [ + "Brian Macdonald" + ], + "codes": [ + "S&DS 625" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "Enrollment limited; requires permission of the instructor.", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240311133, + "title": "Jesus to Muhammad: Ancient Christianity to the Rise of Islam", + "description": "The history of Christianity and the development of Western culture from Jesus to the early Middle Ages. The creation of orthodoxy and heresy; Christian religious practice; philosophy and theology; politics and society; gender; Christian literature in its various forms, up to and including the early Islamic period.", + "professors": [ + "Stephen Davis" + ], + "codes": [ + "HUMS 129", + "HIST 159", + "NELC 158", + "RLST 158", + "CLCV 129", + "RLST 649" + ], + "flags": [], + "distributions": [ + "Hu" + ], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240311540, + "title": "Independent Study", + "description": "By arrangement with faculty and with approval of the DGS.", + "professors": [], + "codes": [ + "EAST 910" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240311660, + "title": "Readings in Anthropology", + "description": "For students who wish to investigate an area of anthropology not covered by regular departmental offerings. The project must terminate with at least a term paper or its equivalent. No student may take more than two terms for credit. To apply for admission, a student should present a prospectus and bibliography to the director of undergraduate studies no later than the third week of the term. Written approval from the faculty member who will direct the student's reading and writing must accompany the prospectus.", + "professors": [ + "William Honeychurch" + ], + "codes": [ + "ANTH 472" + ], + "flags": [ + "YC ANTH Archaeology", + "YC ANTH Biological", + "YC ANTH Sociocultural" + ], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240311827, + "title": "Modern Physical Measurement", + "description": "A\u00a0two-term sequence of experiments in classical and modern physics for students who plan to major in Physics. In the first term, the basic principles of mechanics, electricity, and magnetism are illustrated in experiments designed to make use of computer data handling and teach error analysis. In the second term, students plan and carry out experiments illustrating aspects of wave and quantum phenomena and of atomic, solid state, and nuclear physics using modern instrumentation.", + "professors": [ + "David Moore", + "Mehdi Ghiassi-Nejad", + "Reina Maruyama", + "Stephen Irons" + ], + "codes": [ + "PHYS 206L" + ], + "flags": [], + "distributions": [ + "Sc" + ], + "credits": 0.5, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240312047, + "title": "The Senior Essay", + "description": "All senior History majors should attend the mandatory senior essay meeting in early September at a time and location to be announced in the online Senior Essay Handbook.\nThe senior essay is a required one- or two-term independent research project conducted under the guidance of a faculty adviser. As a significant work of primary-source research, it serves as the capstone project of the History major. Students writing the one-term senior essay enroll in HIST 497 (see description), not HIST 495 and 496. The two-term essay takes the form of a substantial article, not longer than 12,500 words (approximately forty to fifty double-spaced typewritten pages). This is a maximum limit; there is no minimum requirement. Length will vary according to the topic and the historical techniques employed. Students writing the two-term senior essay who expect to graduate in May enroll in HIST 495 during the fall term and complete their essays in HIST 496 in the spring term. December graduates enroll in HIST 495 in the spring term and complete their essays in HIST 496 during the following fall term; students planning to begin their essay in the spring term should notify the senior essay director by early December. Each student majoring in History must present a completed Statement of Intention, signed by a department member who has agreed to serve as adviser, to the History Department Undergraduate Registrar by the dates indicated in the Senior Essay Handbook. Blank statement forms are available from the History Undergraduate Registrar and in the Senior Essay handbook. Students enrolled in HIST 495 submit to the administrator in 237 HGS a two-to-three-page analysis of a single primary source, a draft bibliographic essay, and at least ten pages of the essay by the deadlines listed in the Senior Essay Handbook. Those who meet these requirements receive a temporary grade of SAT for the fall term, which will be changed to the grade received by the essay upon its completion. Failure to meet any requirement may result in the student\u2019s being asked to withdraw from HIST 495. Students enrolled in HIST 496 must submit a completed essay to 211 HGS no later than 5 p.m. on the dates indicated in the Senior Essay Handbook. Essays submitted after 5 p.m. will be considered as having been turned in on the following day. If the essay is submitted late without an excuse from the student's residential college dean, the penalty is one letter grade for the first day and one-half letter grade for each of the next two days past the deadline. No essay that would otherwise pass will be failed because it is late, but late essays will not be considered for departmental or Yale College prizes. All senior departmental essays will be judged by members of the faculty other than the adviser. In order to graduate from Yale College, a student majoring in History must achieve a passing grade on the departmental essay.", + "professors": [ + "Anne Eller" + ], + "codes": [ + "HIST 495" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240312048, + "title": "The Senior Essay", + "description": "All senior History majors should attend the mandatory senior essay meeting in early September at a time and location to be announced in the online Senior Essay Handbook.\nThe senior essay is a required one- or two-term independent research project conducted under the guidance of a faculty adviser. As a significant work of primary-source research, it serves as the capstone project of the History major. Students writing the one-term senior essay enroll in HIST 497 (see description), not HIST 495 and 496. The two-term essay takes the form of a substantial article, not longer than 12,500 words (approximately forty to fifty double-spaced typewritten pages). This is a maximum limit; there is no minimum requirement. Length will vary according to the topic and the historical techniques employed. Students writing the two-term senior essay who expect to graduate in May enroll in HIST 495 during the fall term and complete their essays in HIST 496 in the spring term. December graduates enroll in HIST 495 in the spring term and complete their essays in HIST 496 during the following fall term; students planning to begin their essay in the spring term should notify the senior essay director by early December. Each student majoring in History must present a completed Statement of Intention, signed by a department member who has agreed to serve as adviser, to the History Department Undergraduate Registrar by the dates indicated in the Senior Essay Handbook. Blank statement forms are available from the History Undergraduate Registrar and in the Senior Essay handbook. Students enrolled in HIST 495 submit to the administrator in 237 HGS a two-to-three-page analysis of a single primary source, a draft bibliographic essay, and at least ten pages of the essay by the deadlines listed in the Senior Essay Handbook. Those who meet these requirements receive a temporary grade of SAT for the fall term, which will be changed to the grade received by the essay upon its completion. Failure to meet any requirement may result in the student\u2019s being asked to withdraw from HIST 495. Students enrolled in HIST 496 must submit a completed essay to 211 HGS no later than 5 p.m. on the dates indicated in the Senior Essay Handbook. Essays submitted after 5 p.m. will be considered as having been turned in on the following day. If the essay is submitted late without an excuse from the student's residential college dean, the penalty is one letter grade for the first day and one-half letter grade for each of the next two days past the deadline. No essay that would otherwise pass will be failed because it is late, but late essays will not be considered for departmental or Yale College prizes. All senior departmental essays will be judged by members of the faculty other than the adviser. In order to graduate from Yale College, a student majoring in History must achieve a passing grade on the departmental essay.", + "professors": [ + "Anne Eller" + ], + "codes": [ + "HIST 496" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240312083, + "title": "Research and Senior Thesis", + "description": "Two terms of independent library, laboratory, field, or modeling-based research under faculty supervision. To register for this course, each student must submit a written plan of study, approved by a faculty adviser, to the director of undergraduate studies by the start of the senior year.\u00a0The plan requires approval of the full EPS faculty.", + "professors": [ + "Pincelli Hull" + ], + "codes": [ + "EPS 490" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240312084, + "title": "Research and Senior Thesis", + "description": "Two terms of independent library, laboratory, field, or modeling-based research under faculty supervision. To register for this course, each student must submit a written plan of study, approved by a faculty adviser, to the director of undergraduate studies by the start of the senior year.\u00a0The plan requires approval of the full EPS faculty.", + "professors": [ + "Pincelli Hull" + ], + "codes": [ + "EPS 491" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240312214, + "title": "Frontiers of Public Health", + "description": "This course is designed to expose students to the breadth of public health and is required of M.S. and Ph.D. students who do not have prior degrees in public health. It explores the major public health achievements in the last century in order to provide students with a conceptual interdisciplinary framework by which effective interventions are developed and implemented. Case studies and discussions examine the advances across public health disciplines including epidemiology and biostatistics, environmental and behavioral sciences, and health policy and management services that led to these major public health achievements. The course examines global and national trends in the burden of disease and underlying determinants of disease, which pose new challenges; and it covers new approaches that are on the forefront of addressing current and future public health needs.", + "professors": [ + "Jeannette Ickovics" + ], + "codes": [ + "EPH 537E" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "Open only to students enrolled in the Executive Online M.P.H. Program. Not open to auditors.", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240312531, + "title": "Statistical Case Studies", + "description": "Statistical analysis of a variety of statistical problems using real data. Emphasis on methods of choosing data, acquiring data, assessing data quality, and the issues posed by extremely large data sets. Extensive computations using R statistical software.", + "professors": [ + "Jay Emerson" + ], + "codes": [ + "S&DS 425", + "S&DS 625" + ], + "flags": [ + "YC GLBL Addtl Methods Course" + ], + "distributions": [ + "QR" + ], + "credits": 1, + "requirements": "Prerequisites: S&DS 361, and prior course work in probability, statistics, and data analysis (e.g. 363, 365, 220, 230, etc., equivalent courses, or equivalent research/internship experience).\u00a0 Enrollment limited; requires permission of the instructor.\u00a0", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240313588, + "title": "Senior Capstone Project: AI, Emerging Tech and the Midd", + "description": "Students work in small task-force groups and complete a one-term public policy project under the guidance of a faculty member. Clients for the projects are drawn from government agencies, nongovernmental organizations and nonprofit groups, and private sector organizations in the United States and abroad. Projects and clients vary from year to year.\nFulfills the capstone project requirement for the Global Affairs major.", + "professors": [ + "Roland McKay" + ], + "codes": [ + "GLBL 499" + ], + "flags": [ + "PH Glbl Hlth Devlpmt/PolEcon" + ], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 240313622, + "title": "Structural and Functional Organization of the Human Nervous System", + "description": "An integrative overview of the structure and function of the human brain as it pertains to major neurological and psychiatric disorders. Neuroanatomy, neurophysiology, and clinical correlations are interrelated to provide essential background in the neurosciences. Lectures in neurocytology and neuroanatomy survey neuronal organization in the human brain, with emphasis on long fiber tracts related to clinical neurology. Lectures in neurophysiology cover various aspects of neural function at the cellular and systems levels, with a strong emphasis on the mammalian nervous system. Clinical correlations consist of sessions applying basic science principles to understanding pathophysiology in the context of patients. Seven three-hour laboratory sessions are coordinated with lectures throughout the course to provide an understanding of the structural basis of function and disease. Case-based conference sections provide an opportunity to integrate and apply the information learned about the structure and function of the nervous system in the rest of the course to solving a focused clinical problem in a journal club format. Variable class schedule; contact course instructors. This course is offered to graduate and M.D./Ph.D. students only and cannot be audited.", + "professors": [ + "Thomas Biederer" + ], + "codes": [ + "INP 510" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202403", + "colsem": false, + "fysem": false, + "sysem": false + } +] \ No newline at end of file diff --git a/scrapers/coursetableScraper/results_202501.json b/scrapers/coursetableScraper/results_202501.json new file mode 100644 index 0000000..1c87877 --- /dev/null +++ b/scrapers/coursetableScraper/results_202501.json @@ -0,0 +1,535 @@ +[ + { + "course_id": 250121861, + "title": "Social and Cultural Factors in Mental Health and Illness", + "description": "This course provides an introduction to mental health and illness with a focus on the complex interplay between risk and protective factors and social and cultural influences on mental health status. We examine the role of social and cultural factors in the etiology, course, and treatment of substance misuse; depressive, anxiety, and psychotic disorders; and some of the severe behavioral disorders of childhood. The social consequences of mental illness such as stigma, isolation, and barriers to care are explored, and their impact on access to care and recovery considered. The effectiveness of the current system of services and the role of public health and public health professionals in mental health promotion are discussed.", + "professors": [], + "codes": [ + "PSYC 576" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250125120, + "title": "Research", + "description": "Individual research for Ph.D. degree candidates in the Department of Chemistry, under the direct supervision of one or more faculty members.", + "professors": [ + "Tianyu Zhu" + ], + "codes": [ + "CHEM 990" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250121135, + "title": "Junior Seminar", + "description": "Ongoing visual projects addressed in relation to historical and contemporary issues. Readings, slide presentations, critiques by School of Art faculty, and gallery and museum visits. Critiques address all four areas of study in the Art major.", + "professors": [ + "Elle Perez" + ], + "codes": [ + "ART 395" + ], + "flags": [], + "distributions": [ + "Hu" + ], + "credits": 1, + "requirements": "Prerequisite: at least four courses in Art.", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250121016, + "title": "Topics: Events, Distributivity, Durational Modifiers", + "description": "This course bridges introductory courses (LING 263, LING 264) and advanced seminars in semantics. It explores selected topics in some detail, allowing students to appreciate the nuances of semantic argumentation while at the same time emphasizing the foundational issues involved.\u00a0The goal of this course is to allow students, within a structured format, to become comfortable engaging with open-ended problems and to gain confidence in proposing original solutions to such problems.\u00a0Topics vary across semesters.", + "professors": [ + "Veneeta Dayal", + "Simon Charlow" + ], + "codes": [ + "LING 291", + "LING 691" + ], + "flags": [ + "YC LING Depth Semntcs/Pragmat", + "YC LING Elective", + "YC LING Intermediate Courses" + ], + "distributions": [ + "So" + ], + "credits": 1, + "requirements": "Prerequisite: LING 263 / LING 663 or permission of Instructor", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250123229, + "title": "Yale Concert Band", + "description": "The Yale Concert Band, a group of 45-60 wind, brass, and percussion players, embraces the aesthetics of the traditional wind band and the contemporary experimental ensemble. Our repertoire consists of a panoply of wind band classics; premieres by and commissions of Yale students, faculty and established world-class composers; and the newest wind band literature that incorporates electro-acoustic sounds, folk/rock/hip hop music, soloists, and theatrical trappings. The Yale Concert Band regularly presents concerts to benefit causes and organizations, ranging from benefit concerts to support the work of New Haven\u2019s IRIS (Integrated Refugee and Immigrant Services (2017, 2018, 2019); to provide aid to the relief efforts after Hurricane Katrina (2005), floods in Myanmar (2007), tornadoes in the American midwest (2007), the earthquake in Haiti (2010), the tsunami in Japan (2011), and West African Ebola recovery efforts (2016).\u00a0 In 1959, the Yale Concert Band became the first university band to produce an international concert tour, and, since then, has appeared in concerts in Japan, South Africa, Swaziland, Mexico, Brazil, Bermuda, Russia, Finland, the Czech Republic, Austria, Ireland, England, France, Italy, Denmark, Germany, Holland, Belgium, Lithuania, Latvia, Estonia, Ghana, Haiti, Greece, Australia, and Spain. This course cannot be applied toward the 36-course-credit requirement for the Yale bachelor's degree.", + "professors": [ + "Thomas Duffy" + ], + "codes": [ + "MUSI 190" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "By audition at the beginning of the academic year or by permission of instructor.", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250124916, + "title": "Exploring and Understanding White Collar Crime", + "description": "This course examines the many aspects of white collar crime; perjury, obstruction of justice, corporate crimes, Ponzi Schemes, insider trading, money laundering bribery and political corruption. The course explores how white collar crime, once virtually ignored by law enforcement has become a major focus of federal and state investigative agencies with massive resources allocated toward combatting it. The seminar examines the root causes of white collar crime as well as its pervasiveness in every day life.\u00a0 Specific cases of white collar defendants, both individuals and corporations that have profoundly impacted business, law, science, healthcare and other disciplines are examined.", + "professors": [ + "Bradley Simon" + ], + "codes": [ + "CSMY 220" + ], + "flags": [ + "YC College Seminar" + ], + "distributions": [], + "credits": 1, + "requirements": "Students may enroll in no more than 1 RCS for credit in a given term.", + "term": "202501", + "colsem": true, + "fysem": false, + "sysem": false + }, + { + "course_id": 250126581, + "title": "EL IntMedHematology2WK", + "description": "", + "professors": [], + "codes": [ + "MD 3090" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250126701, + "title": "Global Health Elective Ghana", + "description": "", + "professors": [], + "codes": [ + "MD 301" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250120660, + "title": "Methods in Gender and Sexuality Studies", + "description": "This seminar explores\u00a0the dynamics of power and knowledge, the ethics of representation and accountability, and the nexus between disciplinarity and interdisciplinarity. It is designed for graduate students developing research projects that engage feminist, queer, postcolonial, and critical race methodologies, among others. The course adopts an epistemological approach that centers \"encounter\" across geopolitical scales and multiple disciplinary fronts in the humanities and social sciences. It posits that research methods, regardless of their origin, can adopt feminist, queer, decolonial/postcolonial, and critical race perspectives and potentially serve counter-disciplinary purposes. Although we cover a broad spectrum of methods\u2014ranging from ethnographic, historiographic/archival, and geographic, to literary, media, and textual analysis, cultural studies, and political theory\u2014our work does not unfold as a practicum. Instead of experimenting with a predefined \"toolkit,\" students critically engage book-length works that demonstrate counter-disciplinary methodologies, reflecting hermeneutically on how method and theory relate in these texts by drawing on Foucault's framework of \"the archaeology of knowledge.\"", + "professors": [ + "Eda Pepi" + ], + "codes": [ + "AMST 798", + "WGSS 800" + ], + "flags": [ + "YC Ethnography Methods" + ], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250121028, + "title": "Special Investigations", + "description": "Directed research by arrangement with individual faculty members and approved by the DGS. Students are expected to propose and complete a term-long research project. The culmination of the project is a presentation that fulfills the departmental requirement for the research qualifying event.", + "professors": [ + "Daisuke Nagai", + "Rona Ramos" + ], + "codes": [ + "PHYS 990" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250121423, + "title": "Topics in Biomedical Informatics and Data Science", + "description": "The course focuses on providing an introduction to common unifying themes that serve as the foundation for different areas of biomedical informatics, including clinical, neuro-, and genome informatics. The course is designed for students with basic computer experience and course work who plan to build databases and computational tools for use in biomedical research. Emphasis is on understanding basic principles underlying informatics approaches to interoperation among biomedical databases and software tools, standardized biomedical vocabularies and ontologies, biomedical natural language processing, modeling of biological systems, high-performance computation in biomedicine, and other related topics.", + "professors": [ + "Samah Jarad" + ], + "codes": [ + "BIS 550", + "CB&B 750", + "HSCI 5500" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250121441, + "title": "Topics in Biomedical Informatics and Data Science", + "description": "The course focuses on providing an introduction to common unifying themes that serve as the foundation for different areas of biomedical informatics, including clinical, neuro-, and genome informatics. The course is designed for students with significant computer experience and course work who plan to build databases and computational tools for use in biomedical research. Emphasis is on understanding basic principles underlying informatics approaches to interoperation among biomedical databases and software tools, standardized biomedical vocabularies and ontologies, biomedical natural language processing, modeling of biological systems, high-performance computation in biomedicine, and other related topics.", + "professors": [ + "Samah Jarad", + "Kei-Hoi Cheung" + ], + "codes": [ + "BIS 543E" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "Open only to students enrolled in the Executive Online M.P.H. Program. Not open to auditors.", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250122238, + "title": "Corporate Sustainability: Strategy and Management", + "description": "This survey course focuses on the policy and business logic for making environmental issues and sustainability a core focus of corporate strategy and management. Students are asked to analyze when and how sustainability leadership can translate into competitive advantage by helping to cut costs, reduce risk, drive growth, and promote brand identity and intangible value. The course seeks to provide students with an introduction to the range of sustainability issues and challenges that companies face in today\u2019s fast-changing marketplace. It introduces key corporate sustainability terms, concepts, tools, strategies, and frameworks based on the overarching theory that the traditional profit-maximizing mission of business (often called shareholder primacy) is giving way to a new vision of stakeholder responsibility that still seeks to provide good returns to the enterprise\u2019s owners but also acknowledges obligations to employees, suppliers, customers, communities, and society more broadly. The course combines lectures, case studies, and class discussions on management theory and tools, the legal and regulatory frameworks that shape the business-environment interface, and the evolving role of business in society. It explores how to deal with a world of diverse stakeholders, increasing transparency, and rising expectations related to corporate environmental, social, and governance (ESG) performance. Self-scheduled examination.", + "professors": [ + "Daniel Esty" + ], + "codes": [ + "ENV 807", + "MGT 688" + ], + "flags": [ + "YC ENRG Energy & Environment", + "YSE MEM B&E Core", + "YSE MEM IEGC Add'l Electives", + "YSE MEM IEGC Primary Electives" + ], + "distributions": [], + "credits": 0, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250122406, + "title": "What was Latinx Literature", + "description": "With the arrival of \"Latinx,\" the last decade was defined as a moment of rupture and break with traditional notions of latinidad. Artists and activists asserted refusal and historical reckoning as the mode of doing politics and aesthetics. Now, pessimistic about Latinx as a signifier of a unified political project, the generational tides have shifted to \"Latine.\" This seminar asks what is \"Latinx literature\" and why are the methods of \"Latinx studies\" considered revolutionary or disruptive? What ideas were rooted in prior generations of feminist and queer collectives that sustained life when the arrival of a decolonial future seemed forever deferred and withheld from reach? We examine contemporary artists alongside historical antecedents to reevaluate what literary and social forms can help us challenge a racialized, heteronormative conception of citizenship. One possibility is that Gloria Anzald\u00faa\u2014rightly critiqued for her relation to mestizaje \u2014might be helpful in this moment of growing nationalism and hostility towards migrants to think about other ways of organizing life aside borders and the nation. We read across a long and varied arc of creative expression to consider forms that endure amidst colonial duress. For example: the serial, montage, anthology, performance collective, and inter-linked storytelling. Artists up for discussion may include Natalie Diaz, John Rechy, and Jes\u00fas Col\u00f3n. Students will engage these works alongside theorists like Jos\u00e9 Esteban Mu\u00f1oz and Juana Mar\u00eda Rodr\u00edguez. Previously ENGL 331.", + "professors": [ + "Joseph Miranda" + ], + "codes": [ + "ENGL 4831", + "ER&M 268" + ], + "flags": [ + "YC ENGL 20th/21st Century", + "YC ENGL Senior Seminar" + ], + "distributions": [ + "Hu", + "WR" + ], + "credits": 1, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250122770, + "title": "Machine Learning for Economic Analysis", + "description": "Machine learning algorithms and their applications to economic analysis, specifically causal inference, learning, and game theory. Curse of dimensionality, model selection, and choice of tuning parameters from a computational and econometric perspective.", + "professors": [ + "Max Cytrynbaum" + ], + "codes": [ + "ECON 566" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250122954, + "title": "Machine Learning for Economic Analysis", + "description": "Machine learning algorithms and their applications to economic analysis, specifically causal inference, learning, and game theory. Curse of dimensionality, model selection, and choice of tuning parameters from a computational and econometric perspective.", + "professors": [ + "Max Cytrynbaum" + ], + "codes": [ + "ECON 428" + ], + "flags": [], + "distributions": [ + "So" + ], + "credits": 1, + "requirements": "Prerequisites: CPSC 100 or CPSC 112; and ECON 117 or ECON 136.", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250123384, + "title": "Secndry Instrmnt--: Secndry Instrmnt--PIANO", + "description": "2 credits per term. P/F. All students enrolled in secondary lessons can receive instruction in either voice or piano. In addition, YSM keyboard majors may take secondary organ or harpsichord, and YSM violinists may take secondary viola. Any other students who wish to take secondary lessons in any other instruments must petition the director of secondary lessons, Kyung Yu, by email (kyung.yu@yale.edu) no later than Aug. 30, 2021, for the fall term and Jan. 14, 2022, for the spring term. Students who are not conducting majors may take only one secondary instrument per term. YSM students who wish to take secondary lessons must register for the course and request a teacher using the online form for graduate students found at http://music.yale.edu/study/music-lessons; the availability of a secondary lessons teacher is not guaranteed until the form is received and a teacher assigned by the director of lessons. Secondary instruction in choral conducting and orchestral conducting is only available with permission of the instructor and requires as prerequisites MUS 565 for secondary instruction in choral conducting, and both MUS 529 and MUS 530 for secondary instruction in orchestral conducting. Students of the Yale Divinity School, School of Drama, and School of Art may also register as above for secondary lessons and will be charged $200 per term for these lessons. Questions may be emailed to the director, Kyung Yu (kyung.yu@yale.edu).", + "professors": [ + "Kyung Yu" + ], + "codes": [ + "MUS 541" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250123877, + "title": "The Senior Essay I", + "description": "Students wishing to undertake an independent senior essay in English must submit a proposal to the DUS in the previous term; deadlines and instructions are posted at https://english.yale.edu/undergraduate/courses/independent-study-courses. For one-term senior essays, the essay itself is due in the office of the director of undergraduate studies according to the following schedule: (1) end of the fourth week of classes: five to ten pages of writing and/or an annotated bibliography; (2) end of the ninth week of classes: a rough draft of the complete essay; (3) end of the last week of classes (fall term) or end of the next-to-last week of classes (spring term): the completed essay. Consult the director of undergraduate studies regarding the schedule for submission of the yearlong senior essay.", + "professors": [ + "Marcel Elias", + "Stefanie Markovits" + ], + "codes": [ + "ENGL 4100" + ], + "flags": [], + "distributions": [], + "credits": 1, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250124362, + "title": "Styles of Professional Prose: Writing about Legal Affairs", + "description": "A seminar and workshop in the conventions of good writing in a specific field. Each section focuses on one professional kind of writing and explores its distinctive features through a variety of written and oral assignments, in which students both analyze and practice writing in the field. Section topics, which change yearly, are listed at the beginning of each term on the English department website. This course may be repeated for credit in a section that treats a different genre or style of writing; may not be repeated for credit toward the major. ENGL 121 and ENGL 421 may not be taken for credit on the same topic.", + "professors": [ + "Lincoln Caplan" + ], + "codes": [ + "ENGL 1021" + ], + "flags": [], + "distributions": [ + "WR" + ], + "credits": 1, + "requirements": "Prerequisite: ENGL 114, 115, 120, or another writing-intensive course at Yale.", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250124828, + "title": "Abolition in the Americas", + "description": "This seminar examines histories of slavery's abolition in the Americas. It situates the end of slavery in the United States within hemispheric and transatlantic contexts, touching upon processes of abolition\u00a0in Antigua, Brazil, Colombia, Cuba, the Dominican Republic, Haiti, Jamaica, and more. The course approaches abolition as a historiographical problem, considering debates about its definition, its causes and consequences, its primary agents, its periodization, and its relation to other historical processes. Questions include: How have historians defined abolition? How have they periodized it? How have scholars variously characterized the forms it took and who was responsible for it? How have they differently understood the social, cultural, economic, legal, and political conditions that gave rise to abolition? How have historians agreed and disagreed upon its effects and its aftermath? How have they framed the relation between freedom and formal emancipation? How have the communicated the stakes of their accounts of abolition? The organization of the course is topical and loosely chronological. Readings address the origins of abolition in the Atlantic world, the Haitian Revolution, processes of gradual emancipation, the historical significance of Black abolitionists, the activism of women and children, the formation and contributions of antislavery movements, the practice of moral suasion, the question of violence and antislavery militantism, antislavery discourses of rights and sexual morals, the circulation of racial and climate science in abolitionist circles, enslaved people\u2019s practices of fugitivity, self-purchase, and revolt, the relation between capitalism and abolitionism, the Civil War, and the so-called \"last abolition\" of slavery in Brazil.", + "professors": [ + "Caleb Knapp" + ], + "codes": [ + "HIST 124J" + ], + "flags": [ + "YC HIST Cultural History", + "YC HIST Departmental Seminars", + "YC HIST Empires & Colonialism", + "YC HIST Ideas & Intellectuals", + "YC HIST Pltcs, Law & Govt", + "YC HIST Race Gender&Sexuality", + "YC HIST Soc Chng&Social Mvmnt", + "YC HIST United States", + "YC HIST War & Society", + "YC HSHM Colonial Know & Power", + "YC HSHM Gender, Reprod & Body" + ], + "distributions": [ + "Hu", + "WR" + ], + "credits": 1, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250125902, + "title": "Clinical Practice II for Global Health Track", + "description": "This clinical application course for students in the global health track provides opportunities to develop advanced nursing skills with a range of global populations within the students\u2019 areas of specialization. While in clinical settings, students develop skills in assessment and management of acute and chronic conditions using evidence-based patient management strategies in accordance with the cultural beliefs and practices of populations of immigrants, refugees, American Indians, and Alaskan native and rural residents. These experiences may take place in YSN-approved U.S. or international settings. Additional experiences with local resettlement organizations such as Integrated Refugee and Immigrant Services (IRIS) and Connecticut Institute for Refugees and Immigrants (CIRI) are also available. These experiences may include developing and presenting education programs to groups of refugees, immigrants, or asylum seekers; creating training materials for the resettlement agencies; or serving as a cultural companion or health navigator for newly arrived families. Required of all students pursuing the global health track during the fall term of their second specialty year. Thirty hours of face-to-face interactions either in a health care setting or in an alternative setting, and one hour per week of clinical conference. Taken after NURS 6230.", + "professors": [ + "Sandy Cayo" + ], + "codes": [ + "NURS 6240" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250126137, + "title": "Independent Course Work", + "description": "Program to be determined with a faculty adviser of the student\u2019s choice and submitted, with the endorsement of the study area coordinators, to the Rules Committee for confirmation of the student\u2019s eligibility under the rules. (See the School\u2019s Academic Rules and Regulations.)", + "professors": [ + "Brennan Buck" + ], + "codes": [ + "ARCH 2299" + ], + "flags": [], + "distributions": [], + "credits": 0, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250120297, + "title": "American Political Institutions", + "description": "The origins and development of American political institutions, especially in relation to constitutional choice and the agency of persons seeking freedom, equality, and self-governing capabilities as a driver of constitutional change. Key concepts include: American federalism, compound republic, citizenship, social movements, racial justice, and nonviolence.", + "professors": [ + "Michael Fotos" + ], + "codes": [ + "PLSC 256", + "AFAM 177", + "EP&E 248" + ], + "flags": [ + "YC EP&E Politics Core" + ], + "distributions": [ + "So", + "WR" + ], + "credits": 1, + "requirements": "", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250120530, + "title": "Elementary Modern Standard Arabic II", + "description": "Continuation of ARBC 110.", + "professors": [ + "Muhammad Aziz" + ], + "codes": [ + "ARBC 120", + "ARBC 501" + ], + "flags": [], + "distributions": [ + "L2" + ], + "credits": 1.5, + "requirements": "Prerequisite: ARBC 110 or\u00a0requisite score on a\u00a0placement test.", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + }, + { + "course_id": 250121598, + "title": "Chinese for Current Affairs", + "description": "Advanced language course with a focus on speaking and writing in formal styles. Current affairs are used as a vehicle to help students learn advanced vocabulary, idiomatic expressions, complex sentence structures, news writing styles and formal stylistic register. Materials include texts and videos selected from news media worldwide to improve students\u2019 language proficiency for sophisticated communications on a wide range of topics.", + "professors": [ + "Jingjing Ao" + ], + "codes": [ + "CHNS 167" + ], + "flags": [], + "distributions": [ + "L5" + ], + "credits": 1, + "requirements": "After CHNS 153, or 157, or 159,\u00a0 or equivalent.", + "term": "202501", + "colsem": false, + "fysem": false, + "sysem": false + } +] \ No newline at end of file From df79f3c80db051e23f7fd5421131dc1e79655feb Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Thu, 20 Mar 2025 12:53:25 -0700 Subject: [PATCH 37/38] prog api load render --- frontend/src/app/api/programs/route.ts | 2 - frontend/src/app/api/programs/transformers.ts | 2 +- frontend/src/app/graduation/page.tsx | 42 +---------- .../majors/course-icon/MajorsCourseIcon.tsx | 12 ++-- frontend/src/app/majors/metadata/Metadata.tsx | 41 +++++------ frontend/src/app/majors/page.tsx | 10 ++- .../app/majors/requirements/Requirements.tsx | 69 ++++++++++--------- frontend/src/context/ProgramProvider.tsx | 67 +++++++++--------- frontend/src/types/type-program.ts | 9 --- frontend/src/types/type-user.ts | 6 ++ 10 files changed, 109 insertions(+), 151 deletions(-) diff --git a/frontend/src/app/api/programs/route.ts b/frontend/src/app/api/programs/route.ts index 18ba359..f37bfd0 100644 --- a/frontend/src/app/api/programs/route.ts +++ b/frontend/src/app/api/programs/route.ts @@ -13,8 +13,6 @@ export async function GET() { // Create program dictionary const programDict = createProgramDict(transformedPrograms); - - console.log(programDict); return NextResponse.json(programDict); } catch (error) { diff --git a/frontend/src/app/api/programs/transformers.ts b/frontend/src/app/api/programs/transformers.ts index 9194905..2c64c13 100644 --- a/frontend/src/app/api/programs/transformers.ts +++ b/frontend/src/app/api/programs/transformers.ts @@ -60,7 +60,7 @@ export function transformRequirement(requirementData: any, index: number): Requi description: requirementData.description || "", courses_required_count: requirementData.courses_required_count || 0, subreqs_required_count: requirementData.subreqs_required_count || 0, - checkbox: requirementData.checkbox, + checkbox: requirementData.checkbox || false, subrequirements: subrequirements.map( (item: any, i: number) => transformSubrequirement(item.data, item.index || i) ), diff --git a/frontend/src/app/graduation/page.tsx b/frontend/src/app/graduation/page.tsx index ba6581d..9dbb85b 100644 --- a/frontend/src/app/graduation/page.tsx +++ b/frontend/src/app/graduation/page.tsx @@ -4,50 +4,10 @@ import { useState, useEffect } from "react"; import Style from "./Graduation.module.css"; import NavBar from "@/components/navbar/NavBar"; -import { Program } from "@prisma/client"; - -// type Course = { -// course_id: number; -// codes: string; -// title: string; -// description: string; -// requirements?: string; -// professors?: string[]; -// distributions?: string; -// flags?: string; -// credits: number; -// season_code?: string; -// colsem?: string; -// fysem?: string; -// sysem?: string; -// }; - export default function Graduation() { - const [programs, setPrograms] = useState([]) - const [loading, setLoading] = useState(true) - - useEffect(() => { - async function fetchPrograms() { - try { - const response = await fetch('/api/programs') - const data = await response.json() - console.log(data) - setPrograms(data) - } catch (error) { - console.error('Error fetching programs:', error) - } finally { - setLoading(false) - } - } - - fetchPrograms() - }, []) - - if (loading) return
Loading programs...
- return (
- +
diff --git a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx index 5cd2675..c07761f 100644 --- a/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx +++ b/frontend/src/app/majors/course-icon/MajorsCourseIcon.tsx @@ -4,7 +4,7 @@ import Style from "./MajorsCourseIcon.module.css" import Image from "next/image"; -import { ConcentrationSubrequirement } from "@/types/type-program"; +import { Subrequirement } from "@/types/type-program"; import { StudentCourse, Course } from "@/types/type-user"; import DistributionCircle from "@/components/distribution-circle/DistributionsCircle"; @@ -37,7 +37,7 @@ function SeasonComp(props: { seasons: string[] }) function CourseIcon(props: { edit: boolean; course: Course; - subreq: ConcentrationSubrequirement; + subreq: Subrequirement; onRemoveCourse: Function; }){ return ( @@ -45,10 +45,10 @@ function CourseIcon(props: { {props.edit && ( props.onRemoveCourse(props.course, props.subreq, false)} /> )} - + {/* */} {props.course.codes[0]}
- +
); @@ -57,7 +57,7 @@ function CourseIcon(props: { function StudentCourseIcon(props: { edit: boolean; studentCourse: StudentCourse; - subreq: ConcentrationSubrequirement; + subreq: Subrequirement; onRemoveCourse: Function; }) { return ( @@ -167,7 +167,7 @@ function EmptyIcon(props: { export function MajorsIcon(props: { edit: boolean; contentCourse: Course | StudentCourse | null; - subreq: ConcentrationSubrequirement; + subreq: Subrequirement; onRemoveCourse: Function; onAddCourse: Function; }) { diff --git a/frontend/src/app/majors/metadata/Metadata.tsx b/frontend/src/app/majors/metadata/Metadata.tsx index 1504d16..f09dbf7 100644 --- a/frontend/src/app/majors/metadata/Metadata.tsx +++ b/frontend/src/app/majors/metadata/Metadata.tsx @@ -3,7 +3,8 @@ import { useState, useEffect } from "react"; import Style from "./Metadata.module.css"; import Link from 'next/link'; -import { MajorsIndex, Program, ProgramDict } from "@/types/type-program"; +import { Program, ProgramDict } from "@/types/type-program"; +import { MajorsIndex } from "@/types/type-user"; import { usePrograms } from "@/context/ProgramProvider"; import { useAuth } from "@/context/AuthProvider"; @@ -24,22 +25,22 @@ function MetadataTopshelf(props: {
- + */}
- {props.program.prog_data.prog_name} + {props.program.name}
- {props.program.prog_data.prog_stud_count} + {props.program.student_count}
MAJOR
- {props.program.prog_data.prog_abbr} + {props.program.abbreviation}
@@ -92,28 +93,28 @@ function MetadataToggle(props: { return(
- {props.program.prog_degs.map((deg, index) => ( + {props.program.degrees.map((deg, index) => ( ))}
- {props.program.prog_degs[props.index.deg].deg_concs.length > 1 && ( + {props.program.degrees[props.index.deg].concentrations.length > 1 && (
- {props.program.prog_degs[props.index.deg].deg_concs.map((conc, index) => ( + {props.program.degrees[props.index.deg].concentrations.map((conc, index) => ( ))}
@@ -134,17 +135,17 @@ function MetadataBody(props: { ABOUT
- {props.program.prog_degs[props.index.deg].deg_concs[props.index.conc].conc_desc} + {props.program.degrees[props.index.deg].concentrations[props.index.conc].description}
DUS
- {props.program.prog_data.prog_dus.dus_name}; {props.program.prog_data.prog_dus.dus_email} + {/* {props.program.prog_data.prog_dus.dus_name}; {props.program.prog_data.prog_dus.dus_email} */}
-
MAJOR CATALOG
-
MAJOR WEBSITE
+
MAJOR CATALOG
+
MAJOR WEBSITE
); @@ -166,10 +167,10 @@ function MetadataScrollButton(props: {
- {props.programs[nextProg].prog_data.prog_name} + {props.programs[nextProg].name}
- {props.programs[nextProg].prog_data.prog_abbr} + {props.programs[nextProg].abbreviation}
@@ -189,7 +190,7 @@ function ProgramList(props: { className={Style.ProgramOption} onClick={() => props.setIndex({ conc: 0, deg: 0, prog: progCode })} > - {program.prog_data.prog_name} ({program.prog_data.prog_abbr}) + {program.name} ({program.abbreviation})
))}
diff --git a/frontend/src/app/majors/page.tsx b/frontend/src/app/majors/page.tsx index de50e6c..e3e576c 100644 --- a/frontend/src/app/majors/page.tsx +++ b/frontend/src/app/majors/page.tsx @@ -6,19 +6,18 @@ import { usePrograms } from "@/context/ProgramProvider"; import { useState, useEffect } from "react"; import Style from "./Majors.module.css"; -import { MajorsIndex } from "@/types/type-program"; +import { MajorsIndex } from "@/types/type-user"; import { initializeMajorsIndex, updateMajorsIndex } from "./MajorsUtils"; import NavBar from "@/components/navbar/NavBar"; import Overhead from "./overhead/Overhead"; import Metadata from "./metadata/Metadata"; import Requirements from "./requirements/Requirements"; -import { fill } from "@/utils/preprocessing/Fill"; function Majors() { const { user } = useAuth(); - const { progDict, setProgDict } = usePrograms(); + const { progDict } = usePrograms(); const progKeys = Object.keys(progDict); const [filteredProgKeys, setFilteredProgKeys] = useState(progKeys); @@ -53,10 +52,9 @@ function Majors() return(
- {/* }/> + }/>
setListView((prev) => !prev)}/> -
fill(user.FYP.studentCourses, progDict, setProgDict)}/>
-
*/} +
); } diff --git a/frontend/src/app/majors/requirements/Requirements.tsx b/frontend/src/app/majors/requirements/Requirements.tsx index 39d7d55..616dc3f 100644 --- a/frontend/src/app/majors/requirements/Requirements.tsx +++ b/frontend/src/app/majors/requirements/Requirements.tsx @@ -7,15 +7,16 @@ import { useState, useEffect } from "react"; import Style from "./Requirements.module.css"; import { Course } from "@/types/type-user"; -import { ConcentrationSubrequirement, ConcentrationRequirement, MajorsIndex, SubreqCourseOption } from "@/types/type-program"; +import { Subrequirement, Requirement, Option } from "@/types/type-program"; +import { MajorsIndex } from "@/types/type-user"; import { getStudentConcentration, removeCourseInSubreq, addCourseInSubreq, toggleSubreqSelection } from "./RequirementsUtils"; import { MajorsIcon } from "../course-icon/MajorsCourseIcon"; function RenderSubrequirementCourse(props: { edit?: boolean; - option: SubreqCourseOption; - subreq: ConcentrationSubrequirement; + option: Option; + subreq: Subrequirement; onRemoveCourse: Function, onAddCourse: Function }){ @@ -23,7 +24,7 @@ function RenderSubrequirementCourse(props: {
opt.s !== null).length; - const filteredCourses = satisfiedCount >= props.subreq.subreq_courses_req_count - ? props.subreq.subreq_options.filter(opt => opt.s !== null) - : props.subreq.subreq_options; + const satisfiedCount = props.subreq.options.filter(opt => opt.satisfier !== null).length; + const filteredCourses = satisfiedCount >= props.subreq.courses_required_count + ? props.subreq.options.filter(opt => opt.satisfier !== null) + : props.subreq.options; return(
- {satisfiedCount}|{props.subreq.subreq_courses_req_count} {props.subreq.subreq_name} + {satisfiedCount}|{props.subreq.courses_required_count} {props.subreq.name}
- {props.subreq.subreq_desc} + {props.subreq.description}
{filteredCourses.map((option, option_index) => ( @@ -68,7 +69,7 @@ function RenderSubrequirement(props: { key={option_index} option={option} subreq={props.subreq} - edit={props.subreq.subreq_flex ? props.edit : false} + edit={props.edit} onRemoveCourse={handleRemoveCourse} onAddCourse={handleAddCourse} /> @@ -82,15 +83,15 @@ function RenderRequirement(props: { edit: boolean, majorsIndex: MajorsIndex, reqIndex: number, - req: ConcentrationRequirement + req: Requirement }){ const { user, setUser } = useAuth(); const userConc = getStudentConcentration(user, props.majorsIndex); const selectedSubreqs = userConc?.selected_subreqs[props.reqIndex] ?? []; const visibleSubreqs = selectedSubreqs.length > 0 - ? props.req.subreqs_list.filter((_, i) => selectedSubreqs.includes(i)) - : props.req.subreqs_list; + ? props.req.subrequirements.filter((_, i) => selectedSubreqs.includes(i)) + : props.req.subrequirements; function handleToggleSubreq(subreqIndex: number) { if(userConc && props.edit){ @@ -99,7 +100,7 @@ function RenderRequirement(props: { props.majorsIndex, props.reqIndex, subreqIndex, - props.req.subreqs_required_count ?? props.req.subreqs_list.length + props.req.subreqs_required_count ?? props.req.subrequirements.length ); } } @@ -107,12 +108,12 @@ function RenderRequirement(props: { let dynamicRequiredCount: number | string = props.req.courses_required_count; if(props.req.courses_required_count === -1){ dynamicRequiredCount = selectedSubreqs.length > 0 - ? selectedSubreqs.reduce((sum, idx) => sum + props.req.subreqs_list[idx].subreq_courses_req_count, 0) + ? selectedSubreqs.reduce((sum, idx) => sum + props.req.subrequirements[idx].courses_required_count, 0) : "~"; } - const dynamicSatisfiedCount = props.req.subreqs_list.reduce( - (sum, subreq) => sum + subreq.subreq_options.filter(option => option.s !== null).length, + const dynamicSatisfiedCount = props.req.subrequirements.reduce( + (sum, subreq) => sum + subreq.options.filter(option => option.satisfier !== null).length, 0 ); @@ -120,36 +121,36 @@ function RenderRequirement(props: {
- {props.req.req_name} + {props.req.name}
- {props.req.checkbox !== undefined + {props.req.checkbox ? dynamicSatisfiedCount === dynamicRequiredCount ? "✅" : "❌" : `${dynamicSatisfiedCount}|${dynamicRequiredCount}` }
- {props.req.req_desc} + {props.req.description}
- {(props.req.subreqs_required_count !== undefined && props.req.subreqs_required_count < props.req.subreqs_list.length) && ( + {(props.req.subreqs_required_count !== undefined && props.req.subreqs_required_count < props.req.subrequirements.length) && (
- {props.req.subreqs_list.map((subreq, i) => ( + {props.req.subrequirements.map((subreq, i) => ( ))}
)}
- {visibleSubreqs.map((subreq, subreqIndex) => ( + {visibleSubreqs.map((subreq: Subrequirement, subreqIndex: number) => ( @@ -193,14 +196,14 @@ function Requirements(props: {
Requirements
- {userConc && + {/* {userConc && - } + } */}
- {conc.conc_reqs.map((req: ConcentrationRequirement, reqIndex: number) => ( + {conc.requirements.map((req: Requirement, reqIndex: number) => ( void; - // baseProgDict: ProgramDict; + progDict: ProgramDict; + setProgDict: (dict: ProgramDict) => void; + baseProgDict: ProgramDict; isLoading: boolean; - // error: string | null; - // resetToBase: () => void; + error: string | null; + resetToBase: () => void; } const ProgramContext = createContext(null); @@ -21,40 +21,41 @@ export function ProgramProvider({ children }: { children: React.ReactNode }) { const [progDict, setProgDict] = useState({}); const [error, setError] = useState(null); - // useEffect(() => { - // const fetchPrograms = async () => { - // setIsLoading(true); - // try { - // const response = await fetch('/api/programs'); - // if (!response.ok) throw new Error('Failed to fetch programs'); + useEffect(() => { + async function fetchPrograms() { + setIsLoading(true); + try { + const response = await fetch('/api/programs'); + if (!response.ok) throw new Error('Failed to fetch programs.'); - // const fetchedData = await response.json(); - // // Store as separate objects to prevent reference issues - // setBaseProgDict(JSON.parse(JSON.stringify(fetchedData))); - // setProgDict(JSON.parse(JSON.stringify(fetchedData))); - // } catch (err) { - // setError(err instanceof Error ? err.message : 'Unknown error'); - // } finally { - // setIsLoading(false); - // } - // }; + const fetchedData = await response.json(); - // fetchPrograms(); - // }, []); + // Store as separate objects to prevent reference issues. + setBaseProgDict(JSON.parse(JSON.stringify(fetchedData))); + setProgDict(JSON.parse(JSON.stringify(fetchedData))); + } catch (err) { + setError(err instanceof Error ? err.message : 'Unknown error'); + } finally { + setIsLoading(false); + } + } - // // Deep clone when resetting to base - // const resetToBase = useCallback(() => { - // setProgDict(JSON.parse(JSON.stringify(baseProgDict))); - // }, [baseProgDict]); + fetchPrograms(); + }, []); + + // Deep clone when resetting to base. + const resetToBase = useCallback(() => { + setProgDict(JSON.parse(JSON.stringify(baseProgDict))); + }, [baseProgDict]); return ( {children} diff --git a/frontend/src/types/type-program.ts b/frontend/src/types/type-program.ts index a564f17..e7b50f5 100644 --- a/frontend/src/types/type-program.ts +++ b/frontend/src/types/type-program.ts @@ -47,13 +47,4 @@ export interface Program { degrees: Degree[]; } -// any null strings go to "", null numbers go to 0 - -// string is program.abbreviation export type ProgramDict = Record; - -// export interface MajorsIndex { -// prog: string; -// deg: number; -// conc: number; -// } diff --git a/frontend/src/types/type-user.ts b/frontend/src/types/type-user.ts index 7a3b4a4..ff158a8 100644 --- a/frontend/src/types/type-user.ts +++ b/frontend/src/types/type-user.ts @@ -66,3 +66,9 @@ export interface User { netID: string; FYP: FYP; } + +export interface MajorsIndex { + prog: string; + deg: number; + conc: number; +} From c31412b44bf7b8230ee18af8d7cdf53bdef9f0bb Mon Sep 17 00:00:00 2001 From: RyanGumlia Date: Thu, 20 Mar 2025 14:51:36 -0700 Subject: [PATCH 38/38] prisma joins --- frontend/src/app/api/programs/db-service.ts | 114 +++++--------------- 1 file changed, 28 insertions(+), 86 deletions(-) diff --git a/frontend/src/app/api/programs/db-service.ts b/frontend/src/app/api/programs/db-service.ts index 75368c2..ecc9550 100644 --- a/frontend/src/app/api/programs/db-service.ts +++ b/frontend/src/app/api/programs/db-service.ts @@ -1,99 +1,41 @@ - -// db-service.ts import prisma from '@/database/client' export async function fetchProgramHierarchy() { - const programs = await prisma.program.findMany() - - return Promise.all(programs.map(async program => { - const degrees = await prisma.degree.findMany({ - where: { program_id: program.id } - }) - - const degreesWithRelations = await Promise.all(degrees.map(async degree => { - const concentrations = await prisma.concentration.findMany({ - where: { degree_id: degree.id } - }) - - const concentrationsWithRequirements = await Promise.all(concentrations.map(async concentration => { - const concentrationRequirements = await prisma.concentrationRequirement.findMany({ - where: { concentration_id: concentration.id }, - include: { requirement: true } - }) - - const requirementsWithSubreqs = await Promise.all( - concentrationRequirements.map(async cr => { - const requirementSubrequirements = await prisma.requirementSubrequirement.findMany({ - where: { requirement_id: cr.requirement_id }, - include: { subrequirement: true } - }) - - const subrequirementsWithOptions = await Promise.all( - requirementSubrequirements.map(async rs => { - const subrequirementOptions = await prisma.subrequirementOption.findMany({ - where: { subrequirement_id: rs.subrequirement_id } - }); - - const optionsWithDetails = await Promise.all( - subrequirementOptions.map(async so => { - if (so.option_id) { - const option = await prisma.option.findUnique({ - where: { id: so.option_id } - }); - - if (option && option.option_course_id) { - const course = await prisma.course.findUnique({ - where: { id: option.option_course_id } - }); - - return { - ...so, - option: { ...option, course } - }; + return prisma.program.findMany({ + include: { + degrees: { + include: { + concentrations: { + include: { + concentration_requirements: { + include: { + requirement: { + include: { + requirement_subrequirements: { + include: { + subrequirement: { + include: { + subrequirement_options: { + include: { + option: { + include: { + course: true + } + } + } + } + } + } + } } - - return { ...so, option }; } - - return so; - }) - ); - - return { - ...rs, - subrequirement: { - ...rs.subrequirement, - subrequirement_options: optionsWithDetails } } - }) - ) - - return { - ...cr, - requirement: { - ...cr.requirement, - requirement_subrequirements: subrequirementsWithOptions } } - }) - ) - - return { - ...concentration, - concentration_requirements: requirementsWithSubreqs + } } - })) - - return { - ...degree, - concentrations: concentrationsWithRequirements } - })) - - return { - ...program, - degrees: degreesWithRelations } - })) + }) }