diff --git a/src/app/(main)/course-progression/page.jsx b/src/app/(main)/course-progression/page.jsx new file mode 100644 index 0000000..114cead --- /dev/null +++ b/src/app/(main)/course-progression/page.jsx @@ -0,0 +1,1208 @@ +"use client"; +import React, { useState, useEffect, useCallback, useMemo } from "react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + cseCurriculum, + getTotalCredits as getCseTotalCredits, + prerequisiteOverrides as csePrerequisiteOverrides, +} from "@/constants/cseCurriculum"; +import { + csCurriculum, + getTotalCredits as getCsTotalCredits, + prerequisiteOverrides as csPrerequisiteOverrides, +} from "@/constants/csCurriculum"; +import { + microbiologyCurriculum, + getTotalCredits as getMicrobioTotalCredits, + prerequisiteOverrides as microbioPrerequisiteOverrides, +} from "@/constants/microbioCurriculum"; +import { CheckCircle2, ChevronDown, ChevronUp, Lock, Search, Unlock, RefreshCw, Undo2 } from "lucide-react"; + +const allDepartments = [ + { code: "CSE", name: "Computer Science & Engineering" }, + { code: "CS", name: "Computer Science" }, + { code: "MIC", name: "Microbiology" }, + { code: "ARCH", name: "Architecture" }, + { code: "BBA", name: "Business Administration" }, + { code: "LAW", name: "Law" }, +]; + +const departments = allDepartments.filter((department) => !["ARCH", "BBA", "LAW"].includes(department.code)); + +const departmentCurricula = { + CSE: cseCurriculum, + CS: csCurriculum, + MIC: microbiologyCurriculum, +}; + +const departmentPrerequisiteOverrides = { + CSE: csePrerequisiteOverrides, + CS: csPrerequisiteOverrides, + MIC: microbioPrerequisiteOverrides, +}; + +const supportedDepartments = new Set(["CSE", "CS", "MIC"]); + +function getCurriculumForDepartment(code) { + return departmentCurricula[code] ?? []; +} + +function getPrerequisiteOverridesForDepartment(code) { + return departmentPrerequisiteOverrides[code] ?? csePrerequisiteOverrides; +} + +function getTotalCreditsForDepartment(code, curriculum) { + if (code === "CS") return getCsTotalCredits(curriculum); + if (code === "MIC") return getMicrobioTotalCredits(curriculum); + return getCseTotalCredits(curriculum); +} + +function getCompletedCoursesForCurriculum(curriculum, completedCourses) { + return completedCourses.reduce((total, courseCode) => { + for (const section of curriculum) { + let courses = []; + if (section.courses) courses = section.courses; + else if (section.streams) courses = section.streams.flatMap((stream) => stream.courses); + + const course = courses.find((c) => c.code === courseCode); + if (course) return total + Number(course.credits ?? 3); + } + + return total; + }, 0); +} + +function getSectionAnchorId(sectionName) { + return `section-${sectionName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`; +} + +function getCourseCardId(courseCode) { + return `course-card-${courseCode.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`; +} + +function normalizeSearchText(value) { + return value?.toString().trim().toLowerCase() ?? ""; +} + +function getSearchTokens(query) { + return normalizeSearchText(query) + .split(/\s+/) + .filter(Boolean); +} + +function findCourseMatches(courses, query) { + const searchTokens = getSearchTokens(query); + + if (searchTokens.length === 0) return []; + + return courses + .map((course) => { + const code = normalizeSearchText(course.code); + const name = normalizeSearchText(course.name); + const sectionName = normalizeSearchText(course.sectionName); + const streamName = normalizeSearchText(course.streamName); + const alternatives = Array.isArray(course.alternatives) ? course.alternatives : []; + + const courseText = [code, name, sectionName, streamName].filter(Boolean).join(" "); + + const matchedAlternatives = alternatives.filter((alternative) => { + const normalizedAlternative = normalizeSearchText(alternative); + return searchTokens.some((token) => normalizedAlternative.includes(token)); + }); + + const matchesQuery = + searchTokens.some((token) => courseText.includes(token)) || matchedAlternatives.length > 0; + + if (!matchesQuery) return null; + + return { + course, + matchedAlternatives, + locationLabel: course.streamName ? `${course.sectionName} · ${course.streamName}` : course.sectionName, + }; + }) + .filter(Boolean); +} + +//! MARK: Helpers +function areCourseListsEqual(left, right) { + if (left.length !== right.length) return false; + return left.every((courseCode, index) => courseCode === right[index]); +} + +function addUndoSnapshot(setUndoStackByDept, dept, snapshot) { + setUndoStackByDept((prev) => { + const history = prev[dept] ?? []; + const lastSnapshot = history[history.length - 1]; + + if (lastSnapshot && areCourseListsEqual(lastSnapshot, snapshot)) { + return prev; + } + + return { + ...prev, + [dept]: [...history, snapshot].slice(-10), + }; + }); +} + +//! MARK: Prereq Parsing +function parsePrereqString(prereqStr) { + if (!prereqStr || typeof prereqStr !== "string" || !prereqStr.trim()) return null; + + let cleaned = prereqStr.trim(); + + if (cleaned.startsWith("(") && cleaned.endsWith(")")) { + const inner = cleaned.slice(1, -1); + if (inner.match(/^[A-Z]{3,}[0-9]{3,}$/)) { + return inner; + } + cleaned = inner; + } + + const tokens = []; + const regex = /\(|\)|AND|OR|[A-Z]{3,}[0-9]{3,}/gi; + let match; + + while ((match = regex.exec(cleaned)) !== null) { + tokens.push(match[0].toUpperCase()); + } + + if (tokens.length === 0) return null; + + let pos = 0; + + function parseExpression() { + let args = []; + let currentOp = null; + + while (pos < tokens.length) { + const token = tokens[pos]; + + if (token === "(") { + pos++; + args.push(parseExpression()); + } else if (token === ")") { + pos++; + break; + } else if (token === "AND" || token === "OR") { + currentOp = token; + pos++; + } else { + args.push(token); + pos++; + } + } + + if (args.length === 0) return null; + if (args.length === 1) return args[0]; + if (!currentOp && args.length > 1) return { op: "AND", args }; + return { op: currentOp, args }; + } + + return parseExpression(); +} + +//! MARK: Prereq Tree +function getAllPrerequisiteCodes(prereqTree) { + if (!prereqTree) return []; + if (typeof prereqTree === "string") return [prereqTree]; + if (Array.isArray(prereqTree)) return prereqTree.flatMap(getAllPrerequisiteCodes); + if (prereqTree.args) return prereqTree.args.flatMap(getAllPrerequisiteCodes); + return []; +} + +function getDependentCompletedCourses(courseCode, completedCourses, courseMap) { + const toRemove = new Set([courseCode]); + const completedSet = new Set(completedCourses); + let changed = true; + + while (changed) { + changed = false; + + for (const completedCourse of completedCourses) { + if (toRemove.has(completedCourse)) continue; + + const prereqCodes = getAllPrerequisiteCodes(courseMap[completedCourse]?.prereqTree); + const dependsOnRemovedCourse = prereqCodes.some((prereqCode) => toRemove.has(prereqCode)); + + if (dependsOnRemovedCourse && completedSet.has(completedCourse)) { + toRemove.add(completedCourse); + changed = true; + } + } + } + + return toRemove; +} + +//! MARK: Prereq Satisfaction +function arePrerequisitesSatisfied(prereqTree, completedCourses) { + if (!prereqTree) return true; + + function evaluate(expr) { + if (typeof expr === "string") { + return completedCourses.includes(expr); + } + if (expr.op === "AND") { + return expr.args.every(evaluate); + } + if (expr.op === "OR") { + return expr.args.some(evaluate); + } + return false; + } + + return evaluate(prereqTree); +} + +//! MARK: Course Data Fetch +//! CDN +function useConnectCDN(prereqOverrides = csePrerequisiteOverrides) { + const [courseMap, setCourseMap] = useState({}); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + async function fetchData() { + setLoading(true); + setError(null); + try { + const res = await fetch("https://usis-cdn.eniamza.com/connect.json"); + if (!res.ok) throw new Error("Failed to fetch connect.json"); + const data = await res.json(); + + const map = {}; + for (const c of data) { + if (!map[c.courseCode]) { + const overridePrereq = prereqOverrides[c.courseCode]; + const effectivePrereqRaw = overridePrereq ?? c.prerequisiteCourses; + const effectivePrereqTree = parsePrereqString(effectivePrereqRaw); + + map[c.courseCode] = { + code: c.courseCode, + name: c.courseName, + credits: c.courseCredit, + prereqRaw: effectivePrereqRaw, + prereqTree: effectivePrereqTree, + allPrereqs: getAllPrerequisiteCodes(effectivePrereqTree), + }; + } + } + setCourseMap(map); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + } + fetchData(); + }, [prereqOverrides]); + + return { courseMap, loading, error }; +} + +//! MARK: Course Card +function CourseCard({ + course, + isCompleted, + isAvailable, + isSelected, + isHighlighted, + onClick, + onPrereqClick, + courseDetails, + searchQuery, +}) { + const [showTooltip, setShowTooltip] = useState(false); + const normalizedSearchQuery = normalizeSearchText(searchQuery); + const hasAlternatives = Array.isArray(course.alternatives) && course.alternatives.length > 0; + + let statusColor = "border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800"; + let statusIcon = null; + let statusText = ""; + + if (isCompleted) { + statusColor = "border-green-500 bg-green-50 dark:bg-green-900/20 hover:border-green-600"; + statusIcon = ; + statusText = "Completed (Click to undo)"; + } else if (isAvailable) { + statusColor = "border-yellow-500 bg-yellow-50 dark:bg-yellow-900/20 hover:border-yellow-600"; + statusIcon = ; + statusText = "Available (Click to complete)"; + } else { + statusColor = "border-gray-300 dark:border-gray-800 bg-gray-200 dark:bg-gray-900/50 text-black dark:text-white"; + statusIcon = ; + statusText = "Locked - Prerequisites needed"; + } + + const highlightClass = isHighlighted ? "course-card-flash ring-2 ring-blue-500 ring-offset-2 ring-offset-white dark:ring-offset-gray-900 z-10" : ""; + + return ( +
onClick(course.code)} + onMouseEnter={() => setShowTooltip(true)} + onMouseLeave={() => setShowTooltip(false)} + title="Click to complete or undo. Prerequisite tags jump to courses." + > +
+
+
{course.code}
+
+ {course.name || courseDetails?.name || "Course"} +
+
{course.credits ?? courseDetails?.credits ?? 3} credits
+ + {courseDetails?.allPrereqs?.length > 0 && ( +
+ {courseDetails.allPrereqs.map((prereq) => ( + + ))} +
+ )} + + {hasAlternatives && ( +
+
+ Includes +
+
+ {course.alternatives.slice(0, 4).map((alternative) => { + const isHighlightedAlternative = normalizeSearchText(alternative) === normalizedSearchQuery; + + return ( + + {alternative} + + ); + })} + {course.alternatives.length > 4 && ( + + +{course.alternatives.length - 4} more + + )} +
+
+ )} +
+
{statusIcon}
+
+ + {/* Tooltip */} + {showTooltip && ( +
+ {statusText} + {isSelected &&
Selected
} + {courseDetails?.allPrereqs?.length > 0 && !isCompleted && !isAvailable && ( +
Requires: {courseDetails.allPrereqs.join(", ")}
+ )} + {courseDetails?.allPrereqs?.length > 0 && ( +
Click prerequisite tags to jump
+ )} + {isCompleted && courseDetails?.allPrereqs?.length > 0 && ( +
Note: Uncompleting will lock dependent courses
+ )} +
+
+ )} +
+ ); +} + +//! MARK: SectionCourses +function SectionCourses({ + section, + curriculum, + courseMap, + loading, + error, + completedCourses, + highlightedCourseCode, + onCourseToggle, + onCourseUntoggle, + onPrereqClick, + searchQuery, +}) { + const [selectedCourse, setSelectedCourse] = useState(null); + + const sectionObj = curriculum.find((s) => s.section === section); + + if (!sectionObj) return
Section not found.
; + + // Get all courses in this section + let allCourses = []; + if (sectionObj.courses) { + allCourses = sectionObj.courses; + } else if (sectionObj.streams) { + allCourses = sectionObj.streams.flatMap((stream) => stream.courses); + } + + // Calculate course statuses based on prerequisites + const getCourseStatus = useCallback( + (courseCode) => { + const isCompleted = completedCourses.includes(courseCode); + + if (isCompleted) return "completed"; + + // Check if prerequisites are satisfied + const courseDetails = courseMap[courseCode]; + if (courseDetails && courseDetails.prereqTree) { + const prerequisitesMet = arePrerequisitesSatisfied(courseDetails.prereqTree, completedCourses); + if (prerequisitesMet) return "available"; + } else if (!courseDetails || !courseDetails.prereqTree) { + // No prerequisites means it's always available + return "available"; + } + + return "locked"; + }, + [completedCourses, courseMap], + ); + + // Handle course click + const handleCourseClick = (courseCode) => { + const status = getCourseStatus(courseCode); + + // Toggle completed state directly for any course. + if (status === "completed") { + onCourseUntoggle(courseCode); + if (selectedCourse === courseCode) { + setSelectedCourse(null); + } + } else { + onCourseToggle(courseCode); + setSelectedCourse(courseCode); + } + }; + + if (loading) { + return ( +
+
+
+

Loading course data...

+
+
+ ); + } + + if (error) { + return ( +
Error loading course data: {error}
+ ); + } + + return ( +
+ {/* Streams/Categories */} + {sectionObj.streams ? ( +
+ {sectionObj.streams.map((stream) => ( +
+
+

{stream.name}

+ + {stream.credits} credits + +
+ {stream.note &&

{stream.note}

} +
+ {stream.courses.map((course) => { + const status = getCourseStatus(course.code); + return ( + + ); + })} +
+
+ ))} +
+ ) : ( +
+ {allCourses.map((course) => { + const status = getCourseStatus(course.code); + return ( + + ); + })} +
+ )} +
+ ); +} + +//! MARK: Page State +export default function CourseProgressionPage() { + const [selectedDept, setSelectedDept] = useState(null); + const [showComingSoon, setShowComingSoon] = useState(false); + const [completedCoursesByDept, setCompletedCoursesByDept] = useState({}); + const [undoStackByDept, setUndoStackByDept] = useState({}); + const [highlightedCourseCode, setHighlightedCourseCode] = useState(null); + const [courseSearchDraft, setCourseSearchDraft] = useState(""); + const [courseSearchQuery, setCourseSearchQuery] = useState(""); + const [showProgressPanel, setShowProgressPanel] = useState(false); + + const activeCurriculum = getCurriculumForDepartment(selectedDept); + const activePrerequisiteOverrides = getPrerequisiteOverridesForDepartment(selectedDept); + const completedCourses = selectedDept ? (completedCoursesByDept[selectedDept] ?? []) : []; + const undoStack = selectedDept ? (undoStackByDept[selectedDept] ?? []) : []; + + // Use the selected department's prerequisite graph to auto-mark prerequisite chains when selecting a course. + const { courseMap, loading, error } = useConnectCDN(activePrerequisiteOverrides); + + const handleDeptClick = (code) => { + if (supportedDepartments.has(code)) { + setSelectedDept(code); + setShowComingSoon(false); + } else { + setSelectedDept(code); + setShowComingSoon(true); + } + }; + + // Mark as completed + const handleCourseToggle = (courseCode) => { + if (!selectedDept || !supportedDepartments.has(selectedDept)) return; + + const currentCompleted = completedCoursesByDept[selectedDept] ?? []; + const next = new Set(currentCompleted); + const stack = [courseCode]; + + while (stack.length > 0) { + const code = stack.pop(); + if (!code || next.has(code)) continue; + + next.add(code); + + const prereqCodes = getAllPrerequisiteCodes(courseMap[code]?.prereqTree); + for (const prereqCode of prereqCodes) { + if (!next.has(prereqCode)) { + stack.push(prereqCode); + } + } + } + + const nextCompleted = Array.from(next); + + if (areCourseListsEqual(currentCompleted, nextCompleted)) { + return; + } + + addUndoSnapshot(setUndoStackByDept, selectedDept, currentCompleted); + setCompletedCoursesByDept((prev) => ({ + ...prev, + [selectedDept]: nextCompleted, + })); + }; + + // Mark as incomplete and remove any completed courses that depend on it. + const handleCourseUntoggle = (courseCode) => { + if (!selectedDept || !supportedDepartments.has(selectedDept)) return; + + const currentCompleted = completedCoursesByDept[selectedDept] ?? []; + + if (!currentCompleted.includes(courseCode)) { + return; + } + + const toRemove = getDependentCompletedCourses(courseCode, currentCompleted, courseMap); + const nextCompleted = currentCompleted.filter((c) => !toRemove.has(c)); + + addUndoSnapshot(setUndoStackByDept, selectedDept, currentCompleted); + setCompletedCoursesByDept((prev) => ({ + ...prev, + [selectedDept]: nextCompleted, + })); + }; + + const handleUndo = () => { + if (!selectedDept || !supportedDepartments.has(selectedDept)) return; + + const history = undoStackByDept[selectedDept] ?? []; + if (history.length === 0) return; + + const previousCompleted = history[history.length - 1]; + + setCompletedCoursesByDept((prev) => ({ + ...prev, + [selectedDept]: previousCompleted, + })); + + setUndoStackByDept((prev) => ({ + ...prev, + [selectedDept]: history.slice(0, -1), + })); + }; + + // Reset all progress + const handleReset = () => { + if (!selectedDept || !supportedDepartments.has(selectedDept)) return; + + if (window.confirm("Are you sure you want to reset all your progress? This cannot be undone.")) { + setUndoStackByDept((prev) => ({ + ...prev, + [selectedDept]: [...(prev[selectedDept] ?? []), completedCourses].slice(-10), + })); + + setCompletedCoursesByDept((prev) => ({ + ...prev, + [selectedDept]: [], + })); + } + }; + + const activeSections = activeCurriculum.map((s) => ({ + name: s.section, + credits: s.credits, + description: s.description, + referenceLink: s.referenceLink, + })); + + const allSectionCourses = useMemo( + () => + activeCurriculum.flatMap((section) => { + if (section.courses) { + return section.courses.map((course) => ({ + ...course, + sectionName: section.section, + streamName: null, + })); + } + + if (section.streams) { + return section.streams.flatMap((stream) => + stream.courses.map((course) => ({ + ...course, + sectionName: section.section, + streamName: stream.name, + })), + ); + } + + return []; + }), + [activeCurriculum], + ); + + const courseSearchMatches = useMemo(() => findCourseMatches(allSectionCourses, courseSearchQuery), [allSectionCourses, courseSearchQuery]); + + const availableCourses = useMemo(() => { + const completedSet = new Set(completedCourses); + + return allSectionCourses.filter((course) => { + if (completedSet.has(course.code)) return false; + + const prereqTree = courseMap[course.code]?.prereqTree; + if (!prereqTree) return true; + + return arePrerequisitesSatisfied(prereqTree, completedCourses); + }); + }, [allSectionCourses, courseMap, completedCourses]); + + const handleJumpToCourse = useCallback((sectionName, courseCode) => { + setHighlightedCourseCode(courseCode); + const cardEl = document.getElementById(getCourseCardId(courseCode)); + if (cardEl) { + cardEl.scrollIntoView({ behavior: "smooth", block: "center" }); + return; + } + + const sectionEl = document.getElementById(getSectionAnchorId(sectionName)); + if (sectionEl) { + sectionEl.scrollIntoView({ behavior: "smooth", block: "start" }); + } + }, []); + + const handlePrereqJump = useCallback((courseCode) => { + setHighlightedCourseCode(courseCode); + const cardEl = document.getElementById(getCourseCardId(courseCode)); + if (cardEl) { + cardEl.scrollIntoView({ behavior: "smooth", block: "center" }); + } + }, []); + + const handleCourseSearchSubmit = useCallback( + (event) => { + event.preventDefault(); + + const query = courseSearchDraft.trim(); + setCourseSearchQuery(query); + + if (!query) { + setHighlightedCourseCode(null); + return; + } + + const matches = findCourseMatches(allSectionCourses, query); + const target = matches[0]; + + if (!target) return; + + setHighlightedCourseCode(target.course.code); + + const cardEl = document.getElementById(getCourseCardId(target.course.code)); + if (cardEl) { + cardEl.scrollIntoView({ behavior: "smooth", block: "center" }); + } + }, + [allSectionCourses, courseSearchDraft], + ); + + useEffect(() => { + if (!highlightedCourseCode || courseSearchQuery.trim()) return; + + const timer = setTimeout(() => { + setHighlightedCourseCode(null); + }, 2600); + + return () => clearTimeout(timer); + }, [highlightedCourseCode, courseSearchQuery]); + + useEffect(() => { + setCourseSearchDraft(""); + setCourseSearchQuery(""); + setHighlightedCourseCode(null); + }, [selectedDept]); + + // Calculate total completed credits + const totalCompletedCredits = getCompletedCoursesForCurriculum(activeCurriculum, completedCourses); + + const totalCredits = selectedDept ? getTotalCreditsForDepartment(selectedDept, activeCurriculum) : 0; + const progressPercent = totalCredits > 0 ? Math.min(100, Math.round((totalCompletedCredits / totalCredits) * 100)) : 0; + const coursesLeft = Math.max(0, allSectionCourses.length - completedCourses.length); + const canUndo = undoStack.length > 0; + const canReset = completedCourses.length > 0; + + //! MARK: Bottom Panel + return ( +
+
+
+

+ Track Your{" "} + + Course Progression + +

+

+ Click any Available course to + mark it complete. Click a{" "} + Completed course to undo it; + dependent courses will become{" "} + Locked again automatically. +

+
+ + + Completed + + + + Available + + + + Locked + +
+
+ + How it works + + + + + +
+
Select a department to begin
+ +
+ +
+ Click courses to complete or undo them +
+ +
+ +
+ Undo reverses up to 10 recent steps +
+ +
+ +
+ Click Mini CourseCode Chips to jump quickly to the specific courses +
+
+
+
+ + {/* Department Selection */} +
+ {departments.map((dept) => ( + + ))} +
+ + {/* Main Content */} +
+ {!selectedDept ? ( +
+ + Select a department to view its course progression. + +
+ ) : showComingSoon ? ( +
+ + {departments.find((d) => d.code === selectedDept)?.name} outline coming soon... + +
+ ) : ( +
+
+
+
+ + setCourseSearchDraft(event.target.value)} + placeholder="Find a course, stream, or code like HUM101" + className="w-full rounded-lg border border-gray-300 bg-white py-2.5 pl-10 pr-3 text-sm text-gray-900 shadow-sm outline-none transition focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-gray-700 dark:bg-gray-900 dark:text-white" + /> +
+
+ + +
+
+ +
+ {courseSearchQuery.trim() ? ( + courseSearchMatches.length > 0 ? ( + <> +

+ Showing {courseSearchMatches.length} match{courseSearchMatches.length > 1 ? "es" : ""} +

+
+ {courseSearchMatches.slice(0, 6).map((match) => ( + + ))} +
+ + ) : ( +

+ No match found. Try a course code, title, or stream name. +

+ ) + ) : ( +

+ Search by code, title, or a hidden option like HUM101 to jump to the stream that contains it. +

+ )} +
+
+ + {activeSections.map((section) => ( +
+
+

{section.name}

+ + {section.credits} credits + +
+ {section.description && ( +

{section.description}

+ )} + {section.referenceLink && ( +

+ + Reference Link + +

+ )} + +
+ ))} +
+ )} +
+
+ + {selectedDept && ( + <> + {!showProgressPanel ? ( +
+
+ {supportedDepartments.has(selectedDept) && canUndo && ( + + )} + + + + {supportedDepartments.has(selectedDept) && canReset && ( + + )} +
+
+ ) : ( +
+
+
+
+
+
+ {supportedDepartments.has(selectedDept) && canUndo && ( + + )} +
+ +
+ {supportedDepartments.has(selectedDept) && canReset && ( + + )} + +
+
+ +
+
+
+ {progressPercent}% +
+
+ +
+

+ Courses{" "} + + {completedCourses.length} + +

+

+ Courses left{" "} + + {coursesLeft} + +

+

+ Credits{" "} + + {totalCompletedCredits} / {totalCredits} + +

+
+
+ +
+
+

+ Available now + + {availableCourses.length} + +

+
+ +

+ Click any course code to jump to that course. +

+ +
+ {supportedDepartments.has(selectedDept) && availableCourses.length > 0 ? ( + availableCourses.map((course) => ( + + )) + ) : ( + + No available courses right now. + + )} +
+ + {!supportedDepartments.has(selectedDept) && ( +

+ Progress tracking for this department is not configured yet. +

+ )} +
+
+
+
+
+ )} + + )} +
+ ); +} diff --git a/src/app/globals.css b/src/app/globals.css index 563ef1d..3edba6e 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -167,3 +167,27 @@ .dark .faculty-dropdown-scroll::-webkit-scrollbar-thumb:hover { background: #2563eb; } + +@keyframes course-card-flash { + 0% { + box-shadow: 0 0 0 0 rgba(255, 255, 255, 0), 0 0 0 0 rgba(255, 255, 255, 0); + filter: brightness(1); + transform: scale(1); + } + + 50% { + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.95), 0 0 30px 8px rgba(255, 255, 255, 0.75); + filter: brightness(1.08); + transform: scale(1.01); + } + + 100% { + box-shadow: 0 0 0 0 rgba(255, 255, 255, 0), 0 0 0 0 rgba(255, 255, 255, 0); + filter: brightness(1); + transform: scale(1); + } +} + +.course-card-flash { + animation: course-card-flash 2000ms ease-out; +} diff --git a/src/constants/csCurriculum.js b/src/constants/csCurriculum.js new file mode 100644 index 0000000..9c18992 --- /dev/null +++ b/src/constants/csCurriculum.js @@ -0,0 +1,286 @@ +/* +! MARK: Data Structure Tree +! Adding This Here So Someone Else Understands What The Actual Fuck This Data Structure Is Supposed To Look Like +? And Of Course- For Future References +? But Mainly Because I believe I made it too complicated lmao (AI is also to partially blame for this) +cseCurriculum (Array) +│ +├── Section +│ ├── section: "University & School Core" +│ ├── credits: 51 +│ ├── description +│ └── streams (Array) +│ │ +│ ├── Stream +│ │ ├── name: "Writing & Communication" +│ │ ├── credits: 6 +│ │ └── courses (Array) +│ │ ├── Course +│ │ │ ├── code: "ENG091" +│ │ │ ├── name +│ │ │ ├── credits +│ │ │ └── optional +│ │ └── ... +│ │ +│ ├── Stream +│ │ ├── name: "Mathematics & Natural Sciences" +│ │ └── courses [...] +│ │ +│ └── ... (more streams) +│ +├── Section +│ ├── section: "Program Core" +│ ├── credits: 75 +│ ├── description +│ └── courses (Array) +│ ├── Course +│ │ ├── code: "CSE110" +│ │ ├── name +│ │ └── credits +│ └── ... +│ +├── Section +│ ├── section: "Program Electives" +│ ├── credits: 6 +│ ├── referenceLink +│ └── courses (Array) +│ ├── Elective Course +│ └── ... +│ +├── Section +│ ├── section: "Final Requirement" +│ ├── credits: 4 +│ └── courses (Array) +│ └── Project / Thesis / Internship +│ +└── (end) +*/ + +/* +prerequisiteOverrides (Object) +│ +├── courseCode: null (default = use CDN) +├── courseCode: null +├── CSE260: "CSE251" // overridden prerequisite +└── ... +*/ +// ! MARK: JSON Starts Here +export const csCurriculum = [ + { + section: "University & School Core", + credits: 51, + description: "University Core (39) + School Core (12)", + streams: [ + { + name: "Stream 1: Writing & Communication", + credits: 6, + courses: [ + { code: "ENG091", name: "Foundation Course", credits: 0 }, + { code: "ENG101", name: "English Fundamentals", credits: 3 }, + { code: "ENG102", name: "English Composition I", credits: 3 }, + { code: "ENG103", name: "Advanced Writing Skills and Presentation (*only for ENG102 freshers)", credits: 3 }, + ], + }, + { + name: "Stream 2: Mathematics & Natural Sciences + School Core", + credits: 21, + courses: [ + { code: "MAT092", name: "Remedial Mathematics", credits: 0, optional: false }, + { code: "MAT110", name: "Math I: Differential Calculus & Coordinate Geometry", credits: 3, optional: false }, + { + code: "MAT120", + name: "Math II: Integral Calculus and Differential Equations", + credits: 3, + optional: false, + }, + { + code: "MAT215", + name: "Math III: Complex Variables and Laplace Transformations", + credits: 3, + optional: false, + }, + { code: "MAT216", name: "Math IV: Linear Algebra and Fourier Analysis", credits: 3, optional: false }, + { code: "PHY111", name: "Principles of Physics I", credits: 3, optional: false }, + { code: "PHY112", name: "Principles of Physics II", credits: 3, optional: false }, + { code: "STA201", name: "Statistics and Probability", credits: 3, optional: false }, + ], + }, + { + name: "Stream 3: Arts & Humanities", + credits: 9, + courses: [ + { code: "HUM103", name: "Ethics and Culture", credits: 3 }, + { code: "BNG103", name: "Bangla Language and Literature", credits: 3 }, + { + code: "Stream 3 - COD", + name: "Choose one from list", + credits: 3, + alternatives: [ + "HUM101", + "HUM102", + "HST102", + "HST103", + "HST104", + "HUM207", + "ENG110", + "ENG113", + "ENG114", + "ENG115", + "ENG333", + ], + }, + ], + note: "One Course From: HUM101, HUM102, HST102, HST103, HST104, HUM207, ENG110, ENG113, ENG114, ENG115, ENG333", + }, + { + name: "Stream 4: Social Sciences", + credits: 6, + courses: [ + { + code: "EMB101/DEV101", + name: "Emergence of Bangladesh / Bangladesh Studies", + credits: 3, + }, + { + code: "Stream 4 - COD", + name: "Choose one from list", + credits: 3, + alternatives: [ + "PSY101", + "SOC101", + "ANT101", + "POL101", + "BUS201", + "ECO101", + "ECO102", + "ECO105", + "BUS102", + "POL102", + "POL103", + "POL201", + "POL202", + "PSY102", + "DEV104", + "DEV201", + "SOC201/ANT202", + "ANT342", + "ANT351", + "BUS333", + "BUS334", + "BUS335", + ], + }, + ], + note: "One Course From: PSY101, SOC101, ANT101, POL101, BUS201, ECO101, ECO102, ECO105, BUS102, POL102, POL103, POL201, POL202, PSY102, DEV104, DEV201, SOC201/ANT202, ANT342, ANT351, BUS333, BUS334, BUS335", + }, + { + name: "Stream 5: Community Service & Transformation", + credits: 3, + courses: [ + { + code: "Stream 5 - COD", + name: "Choose one CST course", + credits: 3, + alternatives: [ + "CST201", + "CST301", + "CST302", + "CST303", + "CST304", + "CST305", + "CST306", + "CST307", + "CST308", + "CST309", + "CST310", + ], + }, + ], + note: "One Course From: CST201, CST301, CST302, CST303, CST304, CST305, CST306, CST307, CST308, CST309, CST310", + }, + { + name: "Courses Out Of Department (COD)", + credits: 6, + courses: [ + { code: "COD - 4", name: "GenEd Elective", credits: 3, optional: true }, + { code: "COD - 5", name: "GenEd Elective", credits: 3, optional: true }, + ], + note: "Two Courses From: HUM101, HUM102, HST102, HST103, HST104, HUM207, ENG110, ENG113, ENG114, ENG115, ENG333, PSY101, SOC101, ANT101, POL101, BUS201, ECO101, ECO102, ECO105, BUS102, POL102, POL103, POL201, POL202, PSY102, DEV104, DEV201, SOC201/ANT202, ANT342, ANT351, BUS333, BUS334, BUS335, CST201, CST301, CST302, CST303, CST304, CST305, CST306, CST307, CST308, CST309, CST310, CHE101, BIO101, ENV103", + }, + ], + }, + + { + section: "Program Core", + credits: 48, + description: "Core Computer Science and Engineering courses", + courses: [ + { code: "CSE110", name: "Programming Language I", credits: 3 }, + { code: "CSE111", name: "Programming Language II", credits: 3 }, + { code: "CSE220", name: "Data Structures", credits: 3 }, + { code: "CSE221", name: "Algorithms", credits: 3 }, + { code: "CSE230", name: "Discrete Mathematics", credits: 3 }, + { code: "CSE260", name: "Digital Logic Design", credits: 3 }, + { code: "CSE321", name: "Operating System", credits: 3 }, + { code: "CSE330", name: "Numerical Methods", credits: 3 }, + { code: "CSE331", name: "Automata and Computability", credits: 3 }, + { code: "CSE340", name: "Computer Architecture", credits: 3 }, + { code: "CSE370", name: "Database Systems", credits: 3 }, + { code: "CSE420", name: "Compiler Design", credits: 3 }, + { code: "CSE421", name: "Computer Networks", credits: 3 }, + { code: "CSE422", name: "Artificial Intelligence", credits: 3 }, + { code: "CSE423", name: "Computer Graphics", credits: 3 }, + { code: "CSE470", name: "Software Engineering", credits: 3 }, + ], + }, + + { + section: "Program Electives", + credits: 21, + description: + "Elective courses (minimum one must be a CSE elective) || Research the Elective Pre-Requisites Carefully From the Reference Link", + referenceLink: + "https://docs.google.com/spreadsheets/d/1-JM6a-JM4y4TiqMv9M4OXMBTnIAbn0lhsvfVGlfR1OU/edit?gid=1207964579#gid=1207964579", + courses: [ + { code: "CS Elective - 1", name: "CSE Elective", credits: 3, elective: true }, + { code: "Open Elective - 2", name: "CSE / Minor / GenEd Elective", credits: 3, elective: true }, + { code: "Open Elective - 3", name: "CSE / Minor / GenEd Elective", credits: 3, elective: true }, + { code: "Open Elective - 4", name: "CSE / Minor / GenEd Elective", credits: 3, elective: true }, + { code: "Open Elective - 5", name: "CSE / Minor / GenEd Elective", credits: 3, elective: true }, + { code: "Open Elective - 6", name: "CSE / Minor / GenEd Elective", credits: 3, elective: true }, + { code: "Open Elective - 7", name: "CSE / Minor / GenEd Elective", credits: 3, elective: true }, + ], + }, + + { + section: "Final Requirement", + credits: 4, + description: "Thesis / Project / Internship", + referenceLink: + "https://docs.google.com/document/d/1pAMjuQAxSEcLgkbvmx9qJGvK2BlPQhvXhgev8OeJTas/edit?tab=t.0#heading=h.aczyuw2yex2w", + courses: [{ code: "CSE400", name: "Project / Thesis / Internship", credits: 4 }], + }, +]; + +// ! Calculate total credits from curriculum +export function getTotalCredits(curriculum = csCurriculum) { + return curriculum.reduce((sum, section) => sum + (section.credits || 0), 0); +} + +// ! Pre-req override map +const allCurriculumCourseCodes = Array.from( + new Set( + csCurriculum.flatMap((section) => { + if (section.courses) return section.courses.map((c) => c.code); + if (section.streams) return section.streams.flatMap((s) => s.courses.map((c) => c.code)); + return []; + }), + ), +); + +export const prerequisiteOverrides = { + ...Object.fromEntries(allCurriculumCourseCodes.map((code) => [code, null])), + CSE260: "CSE230", // more realistic prereq +}; + +export const cseCurriculum = csCurriculum; diff --git a/src/constants/cseCurriculum.js b/src/constants/cseCurriculum.js new file mode 100644 index 0000000..319213b --- /dev/null +++ b/src/constants/cseCurriculum.js @@ -0,0 +1,301 @@ +/* +! MARK: Data Structure Tree +! Adding This Here So Someone Else Understands What The Actual Fuck This Data Structure Is Supposed To Look Like +? And Of Course- For Future References +? But Mainly Because I believe I made it too complicated lmao (AI is also to partially blame for this) +cseCurriculum (Array) +│ +├── Section +│ ├── section: "University & School Core" +│ ├── credits: 51 +│ ├── description +│ └── streams (Array) +│ │ +│ ├── Stream +│ │ ├── name: "Writing & Communication" +│ │ ├── credits: 6 +│ │ └── courses (Array) +│ │ ├── Course +│ │ │ ├── code: "ENG091" +│ │ │ ├── name +│ │ │ ├── credits +│ │ │ └── optional +│ │ └── ... +│ │ +│ ├── Stream +│ │ ├── name: "Mathematics & Natural Sciences" +│ │ └── courses [...] +│ │ +│ └── ... (more streams) +│ +├── Section +│ ├── section: "Program Core" +│ ├── credits: 75 +│ ├── description +│ └── courses (Array) +│ ├── Course +│ │ ├── code: "CSE110" +│ │ ├── name +│ │ └── credits +│ └── ... +│ +├── Section +│ ├── section: "Program Electives" +│ ├── credits: 6 +│ ├── referenceLink +│ └── courses (Array) +│ ├── Elective Course +│ └── ... +│ +├── Section +│ ├── section: "Final Requirement" +│ ├── credits: 4 +│ └── courses (Array) +│ └── Project / Thesis / Internship +│ +└── (end) +*/ + +/* +prerequisiteOverrides (Object) +│ +├── courseCode: null (default = use CDN) +├── courseCode: null +├── CSE260: "CSE251" // overridden prerequisite +└── ... +*/ +// ! MARK: JSON Starts Here +// ! Calculate total credits from curriculum +export function getTotalCredits(curriculum = cseCurriculum) { + return curriculum.reduce((sum, section) => sum + (section.credits || 0), 0); +} + +export const cseCurriculum = [ + { + section: "University & School Core", + credits: 51, + description: "University Core (39) + School Core (12)", + streams: [ + { + name: "Stream 1: Writing & Communication", + credits: 6, + courses: [ + { code: "ENG091", name: "Foundation Course (in English)", credits: 0, optional: false }, + { code: "ENG101", name: "Fundamentals of English", credits: 3, optional: false }, + { code: "ENG102", name: "English Composition I", credits: 3, optional: false }, + { + code: "ENG103", + name: "Advanced Writing Skills and Presentation (*only for ENG102 freshers)", + credits: 3, + optional: false, + }, + ], + }, + { + name: "Stream 2: Mathematics & Natural Sciences + School Core", + credits: 21, + courses: [ + { code: "MAT092", name: "Remedial Mathematics", credits: 0, optional: false }, + { code: "MAT110", name: "Math I: Differential Calculus & Coordinate Geometry", credits: 3, optional: false }, + { + code: "MAT120", + name: "Math II: Integral Calculus and Differential Equations", + credits: 3, + optional: false, + }, + { + code: "MAT215", + name: "Math III: Complex Variables and Laplace Transformations", + credits: 3, + optional: false, + }, + { code: "MAT216", name: "Math IV: Linear Algebra and Fourier Analysis", credits: 3, optional: false }, + { code: "PHY111", name: "Principles of Physics I", credits: 3, optional: false }, + { code: "PHY112", name: "Principles of Physics II", credits: 3, optional: false }, + { code: "STA201", name: "Statistics and Probability", credits: 3, optional: false }, + ], + }, + { + name: "Stream 3: Arts & Humanities", + credits: 9, + courses: [ + { code: "HUM103", name: "Ethics and Culture", credits: 3, optional: false }, + { code: "BNG103", name: "Bangla Language and Literature", credits: 3, optional: false }, + { + code: "Stream 3 - COD", + name: "Pick one course from the provided options above", + credits: 3, + optional: false, + alternatives: [ + "HUM101", + "HUM102", + "HST102", + "HST103", + "HST104", + "HUM207", + "ENG110", + "ENG113", + "ENG114", + "ENG115", + "ENG333", + ], + }, + ], + note: "One Course From: HUM101, HUM102, HST102, HST103, HST104, HUM207, ENG110, ENG113, ENG114, ENG115, ENG333", + }, + { + name: "Stream 4: Social Sciences", + credits: 6, + courses: [ + { + code: "EMB101", + name: "Emergence of Bangladesh / Bangladesh Studies", + credits: 3, + optional: false, + alternatives: ["DEV101"], + }, + { + code: "Stream 4 - COD", + name: "Pick one course from the provided options above", + credits: 3, + optional: false, + alternatives: [ + "PSY101", + "SOC101", + "ANT101", + "POL101", + "BUS201", + "ECO101", + "ECO102", + "ECO105", + "BUS102", + "POL102", + "POL103", + "POL201", + "POL202", + "PSY102", + "DEV104", + "DEV201", + "SOC201/ANT202", + "ANT342", + "ANT351", + "BUS333", + "BUS334", + "BUS335", + ], + }, + ], + note: "One Course From: PSY101, SOC101, ANT101, POL101, BUS201, ECO101, ECO102, ECO105, BUS102, POL102, POL103, POL201, POL202, PSY102, DEV104, DEV201, SOC201/ANT202, ANT342, ANT351, BUS333, BUS334, BUS335", + }, + { + name: "Stream 5: Community Service & Transformation", + credits: 3, + courses: [ + { + code: "Stream 5 - COD", + name: "Pick one course from the provided options above", + credits: 3, + optional: true, + alternatives: [ + "CST201", + "CST301", + "CST302", + "CST303", + "CST304", + "CST305", + "CST306", + "CST307", + "CST308", + "CST309", + "CST310", + ], + }, + ], + note: "One Course From: CST201, CST301, CST302, CST303, CST304, CST305, CST306, CST307, CST308, CST309, CST310", + }, + { + name: "Courses Out Of Department (COD)", + credits: 6, + courses: [ + { code: "COD - 4", name: "Pick one course from the provided options above", credits: 3, optional: true }, + { code: "COD - 5", name: "Pick another course from the provided options above", credits: 3, optional: true }, + ], + note: "Two Courses From: HUM101, HUM102, HST102, HST103, HST104, HUM207, ENG110, ENG113, ENG114, ENG115, ENG333, PSY101, SOC101, ANT101, POL101, BUS201, ECO101, ECO102, ECO105, BUS102, POL102, POL103, POL201, POL202, PSY102, DEV104, DEV201, SOC201/ANT202, ANT342, ANT351, BUS333, BUS334, BUS335, CST201, CST301, CST302, CST303, CST304, CST305, CST306, CST307, CST308, CST309, CST310, CHE101, BIO101, ENV103", + }, + ], + }, + { + section: "Program Core", + credits: 75, + description: "Core Computer Science and Engineering courses", + courses: [ + { code: "CSE110", name: "Programming Language I", credits: 3 }, + { code: "CSE111", name: "Programming Language II", credits: 3 }, + { code: "CSE220", name: "Data Structures", credits: 3 }, + { code: "CSE221", name: "Algorithms", credits: 3 }, + { code: "CSE230", name: "Discrete Mathematics", credits: 3 }, + { code: "CSE331", name: "Automata and Computability", credits: 3 }, + { code: "CSE250", name: "Circuits and Electronics", credits: 3 }, + { code: "CSE251", name: "Electronic Devices and Circuits", credits: 3 }, + { code: "CSE260", name: "Digital Logic Design", credits: 3 }, + { code: "CSE350", name: "Digital Electronics and Pulse Techniques", credits: 3 }, + { code: "CSE341", name: "Microprocessors", credits: 3 }, + { code: "CSE360", name: "Computer Interfacing", credits: 3 }, + { code: "CSE321", name: "Operating System", credits: 3 }, + { code: "CSE340", name: "Computer Architecture", credits: 3 }, + { code: "CSE320", name: "Data Communications", credits: 3 }, + { code: "CSE421", name: "Computer Networks", credits: 3 }, + { code: "CSE370", name: "Database Systems", credits: 3 }, + { code: "CSE330", name: "Numerical Methods", credits: 3 }, + { code: "CSE420", name: "Compiler Design", credits: 3 }, + { code: "CSE422", name: "Artificial Intelligence", credits: 3 }, + { code: "CSE423", name: "Computer Graphics", credits: 3 }, + { code: "CSE460", name: "VLSI Design", credits: 3 }, + { code: "CSE461", name: "Introduction to Robotics", credits: 3 }, + { code: "CSE470", name: "Software Engineering", credits: 3 }, + { code: "CSE471", name: "Systems Analysis and Design", credits: 3 }, + ], + }, + { + section: "Program Electives", + credits: 6, + description: + "Choose elective courses to specialize || Research the Elective Pre-Requisites Carefully From the Reference Link", + referenceLink: + "https://docs.google.com/spreadsheets/d/1-JM6a-JM4y4TiqMv9M4OXMBTnIAbn0lhsvfVGlfR1OU/edit?gid=1207964579#gid=1207964579", + courses: [ + { code: "CSE Elective", name: "CSE Elective", credits: 3, elective: true }, + { code: "Open Elective", name: "CSE / Minor / GenEd", credits: 3, elective: true }, + ], + }, + { + section: "Final Requirement", + credits: 4, + description: "Thesis / Project / Internship", + referenceLink: + "https://docs.google.com/document/d/1pAMjuQAxSEcLgkbvmx9qJGvK2BlPQhvXhgev8OeJTas/edit?tab=t.0#heading=h.aczyuw2yex2w", + courses: [{ code: "CSE400", name: "Project / Thesis / Internship", credits: 4 }], + }, +]; + +// !MARK: Pre-req override map +// null means "use prerequisite from Connect CDN". +// Any non-null string overrides the CDN prerequisite for that course code. +const allCurriculumCourseCodes = Array.from( + new Set( + cseCurriculum.flatMap((section) => { + if (Array.isArray(section.courses)) { + return section.courses.map((course) => course.code); + } + if (Array.isArray(section.streams)) { + return section.streams.flatMap((stream) => stream.courses.map((course) => course.code)); + } + return []; + }), + ), +); + +export const prerequisiteOverrides = { + ...Object.fromEntries(allCurriculumCourseCodes.map((code) => [code, null])), + CSE260: "CSE251", +}; diff --git a/src/constants/featureList.js b/src/constants/featureList.js index c430bbe..8ac79a1 100644 --- a/src/constants/featureList.js +++ b/src/constants/featureList.js @@ -35,6 +35,17 @@ const featureList = [ }, { index: 5, + title: "Course Progression", + description: "Track completed courses, check prerequisites, and visualize your graduation progress.", + footer: "🟡 Work In Progress", + href: "/course-progression", + dashboardHref: "/course-progression" + }, + { + index: 6, + title: "Faculty Review", + description: "Rate your faculty and provide feedback. Scrolling through useless reviews is a thing of the past!", + footer: "🟡 Coming Soon" title: "Gradesheet Analyzer", description: "Calculate Retakes, See Grade Tolerances and Beautiful Charts!", footer: "🟢 Live", diff --git a/src/constants/microbioCurriculum.js b/src/constants/microbioCurriculum.js new file mode 100644 index 0000000..dfd1af6 --- /dev/null +++ b/src/constants/microbioCurriculum.js @@ -0,0 +1,415 @@ +/* +! MARK: Data Structure Tree +! Keeping This Aligned With The CS/CSE Curriculum Modules So The Page Can Treat Every Department The Same Way +*/ + +/* +prerequisiteOverrides (Object) +│ +├── courseCode: null (default = use CDN) +├── courseCode: null +└── ... +*/ + +// ! MARK: JSON Starts Here +const generalEducationWritingOptions = ["ENG101", "ENG102", "ENG103"]; +const generalEducationMathOptions = ["MAT101", "MAT110", "STA101", "STA201", "PHY101", "PHY111", "CHE101", "BIO101", "ENV103", "CSE101"]; +const generalEducationHumanitiesOptions = [ + "ENG113", + "ENG114", + "ENG115", + "ENG333", + "HUM101", + "HUM102", + "HST102", + "HST103", + "ARC294", + "ARC122", +]; +const generalEducationSocialScienceOptions = [ + "BUS101", + "BUS102", + "BUS201", + "ECO105", + "POL101", + "ANT101", + "SOC101", + "PSY101", + "SOC201", + "ANT210", + "ANT342", + "ANT351", +]; +const generalEducationCommunityOptions = ["CST301", "CST302", "CST303", "CST304", "CST305"]; +const generalEducationAdditionalOptions = Array.from( + new Set([ + ...generalEducationWritingOptions, + ...generalEducationMathOptions, + ...generalEducationHumanitiesOptions, + ...generalEducationSocialScienceOptions, + ...generalEducationCommunityOptions, + ]), +); + +export const microbiologyCurriculum = [ + { + section: "General Education", + credits: 39, + description: "University Core (39 credits). Complete the COD-style slots below to satisfy the minimum requirements, then fill the additional COD slots to reach 39 credits.", + streams: [ + { + name: "Stream 1: Writing Comprehension", + credits: 6, + note: "Take any 2 of the ENG courses (each 3 credits).", + courses: [ + { + code: "MIC-GENED-ENG-COD-1", + name: "Pick one course from the provided options above", + credits: 3, + optional: false, + alternatives: generalEducationWritingOptions, + }, + { + code: "MIC-GENED-ENG-COD-2", + name: "Pick one course from the provided options above", + credits: 3, + optional: false, + alternatives: generalEducationWritingOptions, + }, + ], + }, + { + name: "Stream 2: Math and Natural Sciences", + credits: 6, + note: "Take minimum 2 courses from the list (each 3 credits).", + courses: [ + { + code: "MIC-GENED-MNS-COD-1", + name: "Pick one course from the provided options above", + credits: 3, + optional: false, + alternatives: generalEducationMathOptions, + }, + { + code: "MIC-GENED-MNS-COD-2", + name: "Pick one course from the provided options above", + credits: 3, + optional: false, + alternatives: generalEducationMathOptions, + }, + ], + }, + { + name: "Stream 3: Arts and Humanities", + credits: 9, + note: "BNG103 and HUM103 are compulsory. After completing those, take minimum 1 additional course from the list (each 3 credits).", + courses: [ + { code: "BNG103", name: "Bangla Language and Literature", credits: 3, optional: false }, + { code: "HUM103", name: "Ethics and Culture", credits: 3, optional: false }, + { + code: "MIC-GENED-HUM-COD", + name: "Pick one course from the provided options above", + credits: 3, + optional: false, + alternatives: generalEducationHumanitiesOptions, + }, + ], + }, + { + name: "Stream 4: Social Sciences", + credits: 6, + note: "EMB101 is compulsory. After completing it, take minimum 1 additional course from the list (each 3 credits).", + courses: [ + { code: "EMB101", name: "Emergence of Bangladesh", credits: 3, optional: false }, + { + code: "MIC-GENED-SOC-COD", + name: "Pick one course from the provided options above", + credits: 3, + optional: false, + alternatives: generalEducationSocialScienceOptions, + }, + ], + }, + { + name: "Stream 5: Communities, Seeking Transformation", + credits: 3, + note: "Take exactly 1 course from the list (3 credits).", + courses: [ + { + code: "MIC-GENED-CST-COD", + name: "Pick one course from the provided options above", + credits: 3, + optional: false, + alternatives: generalEducationCommunityOptions, + }, + ], + }, + { + name: "Courses Out Of Department (COD)", + credits: 9, + note: "Take 3 additional 3-credit courses from the general education pool above to reach 39 credits.", + courses: [ + { + code: "MIC-GENED-COD-1", + name: "Pick one course from the provided options above", + credits: 3, + optional: false, + alternatives: generalEducationAdditionalOptions, + }, + { + code: "MIC-GENED-COD-2", + name: "Pick one course from the provided options above", + credits: 3, + optional: false, + alternatives: generalEducationAdditionalOptions, + }, + { + code: "MIC-GENED-COD-3", + name: "Pick one course from the provided options above", + credits: 3, + optional: false, + alternatives: generalEducationAdditionalOptions, + }, + ], + } + ] + }, + { + section: "Program Core", + credits: 75, + description: "Departmental Core Courses: 63 credits of theory + 12 credits of laboratory.", + courses: [ + // Theoretical courses (21 x 3 credits) + { code: "MIC101", name: "Introduction to Microbiology", credits: 3 }, + { code: "BCH101", name: "Basic Biochemistry", credits: 3 }, + { code: "BCH102", name: "Biophysical Chemistry", credits: 3 }, + { code: "MIC102", name: "Basic Techniques in Microbiology", credits: 3 }, + { code: "BCH201", name: "Human Physiology", credits: 3 }, + { code: "MIC201", name: "Microbial Chemistry", credits: 3 }, + { code: "MIC202", name: "Microbial Metabolism", credits: 3 }, + { code: "MIC203", name: "Environmental Microbiology", credits: 3 }, + { code: "MIC204", name: "Medical Microbiology", credits: 3 }, + { code: "MIC206", name: "Introduction to Molecular Biology", credits: 3 }, // BTE207/MIC206 + { code: "MIC300", name: "Basic Immunology", credits: 3 }, // MIC300/BCH301 + { code: "MIC301", name: "Virology", credits: 3 }, + { code: "MIC302", name: "Food Microbiology", credits: 3 }, + { code: "MIC303", name: "Agriculture Microbiology", credits: 3 }, + { code: "MIC306", name: "Pharmaceutical Microbiology", credits: 3 }, + { code: "MIC308", name: "Fermentation Technology", credits: 3 }, + { code: "MIC310", name: "Advanced Molecular Biology", credits: 3 }, + { code: "BTE401", name: "Bioinformatics", credits: 3 }, + { code: "MIC401", name: "Microbial Genetic Engineering", credits: 3 }, + { code: "MIC402", name: "Analytical Microbiology", credits: 3 }, + { code: "MIC403", name: "Microbiological Quality Control of Foods, Fish and Beverages", credits: 3 }, + // Laboratory courses (4 x 3 credits) + { code: "MIC155", name: "Microbial LAB I", credits: 3 }, + { code: "MIC255", name: "Microbial LAB II", credits: 3 }, + { code: "MIC355", name: "Microbial LAB III", credits: 3 }, + { code: "MIC455", name: "Microbial LAB IV", credits: 3 } + ] + }, + { + section: "Program Electives", + credits: 15, + description: "Select 15 credits from the following elective courses.", + referenceLink: "", + courses: [ + { + code: "MIC-ELECTIVE-1", + name: "Pick one course from the provided options above", + credits: 3, + elective: true, + alternatives: [ + "BCH202", + "BTE312", + "BTE313", + "BTE315", + "BTE317", + "BTE403", + "BTE404", + "MIC304", + "MIC307", + "MIC309", + "MIC404", + "MIC405", + "MIC406", + "MIC407", + "MIC408", + ], + }, + { + code: "MIC-ELECTIVE-2", + name: "Pick one course from the provided options above", + credits: 3, + elective: true, + alternatives: [ + "BCH202", + "BTE312", + "BTE313", + "BTE315", + "BTE317", + "BTE403", + "BTE404", + "MIC304", + "MIC307", + "MIC309", + "MIC404", + "MIC405", + "MIC406", + "MIC407", + "MIC408", + ], + }, + { + code: "MIC-ELECTIVE-3", + name: "Pick one course from the provided options above", + credits: 3, + elective: true, + alternatives: [ + "BCH202", + "BTE312", + "BTE313", + "BTE315", + "BTE317", + "BTE403", + "BTE404", + "MIC304", + "MIC307", + "MIC309", + "MIC404", + "MIC405", + "MIC406", + "MIC407", + "MIC408", + ], + }, + { + code: "MIC-ELECTIVE-4", + name: "Pick one course from the provided options above", + credits: 3, + elective: true, + alternatives: [ + "BCH202", + "BTE312", + "BTE313", + "BTE315", + "BTE317", + "BTE403", + "BTE404", + "MIC304", + "MIC307", + "MIC309", + "MIC404", + "MIC405", + "MIC406", + "MIC407", + "MIC408", + ], + }, + { + code: "MIC-ELECTIVE-5", + name: "Pick one course from the provided options above", + credits: 3, + elective: true, + alternatives: [ + "BCH202", + "BTE312", + "BTE313", + "BTE315", + "BTE317", + "BTE403", + "BTE404", + "MIC304", + "MIC307", + "MIC309", + "MIC404", + "MIC405", + "MIC406", + "MIC407", + "MIC408", + ], + }, + ] + }, + { + section: "Final Requirement", + credits: 6, + description: "Internship and Thesis/Project (3 credits each).", + courses: [ + { code: "MIC400", name: "Internship", credits: 3 }, + { code: "MIC450", name: "Thesis", credits: 3 } + ] + } +]; + +// ! Calculate total credits from curriculum +export function getTotalCredits(curriculum = microbiologyCurriculum) { + return curriculum.reduce((sum, section) => sum + (section.credits || 0), 0); +} + +// ! Pre-req override map +export const prerequisiteOverrides = { + MIC101: null, + BCH101: null, + BCH102: null, + MIC102: "MIC101", + BCH201: "BCH101", + MIC202: "BCH101", + MIC203: "MIC101", + MIC204: "BCH201", + MIC206: "MIC101, BCH101", + MIC300: "MIC204, MIC206", + MIC301: "MIC300", + MIC302: "MIC101", + MIC303: "MIC203", + MIC306: "BCH101, MIC102", + MIC308: "MIC202", + MIC310: "MIC206", + MIC401: "MIC310", + MIC402: "MIC300", + MIC403: "MIC302", + BTE401: "MIC401, CSE101", + MIC155: null, + MIC255: null, + MIC355: null, + MIC455: null, + MIC304: "MIC202, MIC206", + MIC307: "MIC302", + BTE315: "MIC203", + BTE312: "CSE101", + BTE317: "STA101 OR STA201", + MIC309: "MIC300", + MIC404: "MIC206, MIC301", + MIC405: "MIC203", + MIC406: "MIC300", + MIC407: "MIC204, MIC206", + MIC408: "MIC203", + BCH202: "BCH101", + BTE313: "MIC203", + BTE403: "MIC310", + BTE404: "MIC308, MIC304", + MIC400: null, + MIC450: null +}; + +const allCurriculumCourseCodes = Array.from( + new Set( + microbiologyCurriculum.flatMap((section) => { + if (Array.isArray(section.courses)) { + return section.courses.map((course) => course.code); + } + + if (Array.isArray(section.streams)) { + return section.streams.flatMap((stream) => stream.courses.map((course) => course.code)); + } + + return []; + }), + ), +); + +for (const code of allCurriculumCourseCodes) { + if (!(code in prerequisiteOverrides)) { + prerequisiteOverrides[code] = null; + } +} \ No newline at end of file diff --git a/src/constants/sideBarItems.js b/src/constants/sideBarItems.js index b05f7ac..f8b0637 100644 --- a/src/constants/sideBarItems.js +++ b/src/constants/sideBarItems.js @@ -1,3 +1,4 @@ +import { Home, Sigma, Star, ChevronsLeftRightEllipsis, Hammer, Cable, BookOpen, Users, ArrowRightLeft, FileText, CheckCircle2 } from "lucide-react" import { Home, Sigma, Star, ChevronsLeftRightEllipsis, Hammer, Cable, BookOpen, Users, ArrowRightLeft, FileText, LineChart, Drumstick } from "lucide-react" const sidebarGroups = [ @@ -45,6 +46,10 @@ const sidebarGroups = [ enabled: true }, { + title: "Course Progression", + href: '/course-progression', + description: 'Track your course completion progress and visualize prerequisites.', + icon: CheckCircle2, title: 'Gradesheet Analyzer', href: '/dashboard/gradesheet', description: 'Upload your grade sheet PDF to analyze your CGPA and plan retakes', diff --git a/src/constants/toolLinks.js b/src/constants/toolLinks.js index 14b026c..8d84fe8 100644 --- a/src/constants/toolLinks.js +++ b/src/constants/toolLinks.js @@ -2,6 +2,7 @@ import { title } from "process"; const toolLinks = [ { title: 'PrePreReg', href: '/preprereg' }, + { title: 'Course Progression', href: '/course-progression' }, { title: 'Course Swap', href: '/courseswap' }, { title: 'Merge Routines', href: '/merge-routines' }, { title: 'Course Directory', href: '/course-materials' },