From 5cb3a6f85e365777a18fdf88cbc08b3563480065 Mon Sep 17 00:00:00 2001 From: andycheng233 Date: Sun, 16 Feb 2025 02:53:01 -0500 Subject: [PATCH 1/3] several changes to courses page: year/semester view, auto-checking course completion, added summer semester, fixed many design issues --- backend/output.json | 2 +- frontend/src/app/courses/Courses.module.css | 35 ++++++- .../add-semester/AddSemesterButton.tsx | 6 +- frontend/src/app/courses/page.tsx | 31 +++++-- .../courses/semester/SemesterBox.module.css | 61 +++++++++++++ .../src/app/courses/semester/SemesterBox.tsx | 91 +++++++++++++++++-- .../add-course/AddCourseButton.module.css | 4 +- .../semester/add-course/AddCourseButton.tsx | 11 ++- .../semester/course/CourseBox.module.css | 4 +- .../app/courses/semester/course/CourseBox.tsx | 6 +- .../app/courses/views/SemesterView.module.css | 7 ++ .../src/app/courses/views/SemesterView.tsx | 29 ++++++ .../src/app/courses/views/YearView.module.css | 73 +++++++++++++++ frontend/src/app/courses/views/YearView.tsx | 59 ++++++++++++ frontend/src/database/data-catalog.ts | 23 +++++ 15 files changed, 415 insertions(+), 27 deletions(-) create mode 100644 frontend/src/app/courses/views/SemesterView.module.css create mode 100644 frontend/src/app/courses/views/SemesterView.tsx create mode 100644 frontend/src/app/courses/views/YearView.module.css create mode 100644 frontend/src/app/courses/views/YearView.tsx diff --git a/backend/output.json b/backend/output.json index 667f6b7..0df9b1c 100644 --- a/backend/output.json +++ b/backend/output.json @@ -42270,7 +42270,7 @@ ] }, { - "course_code": "ARCH 4011", + "course_code": "ARCH 1", "title": "Introduction to Urban Design", "credits": 0, "skills": [], diff --git a/frontend/src/app/courses/Courses.module.css b/frontend/src/app/courses/Courses.module.css index bd394d4..1f8eeed 100644 --- a/frontend/src/app/courses/Courses.module.css +++ b/frontend/src/app/courses/Courses.module.css @@ -9,8 +9,9 @@ top: 75px; display: flex; - flex-direction: row; + flex-direction: column; justify-content: center; + align-items: center; width: 100%; @@ -39,3 +40,35 @@ z-index: 2; } + +.ViewToggleButton { + position: fixed; + + top: 95px; + left: 60px; + + width: 30px; + height: 30px; + + color: white; + text-align: center; + line-height: 30px; + font-size: 20px; + + background-color: lightgreen; + border: none; + border-radius: 50%; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.25); + + z-index: 2; +} + +.SemesterAll{ + display: flex; + align-items: center; + flex-direction: column; +} + +.SemesterButton{ + margin-right: auto; +} diff --git a/frontend/src/app/courses/add-semester/AddSemesterButton.tsx b/frontend/src/app/courses/add-semester/AddSemesterButton.tsx index 322de2f..2108dfc 100644 --- a/frontend/src/app/courses/add-semester/AddSemesterButton.tsx +++ b/frontend/src/app/courses/add-semester/AddSemesterButton.tsx @@ -20,8 +20,10 @@ function executeAddSemester(props: { user: User; setUser: Function }, inputRef: }; const updatedSemesters = [...props.user.FYP.studentSemesters, newSemester]; - props.setUser({ ...props.user, FYP: { ...props.user.FYP, studentSemesters: updatedSemesters } }); - setDropVis(false); + const updatedUser = { ...props.user, FYP: { ...props.user.FYP, studentSemesters: updatedSemesters } }; + props.setUser(updatedUser); + console.log("Updated User:", updatedUser); + setDropVis(false); } } diff --git a/frontend/src/app/courses/page.tsx b/frontend/src/app/courses/page.tsx index ef80a8a..add334c 100644 --- a/frontend/src/app/courses/page.tsx +++ b/frontend/src/app/courses/page.tsx @@ -9,6 +9,8 @@ import { StudentSemester } from "@/types/type-user"; import NavBar from "@/components/navbar/NavBar"; import SemesterBox from "./semester/SemesterBox"; import AddSemesterButton from "./add-semester/AddSemesterButton"; +import SemesterView from "./views/SemesterView"; +import YearView from "./views/YearView"; function Courses() { @@ -16,14 +18,22 @@ function Courses() const [renderedSemesters, setRenderedSemesters] = useState([]); const [edit, setEdit] = useState(false); + const [isSemesterView, setIsSemesterView] = useState(true); const toggleEdit = () => { setEdit(!edit); }; + const toggleView = () => { + setIsSemesterView(!isSemesterView); + } + + const semesters = [...user.FYP.studentSemesters].sort((a,b) => a.season-b.season); + useEffect(() => { const newRenderedSemesters = user.FYP.studentSemesters.map((semester: StudentSemester, index: number) => ( )); + setRenderedSemesters(newRenderedSemesters); }, [edit, user, setUser]); @@ -31,13 +41,20 @@ function Courses()
- -
- {renderedSemesters} - {edit && } -
+ + +
+ {isSemesterView ? ( + + ) : ( + + )} + {edit && ( +
+ +
+ )} +
); diff --git a/frontend/src/app/courses/semester/SemesterBox.module.css b/frontend/src/app/courses/semester/SemesterBox.module.css index b41c37a..6408bbf 100644 --- a/frontend/src/app/courses/semester/SemesterBox.module.css +++ b/frontend/src/app/courses/semester/SemesterBox.module.css @@ -3,3 +3,64 @@ display: flex; flex-direction: column; } + +.SemesterCode { + font-weight: 400; + font-size: 20px; +} + +.InfoRow { + display: flex; + gap: 15px; /* Space between info boxes */ + } + + .InfoBox { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 1rem; + } + + .InfoTitle { + font-size: 12px; + font-weight: 500; + color: gray; + margin-bottom: 4px; + } + + .InfoValue { + font-size: 10px; + font-weight: bold; + align-content: center; + color: white; + padding: 4px 8px; + border-radius: 4px; + text-align: center; + width: 25px; /* Fixed width */ + height: 15px; /* Fixed height */ + } + + .courseBox { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + width: 560px; + height: 46px; + + border-radius: 16px; + margin-bottom: 5px; + + padding-left: 10px; + padding-right: 10px; + + background-color: #F5F5F5; + transition: filter 0.4s ease; +} + +.CourseCode { + margin-left: 15px; + font-size: 12px; + font-weight: 500; +} diff --git a/frontend/src/app/courses/semester/SemesterBox.tsx b/frontend/src/app/courses/semester/SemesterBox.tsx index 020d34e..c0a2949 100644 --- a/frontend/src/app/courses/semester/SemesterBox.tsx +++ b/frontend/src/app/courses/semester/SemesterBox.tsx @@ -7,20 +7,95 @@ import { StudentSemester, User } from "@/types/type-user"; import CourseBox from "./course/CourseBox"; import AddCourseButton from "./add-course/AddCourseButton"; -function SemesterBox(props: { edit: boolean, studentSemester: StudentSemester, user: User, setUser: Function }) { +function translateSemester(semesterCode: number) { + let year = Math.floor(semesterCode/100); + let seasonCode = semesterCode % 10; + let season = seasonCode === 1 ? "Fall" : seasonCode === 3 ? "Spring" : "Summer"; + return `${season} ${year}`; +} + +function getCredits(semester: StudentSemester){ + let credit = 0 + for(const course of semester.studentCourses) + { + credit += course.course.credit; + } + + return credit; +} + +function updateStatus(semester : StudentSemester){ + const date = new Date() + const currYear = date.getFullYear(); + const month_day = Number(String(date.getMonth() + 1) + String(date.getDate())) + + + //when the semester ends according to month+day + const semesterEnds: { [key: number]: number } = { + 1: 1224, + 3: 515, + 5: 828 + }; + + let semesterYear = Math.floor(semester.season / 100); + const semesterSeasonCode = semester.season % 10; + + if(semesterSeasonCode == 3 || semesterSeasonCode == 5) + semesterYear++; + + if(currYear > semesterYear || (currYear == semesterYear && month_day > semesterEnds[semesterSeasonCode])) + { + console.log(`curr Year ${currYear} > semesterYear ${semesterYear}, or month_day ${month_day} > semesterEnds ${semesterEnds[semesterSeasonCode]}`) + for(const course of semester.studentCourses) + course.status = "DA_COMPLETE"; + } +} + +function SemesterBox(props: { edit: boolean, studentSemester: StudentSemester, user: User, setUser: Function, width?: string}) { + + updateStatus(props.studentSemester); + const isEmpty = (props.studentSemester.studentCourses.length == 0) ? true : false; + const width = props.width || "560px"; + + const studentCourseBoxes = isEmpty ? ( +
+
+ {"No Courses Added Yet"} +
+
+ ) : ( + props.studentSemester.studentCourses.map((studentCourse, index) => ( + + )) + ); - let studentCourseBoxes = props.studentSemester.studentCourses.map((studentCourse, index) => ( - - )); + + + const infoData = [ + {name: "CREDITS", value: getCredits(props.studentSemester), color: "#d9deda", text_color: "#5e5f61"}, + {name: "RATING", value: "~4.0", color: "#D9F4CF", text_color: "#66C10A"}, + {name: "WORKLOAD", value: "~3.8", color: "#F4E3CF", text_color: "#CA7108"} + /*{name: "DISTRIBUTIONALS"}*/ + ] return(
-
- {props.studentSemester.season} +
+ {translateSemester(props.studentSemester.season)} +
+
+ {infoData.map((item, index) => ( +
+
{item.name}
+
+ {item.value} +
+
+ ))}
-
+
{studentCourseBoxes} - {props.edit && } + {props.edit && }
); diff --git a/frontend/src/app/courses/semester/add-course/AddCourseButton.module.css b/frontend/src/app/courses/semester/add-course/AddCourseButton.module.css index b6dc39b..c9eae89 100644 --- a/frontend/src/app/courses/semester/add-course/AddCourseButton.module.css +++ b/frontend/src/app/courses/semester/add-course/AddCourseButton.module.css @@ -27,8 +27,8 @@ flex-direction: row; justify-content: space-between; align-items: center; - width: 425px; - height: 36px; + width: 560px; + height: 46px; border-radius: 16px; margin-bottom: 5px; padding-left: 10px; diff --git a/frontend/src/app/courses/semester/add-course/AddCourseButton.tsx b/frontend/src/app/courses/semester/add-course/AddCourseButton.tsx index a391fdb..d849957 100644 --- a/frontend/src/app/courses/semester/add-course/AddCourseButton.tsx +++ b/frontend/src/app/courses/semester/add-course/AddCourseButton.tsx @@ -18,11 +18,16 @@ function executeAddCourse( selectedTerm: number, setAddDisplay: Function ){ + console.log("Trying to add course") if(inputRef.current){ + console.log("im in") const targetCode = inputRef.current.value; + console.log(targetCode) + console.log(selectedTerm) const targetCourse = getCatalogCourse(selectedTerm, targetCode); if(targetCourse){ + console.log("target acquired") const status = selectedTerm === props.term ? "MA_VALID" : "MA_HYPOTHETICAL"; const newCourse: StudentCourse = { course: targetCourse, status, term: props.term }; @@ -39,7 +44,9 @@ function executeAddCourse( } } -function AddCourseButton(props: { term: number; user: User; setUser: Function }) { +function AddCourseButton(props: { term: number; user: User; setUser: Function , width?: String}) { + + const width = props.width || "560px"; const inputRef = useRef(null); const addRef = useRef(null); @@ -88,7 +95,7 @@ function AddCourseButton(props: { term: number; user: User; setUser: Function }) +
) : ( -
+
setAddDisplay((prevState) => ({...prevState, active: false}))}> diff --git a/frontend/src/app/courses/semester/course/CourseBox.module.css b/frontend/src/app/courses/semester/course/CourseBox.module.css index 739f3ee..f89f604 100644 --- a/frontend/src/app/courses/semester/course/CourseBox.module.css +++ b/frontend/src/app/courses/semester/course/CourseBox.module.css @@ -37,8 +37,8 @@ justify-content: space-between; align-items: center; - width: 425px; - height: 36px; + width: 560px; + height: 46px; border-radius: 16px; margin-bottom: 5px; diff --git a/frontend/src/app/courses/semester/course/CourseBox.tsx b/frontend/src/app/courses/semester/course/CourseBox.tsx index 6cc7bfb..7b50065 100644 --- a/frontend/src/app/courses/semester/course/CourseBox.tsx +++ b/frontend/src/app/courses/semester/course/CourseBox.tsx @@ -13,7 +13,7 @@ import DistributionCircle from "@/components/distribution-circle/DistributionsCi // import { useModal } from "../../../hooks/modalContext"; -function CourseBox(props: {edit: boolean, studentCourse: StudentCourse, user: User, setUser: Function }) +function CourseBox(props: {edit: boolean, studentCourse: StudentCourse, user: User, setUser: Function, width?: string}) { // const { setModalOpen } = useModal(); // function openModal() { @@ -21,10 +21,12 @@ function CourseBox(props: {edit: boolean, studentCourse: StudentCourse, user: Us // } const getBackgroundColor = () => (props.studentCourse.status === "DA_COMPLETE" ? "#E1E9F8" : "#F5F5F5"); + + const width = props.width || "560px"; return ( -
+
{/* onClick={openModal} */}
diff --git a/frontend/src/app/courses/views/SemesterView.module.css b/frontend/src/app/courses/views/SemesterView.module.css new file mode 100644 index 0000000..b6163c1 --- /dev/null +++ b/frontend/src/app/courses/views/SemesterView.module.css @@ -0,0 +1,7 @@ +.semesterView { + display: flex; + flex-direction: column; + gap: 20px; /* Space between semesters */ + padding: 20px; + padding-bottom:5px; + } \ No newline at end of file diff --git a/frontend/src/app/courses/views/SemesterView.tsx b/frontend/src/app/courses/views/SemesterView.tsx new file mode 100644 index 0000000..42b4982 --- /dev/null +++ b/frontend/src/app/courses/views/SemesterView.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import SemesterBox from "../semester/SemesterBox"; +import Style from "./SemesterView.module.css"; +import { StudentSemester, User } from "@/types/type-user"; + +interface SemesterViewProps { + semesters: StudentSemester[]; + edit: boolean; + user: User; + setUser: Function; +} + +function SemesterView({ semesters, edit, user, setUser }: SemesterViewProps) { + return ( +
+ {semesters.map((semester) => ( + + ))} +
+ ); +} + +export default SemesterView; \ No newline at end of file diff --git a/frontend/src/app/courses/views/YearView.module.css b/frontend/src/app/courses/views/YearView.module.css new file mode 100644 index 0000000..b69be4d --- /dev/null +++ b/frontend/src/app/courses/views/YearView.module.css @@ -0,0 +1,73 @@ +.yearView { + display: flex; + flex-direction: column; + gap: 1.25rem; /* Space between years */ + padding: 1.25rem; + padding-bottom:5px; + } + + .yearContainer { + display: flex; + flex-direction: column; + } + + .yearHeader { + font-size: 2rem; /* 18px */ + font-weight: 550; + margin-bottom: 0.0rem; /* 10px */ + } + + .yearHeader_year { + color:gray; + font-size: 0.9rem; /* 18px */ + font-weight: 550; + } + + /* Arrange semesters in a row, Fall on the left and Spring on the right */ + .semesterRow { + display: flex; + flex-direction: row; /* Row-based layout */ + gap: 2rem; /* Space between Fall and Spring */ + justify-content: flex-start; /* Align semesters properly */ + /*flex-wrap: wrap; /* Allow wrapping if necessary */ + } + + .semesterContainer { + display: flex; + flex-direction: column; + gap: 0.01rem; /* 5px, space between semester header and courses */ + /*width: 48%; /* Ensure both semesters fit side by side */ /* maybe change width based on number in group*/ + width: calc(100% / var(--num-semesters) - var(--gap) * (var(--num-semesters) - 1)); + } + + .semesterHeader { + font-size: 0.875rem; /* 14px */ + font-weight: 500; + margin-bottom: 0.1rem; /* 5px */ + line-height: 1.4; /* Improve readability */ + } + + /* Responsive Design */ + @media (max-width: 768px) { + .yearView { + padding: 1rem; + gap: 1rem; + } + + .semesterRow { + flex-direction: column; /* Stack semesters vertically on smaller screens */ + gap: 1rem; + } + + .yearHeader { + font-size: 1rem; + } + + .semesterContainer { + width: 100%; /* Full width on small screens */ + } + + .semesterHeader { + font-size: 0.8125rem; + } + } \ No newline at end of file diff --git a/frontend/src/app/courses/views/YearView.tsx b/frontend/src/app/courses/views/YearView.tsx new file mode 100644 index 0000000..fb4d8b5 --- /dev/null +++ b/frontend/src/app/courses/views/YearView.tsx @@ -0,0 +1,59 @@ +import React from "react"; +import SemesterBox from "../semester/SemesterBox"; +import Style from "./YearView.module.css"; +import { StudentSemester, User } from "@/types/type-user"; + +interface YearViewProps { + semesters: StudentSemester[]; + edit: boolean; + user: User; + setUser: Function; +} + +function YearView({ semesters, edit, user, setUser }: YearViewProps) { + // Sort semesters by season (earliest first) + const sortedSemesters = [...semesters].sort((a, b) => a.season - b.season); + + // Group sorted semesters by year + const groupedSemesters: { [year: string]: StudentSemester[] } = {}; + + const years = ["First Year", "Second Year", "Third Year", "Fourth Year", "Fifth Year", "Sixth Year", "Seventh Year", "Eighth Year", "Ninth Year", "Tenth Year"]; + let yearTrack = 0; + + sortedSemesters.forEach((semester) => { + const year = Math.floor(semester.season / 100); // Extract the year (e.g., 2023 from 202301) + if (!groupedSemesters[year]) { + groupedSemesters[year] = []; + } + groupedSemesters[year].push(semester); + }); + + return ( +
+ {/* Iterate over each year */} + {Object.entries(groupedSemesters).map(([year, semestersInYear]) => ( +
+

{years[yearTrack++]} ({year}-{Number(year)+1})

+ {/* Render Fall and Spring semesters side by side */} +
+ {semestersInYear.map((semester) => ( +
+

+

+ +
+ ))} +
+
+ ))} +
+ ); +} + +export default YearView; \ No newline at end of file diff --git a/frontend/src/database/data-catalog.ts b/frontend/src/database/data-catalog.ts index 212e244..6d56b2d 100644 --- a/frontend/src/database/data-catalog.ts +++ b/frontend/src/database/data-catalog.ts @@ -7,8 +7,31 @@ interface Catalog { } export const Catalogs: Catalog[] = [ + { number: 202201, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, { 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: 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: 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: 202503, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, + { number: 202205, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, + { number: 202305, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, + { number: 202405, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, + { number: 202505, courses: [{ codes: ["HSAR 401"], title: "Critical Approaches", credit: 1, dist: [], seasons: [] }] }, + { number: 202201, courses: [{ codes: ["CPSC 201"], title: "Introduction to Computer Science", credit: 1, dist: [], seasons: [] }] }, + { number: 202203, courses: [{ codes: ["CPSC 201"], title: "Introduction to Computer Science", credit: 1, dist: [], seasons: [] }] }, + { number: 202301, courses: [{ codes: ["CPSC 201"], title: "Introduction to Computer Science", credit: 1, dist: [], seasons: [] }] }, + { number: 202303, courses: [{ codes: ["CPSC 201"], title: "Introduction to Computer Science", credit: 1, dist: [], seasons: [] }] }, + { number: 202401, courses: [{ codes: ["CPSC 201"], title: "Introduction to Computer Science", credit: 1, dist: [], seasons: [] }] }, + { number: 202403, courses: [{ codes: ["CPSC 201"], title: "Introduction to Computer Science", credit: 1, dist: [], seasons: [] }] }, + { number: 202501, courses: [{ codes: ["CPSC 201"], title: "Introduction to Computer Science", credit: 1, dist: [], seasons: [] }] }, + { number: 202503, courses: [{ codes: ["CPSC 201"], title: "Introduction to Computer Science", credit: 1, dist: [], seasons: [] }] }, + { number: 202205, courses: [{ codes: ["CPSC 201"], title: "Introduction to Computer Science", credit: 1, dist: [], seasons: [] }] }, + { number: 202305, courses: [{ codes: ["CPSC 201"], title: "Introduction to Computer Science", credit: 1, dist: [], seasons: [] }] }, + { number: 202405, courses: [{ codes: ["CPSC 201"], title: "Introduction to Computer Science", credit: 1, dist: [], seasons: [] }] }, + { number: 202505, courses: [{ codes: ["CPSC 201"], title: "Introduction to Computer Science", credit: 1, dist: [], seasons: [] }] }, + ] export const getCatalogCourse = (catalogNumber: number, courseCode: string): Course | null => { From d46790afd23030fb4fdd98823a2b1c69e527e77f Mon Sep 17 00:00:00 2001 From: andycheng233 Date: Sat, 12 Apr 2025 02:21:24 -0400 Subject: [PATCH 2/3] added the graduation page --> recommendations and distributions --- frontend/package-lock.json | 398 +++++++++++++++--- frontend/package.json | 7 +- .../src/app/graduation/Graduation.module.css | 74 ++++ .../DistributionBoxLarge.tsx | 37 ++ .../GraduationDistribution.module.css | 20 + .../GraduationDistribution.tsx | 319 ++++++++++++++ .../InfoButton.module.css | 9 + .../graduation-distribution/InfoButton.tsx | 22 + frontend/src/app/graduation/page.tsx | 43 +- .../recommendation/Recommendation.module.css | 22 + .../recommendation/Recommendation.tsx | 65 +++ frontend/src/app/majors/Majors.module.css | 72 +++- .../src/components/course-icon/CourseIcon.tsx | 2 +- 13 files changed, 1026 insertions(+), 64 deletions(-) create mode 100644 frontend/src/app/graduation/Graduation.module.css create mode 100644 frontend/src/app/graduation/graduation-distribution/DistributionBoxLarge.tsx create mode 100644 frontend/src/app/graduation/graduation-distribution/GraduationDistribution.module.css create mode 100644 frontend/src/app/graduation/graduation-distribution/GraduationDistribution.tsx create mode 100644 frontend/src/app/graduation/graduation-distribution/InfoButton.module.css create mode 100644 frontend/src/app/graduation/graduation-distribution/InfoButton.tsx create mode 100644 frontend/src/app/graduation/recommendation/Recommendation.module.css create mode 100644 frontend/src/app/graduation/recommendation/Recommendation.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d189040..a6c3835 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,9 +9,12 @@ "version": "0.1.0", "dependencies": { "d3": "^7.9.0", - "next": "15.1.6", + "next": "^15.2.4", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-bootstrap": "^2.10.9", + "react-dom": "^19.0.0", + "react-icons": "^5.5.0", + "react-tooltip": "^5.28.0" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -24,6 +27,18 @@ "typescript": "^5" } }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", @@ -152,6 +167,31 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -556,9 +596,10 @@ } }, "node_modules/@next/env": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.6.tgz", - "integrity": "sha512-d9AFQVPEYNr+aqokIiPLNK/MTyt3DWa/dpKveiAaVccUadFbhFEvY6FXYX2LJO2Hv7PHnLBu2oWwB4uBuHjr/w==" + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.4.tgz", + "integrity": "sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==", + "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { "version": "15.1.6", @@ -570,12 +611,13 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.6.tgz", - "integrity": "sha512-u7lg4Mpl9qWpKgy6NzEkz/w0/keEHtOybmIl0ykgItBxEM5mYotS5PmqTpo+Rhg8FiOiWgwr8USxmKQkqLBCrw==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.4.tgz", + "integrity": "sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -585,12 +627,13 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.6.tgz", - "integrity": "sha512-x1jGpbHbZoZ69nRuogGL2MYPLqohlhnT9OCU6E6QFewwup+z+M6r8oU47BTeJcWsF2sdBahp5cKiAcDbwwK/lg==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.4.tgz", + "integrity": "sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -600,12 +643,13 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.6.tgz", - "integrity": "sha512-jar9sFw0XewXsBzPf9runGzoivajeWJUc/JkfbLTC4it9EhU8v7tCRLH7l5Y1ReTMN6zKJO0kKAGqDk8YSO2bg==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.4.tgz", + "integrity": "sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -615,12 +659,13 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.6.tgz", - "integrity": "sha512-+n3u//bfsrIaZch4cgOJ3tXCTbSxz0s6brJtU3SzLOvkJlPQMJ+eHVRi6qM2kKKKLuMY+tcau8XD9CJ1OjeSQQ==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.4.tgz", + "integrity": "sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -630,12 +675,13 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.6.tgz", - "integrity": "sha512-SpuDEXixM3PycniL4iVCLyUyvcl6Lt0mtv3am08sucskpG0tYkW1KlRhTgj4LI5ehyxriVVcfdoxuuP8csi3kQ==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.4.tgz", + "integrity": "sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -645,12 +691,13 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.6.tgz", - "integrity": "sha512-L4druWmdFSZIIRhF+G60API5sFB7suTbDRhYWSjiw0RbE+15igQvE2g2+S973pMGvwN3guw7cJUjA/TmbPWTHQ==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.4.tgz", + "integrity": "sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -660,12 +707,13 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.6.tgz", - "integrity": "sha512-s8w6EeqNmi6gdvM19tqKKWbCyOBvXFbndkGHl+c9YrzsLARRdCHsD9S1fMj8gsXm9v8vhC8s3N8rjuC/XrtkEg==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.4.tgz", + "integrity": "sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -675,12 +723,13 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.6.tgz", - "integrity": "sha512-6xomMuu54FAFxttYr5PJbEfu96godcxBTRk1OhAvJq0/EnmFU/Ybiax30Snis4vdWZ9LGpf7Roy5fSs7v/5ROQ==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.4.tgz", + "integrity": "sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -733,6 +782,85 @@ "node": ">=12.4.0" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz", + "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz", + "integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@popperjs/core": "^2.11.8", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.5.0", + "@types/warning": "^3.0.3", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.4", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/@restart/hooks": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz", + "integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.14.0" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1044,11 +1172,16 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.0.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz", "integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==", - "dev": true, "dependencies": { "csstype": "^3.0.2" } @@ -1062,6 +1195,21 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==", + "license": "MIT" + }, "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", @@ -1680,6 +1828,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -1757,8 +1911,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/d3": { "version": "7.9.0", @@ -2252,6 +2405,15 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -2273,6 +2435,16 @@ "node": ">=0.10.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3364,6 +3536,15 @@ "node": ">=12" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -3765,8 +3946,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -3890,7 +4070,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -3980,11 +4159,12 @@ "dev": true }, "node_modules/next": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/next/-/next-15.1.6.tgz", - "integrity": "sha512-Hch4wzbaX0vKQtalpXvUiw5sYivBy4cm5rzUKrBnUB/y436LGrvOUqYvlSeNVCWFO/770gDlltR9gqZH62ct4Q==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/next/-/next-15.2.4.tgz", + "integrity": "sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==", + "license": "MIT", "dependencies": { - "@next/env": "15.1.6", + "@next/env": "15.2.4", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", @@ -3999,14 +4179,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.1.6", - "@next/swc-darwin-x64": "15.1.6", - "@next/swc-linux-arm64-gnu": "15.1.6", - "@next/swc-linux-arm64-musl": "15.1.6", - "@next/swc-linux-x64-gnu": "15.1.6", - "@next/swc-linux-x64-musl": "15.1.6", - "@next/swc-win32-arm64-msvc": "15.1.6", - "@next/swc-win32-x64-msvc": "15.1.6", + "@next/swc-darwin-arm64": "15.2.4", + "@next/swc-darwin-x64": "15.2.4", + "@next/swc-linux-arm64-gnu": "15.2.4", + "@next/swc-linux-arm64-musl": "15.2.4", + "@next/swc-linux-x64-gnu": "15.2.4", + "@next/swc-linux-x64-musl": "15.2.4", + "@next/swc-win32-arm64-msvc": "15.2.4", + "@next/swc-win32-x64-msvc": "15.2.4", "sharp": "^0.33.5" }, "peerDependencies": { @@ -4036,7 +4216,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4312,13 +4491,25 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "license": "MIT", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4356,6 +4547,37 @@ "node": ">=0.10.0" } }, + "node_modules/react-bootstrap": { + "version": "2.10.9", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.9.tgz", + "integrity": "sha512-TJUCuHcxdgYpOqeWmRApM/Dy0+hVsxNRFvq2aRFQuxhNi/+ivOxC5OdWIeHS3agxvzJ4Ev4nDw2ZdBl9ymd/JQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.9.4", + "@types/prop-types": "^15.7.12", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-dom": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", @@ -4367,11 +4589,55 @@ "react": "^19.0.0" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "license": "MIT" + }, + "node_modules/react-tooltip": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.28.0.tgz", + "integrity": "sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.1", + "classnames": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -4395,6 +4661,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -5120,6 +5392,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -5135,6 +5422,15 @@ "punycode": "^2.1.0" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7c391cf..a4ff64b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,9 +10,12 @@ }, "dependencies": { "d3": "^7.9.0", - "next": "15.1.6", + "next": "^15.2.4", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-bootstrap": "^2.10.9", + "react-dom": "^19.0.0", + "react-icons": "^5.5.0", + "react-tooltip": "^5.28.0" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/frontend/src/app/graduation/Graduation.module.css b/frontend/src/app/graduation/Graduation.module.css new file mode 100644 index 0000000..eee16ee --- /dev/null +++ b/frontend/src/app/graduation/Graduation.module.css @@ -0,0 +1,74 @@ + +.GraduationPage { + position: relative; + z-index: 1; + + display: flex; + flex-direction: row; + justify-content: flex-start; + + padding: 75px; + padding-top: 120px; + + width: 100%; +} + +.row { + display: flex; + flex-direction: row; +} + +.column { + display: flex; + flex-direction: column; +} + +.containerOverview { + width: 325px; + height: 300px; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); + border-radius: 25px; + padding: 20px; +} + +.overviewSection { + display: flex; + font-size: 18px; + font-weight: 540; + color: grey; + margin-bottom: 8px; + justify-content: space-between; +} + +.th { + text-align: left; + color: #727272; + font-size: 12px; + padding-top: 10px; +} + +.reqsContainer { + padding: 20px; + border-radius: 25px; + width: 410px; + max-height: 500px; + height: auto; + background-color: white; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.25); + } + + .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; +} + diff --git a/frontend/src/app/graduation/graduation-distribution/DistributionBoxLarge.tsx b/frontend/src/app/graduation/graduation-distribution/DistributionBoxLarge.tsx new file mode 100644 index 0000000..979529f --- /dev/null +++ b/frontend/src/app/graduation/graduation-distribution/DistributionBoxLarge.tsx @@ -0,0 +1,37 @@ + +import React from "react"; +// import chroma from "chroma-js" +//import { skillsAreasColors } from '../../utilities/constants'; + +type Props = { + readonly text: string; +}; + +const skillsAreasColors: Record = { + "Hu - Humanities & Arts": ["#7b1fa2", "#f3e5f5"], + "So - Social Sciences": ["#0288d1", "#e1f5fe"], + "Sc - Sciences": ["#43a047", "#e8f5e9"], + "QR - Quantitative Reasoning": ["#c2185b", "#fce4ec"], + "WR - Writing": ["#f57c00", "#fff3e0"], + "L - Language": ["#6d4c41", "#efebe9"], +}; +export default function DistributionBox({ text }: Props) { + const textColor = skillsAreasColors[text][0]; + const backgroundColor = skillsAreasColors[text][1] + + return ( +
+ {text} +
+ ); +} diff --git a/frontend/src/app/graduation/graduation-distribution/GraduationDistribution.module.css b/frontend/src/app/graduation/graduation-distribution/GraduationDistribution.module.css new file mode 100644 index 0000000..7692da4 --- /dev/null +++ b/frontend/src/app/graduation/graduation-distribution/GraduationDistribution.module.css @@ -0,0 +1,20 @@ + +@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap'); + +.containerDistributions { + /*background-color: #f8f9fa;*/ + padding-top: 20px; + /*border-radius: 12px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);*/ +} + +.title_text { + font-family: 'Open Sans'; + font-weight: 550; +} + +.text { + font-family: 'Open Sans'; + font-weight: 650; + color: #727272; +} \ No newline at end of file diff --git a/frontend/src/app/graduation/graduation-distribution/GraduationDistribution.tsx b/frontend/src/app/graduation/graduation-distribution/GraduationDistribution.tsx new file mode 100644 index 0000000..b069796 --- /dev/null +++ b/frontend/src/app/graduation/graduation-distribution/GraduationDistribution.tsx @@ -0,0 +1,319 @@ + +import React from "react"; +import Table from "react-bootstrap/Table"; + +import styles from "./GraduationDistribution.module.css"; + +import DistributionBox from "./DistributionBoxLarge"; +import { StudentCourseIcon } from "../../../components/course-icon/CourseIcon"; +import InfoButton from "./InfoButton"; + +import { StudentCourse } from "@/types/type-user"; + + +// account for credit/d taking + +function getRequirements(type: string, year: number) { + if (type === "Areas") { + if (year === 1) { + return 0; + } else if (year === 2) { + return 1; + } else if (year === 3) { + return 1; + } else if (year === 4) { + return 2; + } + } else if (type === "Skills") { + if (year === 1) { + return 1; + } else if (year === 2) { + return 1; + } else if (year === 3) { + return 2; + } else if (year === 4) { + return 2; + } + } + return 0; +} + +function RenderRowLanguage(year: number, LList: Array){ + + if(year === 2){ + return( + RenderRow("L - Language", 1, LList) + ); + } + + const storedLanguagePlacement = localStorage.getItem("languagePlacement"); + let languagePlacement: string | null; + if(storedLanguagePlacement) { + languagePlacement = storedLanguagePlacement; + }else{ + return
+ } + + // if 3 or 4 + let required: number = 0; + if(languagePlacement === "L1" || languagePlacement === "L2"){ + required = 3; + }else{ + required = 2; + } + + + return( + + + + + = required ? "green" : "red"}}> + {`${LList.length}/${required}`} + + + + {LList.slice(0, required).map((course, index) => ( + + ))} + + + ); +} + +function RenderRow(text: string, required: number, classList: Array ) { + + if(required === 0){ + return( + + + 0/0{/**/} + + + ) + } + + const displayValue = (value: number) => { + switch (value) { + case 0.5: + return "½"; + case 2 / 3: + return "⅔"; + default: + return value; + } + }; + + return( + + + + + = required ? "green" : "red" , paddingLeft: "20px"}}> + {`${displayValue(classList.length)}/${displayValue(required)}`} + + + + {classList.slice(0, required).map((course, index) => ( + + ))} + + + ); +} + +function DistributionTableFirstYear(props: { QRList: StudentCourse[], WRList: StudentCourse[], LList: StudentCourse[] }){ + + let QRLen = props.QRList.length; + let WRLen = props.WRList.length; + let LLen = props.LList.length; + + let QRreq = 0; + let WRreq = 0; + let Lreq = 0; + + if (QRLen > 0 && WRLen > 0 && LLen > 0) { + QRreq = 1; + WRreq = 1; + Lreq = 1; + } else if ((QRLen > 0 && WRLen > 0 && LLen === 0) || + (QRLen > 0 && WRLen === 0 && LLen > 0) || + (QRLen === 0 && WRLen > 0 && LLen > 0)) { + QRreq = QRLen > 0 ? 1 : 0; + WRreq = WRLen > 0 ? 1 : 0; + Lreq = LLen > 0 ? 1 : 0; + } else if ((QRLen > 0 && WRLen === 0 && LLen === 0) || + (QRLen === 0 && WRLen > 0 && LLen === 0) || + (QRLen === 0 && WRLen === 0 && LLen > 0)) { + QRreq = QRLen > 0 ? 1 : 0.5; + WRreq = WRLen > 0 ? 1 : 0.5; + Lreq = LLen > 0 ? 1 : 0.5; + } else { + QRreq = 2 / 3; + WRreq = 2 / 3; + Lreq = 2 / 3; + } + + return( +
+ + + + + + + + + + + {RenderRow("Hu - Humanities & Arts", 0, [])} + {RenderRow("So - Social Sciences", 0, [])} + {RenderRow("Sc - Sciences", 0, [])} + + + + + + + + + {RenderRow("QR - Quantitative Reasoning", QRreq, props.QRList)} + {RenderRow("WR - Writing", WRreq, props.WRList)} + {RenderRow("L - Language", Lreq, props.LList)} + +
+
+ AREAS +
+
CREDITSCOURSES
+
+ SKILLS +
CREDITSCOURSES
+
+ ); +} + +function DistributionTable(props: { year: number; studentCourses: StudentCourse[] }){ + + let HuList: StudentCourse[] = []; + let SoList: StudentCourse[] = []; + let ScList: StudentCourse[] = []; + + let QRList: StudentCourse[] = []; + let WRList: StudentCourse[] = []; + let LList: StudentCourse[] = []; + + props.studentCourses.forEach((studentCourse) => { + const dist = studentCourse.course.dist; + + if (dist && dist.includes('Hu')) { + HuList.push(studentCourse); + } + if (dist && dist.includes('So')) { + SoList.push(studentCourse); + } + if (dist && dist.includes('Sc')) { + ScList.push(studentCourse); + } + + if (dist && dist.includes('QR')) { + QRList.push(studentCourse); + } + if (dist && dist.includes('WR')) { + WRList.push(studentCourse); + } + if (dist && dist.some(skill => skill.startsWith('L'))) { + LList.push(studentCourse); + } + }); + + if(props.year === 1){ + return ( + + ); + } + + return ( +
+ + + + + + + + + + + {RenderRow("Hu - Humanities & Arts", getRequirements("Areas", props.year), HuList)} + {RenderRow("So - Social Sciences", getRequirements("Areas", props.year), SoList)} + {RenderRow("Sc - Sciences", getRequirements("Areas", props.year), ScList)} + + + + + + + + + {RenderRow("QR - Quantitative Reasoning", getRequirements("Skills", props.year), QRList)} + {RenderRow("WR - Writing", getRequirements("Skills", props.year), WRList)} + {RenderRowLanguage(props.year, LList)} + +
+
+ AREAS +
+
CREDITSCOURSES
+
+ SKILLS +
+
CREDITSCOURSES
+
+ ); +} + +function Distribution(props: { currYear: number; alterCurrYear: Function; }){ + + const storedStudentCourses = localStorage.getItem("studentCourses"); + let studentCourses: StudentCourse[] = []; + if(storedStudentCourses) { + studentCourses = JSON.parse(storedStudentCourses) as StudentCourse[]; + } + + return ( +
+
+
+ Distributions +
+
+ {[1, 2, 3, 4].map((year) => ( + + ))} +
+
+ +
+ +
+
+ ); +} + +export default Distribution; diff --git a/frontend/src/app/graduation/graduation-distribution/InfoButton.module.css b/frontend/src/app/graduation/graduation-distribution/InfoButton.module.css new file mode 100644 index 0000000..83644ae --- /dev/null +++ b/frontend/src/app/graduation/graduation-distribution/InfoButton.module.css @@ -0,0 +1,9 @@ +.infoButton { + display: flex; + margin-left: 3px; + margin-top: 0px; +} + +.infoButton:hover { + cursor: pointer; +} \ No newline at end of file diff --git a/frontend/src/app/graduation/graduation-distribution/InfoButton.tsx b/frontend/src/app/graduation/graduation-distribution/InfoButton.tsx new file mode 100644 index 0000000..5dfa0c6 --- /dev/null +++ b/frontend/src/app/graduation/graduation-distribution/InfoButton.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { GoInfo } from "react-icons/go"; +import "react-tooltip/dist/react-tooltip.css"; +import { Tooltip } from "react-tooltip"; +import styles from "./InfoButton.module.css"; + +export default function InfoButton(props: { text: string, size?: number}) { + return ( +
+
+ +
+ +
+ ); + } \ No newline at end of file diff --git a/frontend/src/app/graduation/page.tsx b/frontend/src/app/graduation/page.tsx index 15de9bc..1ba2db8 100644 --- a/frontend/src/app/graduation/page.tsx +++ b/frontend/src/app/graduation/page.tsx @@ -1,10 +1,41 @@ -import NavBar from "@/components/navbar/NavBar" +"use client"; +import React, { useState, useEffect } from "react"; +import Style from "./Graduation.module.css"; -export default function Graduation(){ - return( -
- +import { useAuth } from "../providers"; +import { StudentSemester } from "@/types/type-user"; + +import NavBar from "@/components/navbar/NavBar"; +import GraduationDistribution from "./graduation-distribution/GraduationDistribution"; +import Recommendation from "./recommendation/Recommendation"; + +function Graduation() +{ + const {user, setUser} = useAuth(); + + const UserYear = () => { + return 2; + }; + const [currYear, setCurrYear] = useState(UserYear()); + const alterCurrYear = (num: number) => { + setCurrYear(num); + }; + +return ( +
+ +
+
+
+ {/* */} + + +
+
- ) +
+); } + +export default Graduation; \ No newline at end of file diff --git a/frontend/src/app/graduation/recommendation/Recommendation.module.css b/frontend/src/app/graduation/recommendation/Recommendation.module.css new file mode 100644 index 0000000..4c4669f --- /dev/null +++ b/frontend/src/app/graduation/recommendation/Recommendation.module.css @@ -0,0 +1,22 @@ +@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap'); + +.RecommendationContainer { + display: flex; + flex-direction: column; + margin-bottom: 40px; + } + + .RecommendationList { + padding-top: 20px; + } + + .title_text { + font-family: 'Open Sans'; + font-weight: 550; + } + + .text { + font-family: 'Open Sans'; + font-weight: 650; + color: #727272; + } \ No newline at end of file diff --git a/frontend/src/app/graduation/recommendation/Recommendation.tsx b/frontend/src/app/graduation/recommendation/Recommendation.tsx new file mode 100644 index 0000000..0a12e15 --- /dev/null +++ b/frontend/src/app/graduation/recommendation/Recommendation.tsx @@ -0,0 +1,65 @@ +import styles from "./Recommendation.module.css"; +import { StudentCourse } from "@/types/type-user"; + +import { useAuth } from "../../providers"; + +import { FaExclamationTriangle, FaCheckCircle } from "react-icons/fa"; + + +type RecommendationProps = { + user: any; + currYear: number; + }; + + // hold a list of recommendations, each index is a tuple with 3 parts + // logo (either warning or check), text, recommended courses to put below + + const recommendations = [ + ["warning", "You need to take a WR course in your Sophomore spring. Students with similar interests loved these courses:", []], + ["warning", "You have 10/12 credits remaining in your major to do in 5 semesters.", []], + ["good", "Your language requirement is 2/3 complete."] + ]; + + export function RecommendationList() { + return ( +
+ {recommendations.map(([type, message], index) => ( +
+ {type === "warning" ? ( + + ) : ( + + )} + {message} +
+ ))} +
+ ); + } + +function Recommendation({ user, currYear }: RecommendationProps) +{ + +return ( +
+

Hey {user.name}! We have some...

+
+ Recommendations +
+ +
+ ); +} + +export default Recommendation; diff --git a/frontend/src/app/majors/Majors.module.css b/frontend/src/app/majors/Majors.module.css index df741d6..ce36149 100644 --- a/frontend/src/app/majors/Majors.module.css +++ b/frontend/src/app/majors/Majors.module.css @@ -1,11 +1,75 @@ -.MajorsPage { - position: absolute; +.GraduationPage { + position: relative; + z-index: 1; display: flex; flex-direction: row; + justify-content: center; + padding-top: 100px; + + top: 75px; + + width: 100%; +} + +.row { + display: flex; + flex-direction: row; +} + +.column { + display: flex; + flex-direction: column; +} + +.containerOverview { + width: 325px; + height: 300px; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); + border-radius: 25px; + padding: 20px; +} + +.overviewSection { + display: flex; + font-size: 18px; + font-weight: 540; + color: grey; + margin-bottom: 8px; + justify-content: space-between; +} + +/*th { + text-align: left; + color: #727272; + font-size: 12px; + padding-top: 10px; +}*/ - padding: 20px calc(50% - 500px); - margin-top: 100px; +.reqsContainer { + padding: 20px; + border-radius: 25px; + width: 410px; + max-height: 500px; + height: auto; + background-color: white; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.25); } + +.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; +} + diff --git a/frontend/src/components/course-icon/CourseIcon.tsx b/frontend/src/components/course-icon/CourseIcon.tsx index cdaf3f3..22f2dcf 100644 --- a/frontend/src/components/course-icon/CourseIcon.tsx +++ b/frontend/src/components/course-icon/CourseIcon.tsx @@ -4,7 +4,7 @@ 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 fall from "../../../public/fall.svg"; import DistributionCircle from "../distribution-circle/DistributionsCircle"; // import { useModal } from "../../../hooks/modalContext"; From 8efda5d6d430f0e84fafdfd49e15ac5a45fe53fe Mon Sep 17 00:00:00 2001 From: andycheng233 Date: Mon, 14 Apr 2025 23:10:38 -0400 Subject: [PATCH 3/3] some small updates for graduation page, degree overview section --- .../src/app/graduation/Graduation.module.css | 1 + .../DegreeOverview.module.css | 41 +++++++++++++ .../DegreeOverview.tsx | 60 +++++++++++++++++++ frontend/src/app/graduation/page.tsx | 4 +- 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 frontend/src/app/graduation/graduation-distribution/DegreeOverview.module.css create mode 100644 frontend/src/app/graduation/graduation-distribution/DegreeOverview.tsx diff --git a/frontend/src/app/graduation/Graduation.module.css b/frontend/src/app/graduation/Graduation.module.css index eee16ee..d4f3d92 100644 --- a/frontend/src/app/graduation/Graduation.module.css +++ b/frontend/src/app/graduation/Graduation.module.css @@ -16,6 +16,7 @@ .row { display: flex; flex-direction: row; + gap: 50px; } .column { diff --git a/frontend/src/app/graduation/graduation-distribution/DegreeOverview.module.css b/frontend/src/app/graduation/graduation-distribution/DegreeOverview.module.css new file mode 100644 index 0000000..9a39b7f --- /dev/null +++ b/frontend/src/app/graduation/graduation-distribution/DegreeOverview.module.css @@ -0,0 +1,41 @@ +@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap'); + +.reqsContainer { + width: 300px; + aspect-ratio: 6 / 6.5; /* width : height = 300 : 350 */ + border-radius: 12px; /* smooth corners */ + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); /* soft shadow */ + background-color: white; /* optional, but helps shadow stand out */ + padding: 1rem; /* optional, adds space inside the container */ + padding-left: 20px; +} + +.column +{ + display:flex; + flex-direction: column; +} + +.row +{ + display:flex; + flex-direction: row; +} + +.title_text { + font-family: 'Open Sans'; + font-weight: 550; + } + + .text { + font-family: 'Open Sans'; + font-weight:600; + color: #727272; + } + +/* major, distributions, total */ + .header { + display: flex; + flex-direction: row; + gap: 66%; + } \ No newline at end of file diff --git a/frontend/src/app/graduation/graduation-distribution/DegreeOverview.tsx b/frontend/src/app/graduation/graduation-distribution/DegreeOverview.tsx new file mode 100644 index 0000000..ddcf87b --- /dev/null +++ b/frontend/src/app/graduation/graduation-distribution/DegreeOverview.tsx @@ -0,0 +1,60 @@ + +import React from "react"; +import styles from "./DegreeOverview.module.css"; + +//import { Degree } from "@/types/type-user"; +// import { StudentCourse } from "../../../commons/types/TypeCourse"; + +// import ProgramRequirementsBox from "../../Majors/components/ProgramRequirementsBox"; + +// function DegreeTopshelf(props: { degree: Degree }) { +// return( +//
+// Degree ⚙ +//
+//
+// {props.degree.metadata.degreeType === "BACH_ART" ? ( +// B.A. +// ) : ( +// props.degree.metadata.degreeType +// )} +// {props.degree.metadata.name} +//
+//
+//
+// ); +// } + +//for major, distributions, total... +function createHeader(header_name: string, curr_credits: number, total_credits: number) { + return( +
+ {header_name} + {curr_credits}/{total_credits} +
+ ); +} + +function DegreeOverview(/*props: { degree: Degree }*/){ + + // const storedStudentCourses = localStorage.getItem("studentCourses"); + // let studentCourses: StudentCourse[] = []; + // let studentCodes: Set = new Set(); + // if (storedStudentCourses) { + // studentCourses = JSON.parse(storedStudentCourses) as StudentCourse[]; + // studentCodes = new Set(studentCourses.flatMap(studentCourse => studentCourse.course.codes)); + // } + + return( +
+ {/* + */} +
+ Overview + {createHeader("MAJOR", 5, 12)} +
+
+ ); +} + +export default DegreeOverview; diff --git a/frontend/src/app/graduation/page.tsx b/frontend/src/app/graduation/page.tsx index 1ba2db8..09f1819 100644 --- a/frontend/src/app/graduation/page.tsx +++ b/frontend/src/app/graduation/page.tsx @@ -9,6 +9,7 @@ import { StudentSemester } from "@/types/type-user"; import NavBar from "@/components/navbar/NavBar"; import GraduationDistribution from "./graduation-distribution/GraduationDistribution"; import Recommendation from "./recommendation/Recommendation"; +import DegreeOverview from "./graduation-distribution/DegreeOverview"; function Graduation() { @@ -26,11 +27,12 @@ return (
-
{/* */} +
+