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) => (
+ {
+ event.stopPropagation();
+ onPrereqClick?.(prereq);
+ }}
+ className="px-2.5 py-0.5 text-[11px] rounded-full border border-blue-300 dark:border-blue-700 text-blue-700 dark:text-blue-300 bg-blue-50/80 dark:bg-blue-900/30 hover:bg-blue-100 dark:hover:bg-blue-900/50 transition-colors"
+ title={`Jump to prerequisite ${prereq}`}
+ >
+ {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) => (
+ handleDeptClick(dept.code)}
+ >
+ {dept.code}
+
+ ))}
+
+
+ {/* Main Content */}
+
+ {!selectedDept ? (
+
+
+ Select a department to view its course progression.
+
+
+ ) : showComingSoon ? (
+
+
+ {departments.find((d) => d.code === selectedDept)?.name} outline coming soon...
+
+
+ ) : (
+
+
+
+
+
+ {courseSearchQuery.trim() ? (
+ courseSearchMatches.length > 0 ? (
+ <>
+
+ Showing {courseSearchMatches.length} match{courseSearchMatches.length > 1 ? "es" : ""}
+
+
+ {courseSearchMatches.slice(0, 6).map((match) => (
+ {
+ setHighlightedCourseCode(match.course.code);
+ const cardEl = document.getElementById(getCourseCardId(match.course.code));
+ if (cardEl) {
+ cardEl.scrollIntoView({ behavior: "smooth", block: "center" });
+ }
+ }}
+ className="rounded-full border border-gray-300 bg-white px-3 py-1 text-xs font-medium text-gray-800 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-900/80 dark:text-gray-100 dark:hover:bg-gray-800"
+ >
+ {match.course.code}
+ {match.locationLabel}
+
+ ))}
+
+ >
+ ) : (
+
+ 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 && (
+
+
+ Undo({undoStack.length})
+
+ )}
+
+ setShowProgressPanel(true)}
+ className="group inline-flex items-center gap-2 rounded-full border border-white/60 bg-white/90 px-4 py-2 text-xs font-semibold text-gray-700 shadow-lg backdrop-blur-xl transition-all hover:-translate-y-0.5 hover:bg-white dark:border-gray-700/70 dark:bg-gray-900/90 dark:text-gray-200"
+ >
+
+
+
+ {selectedDept} credits {totalCompletedCredits} / {totalCredits}
+
+
+ {supportedDepartments.has(selectedDept) && canReset && (
+
+
+ Reset
+
+ )}
+
+
+ ) : (
+
+
+
+
+
+
+ {supportedDepartments.has(selectedDept) && canUndo && (
+
+
+ Undo({undoStack.length})
+
+ )}
+
+
+
+ {supportedDepartments.has(selectedDept) && canReset && (
+
+
+ Reset
+
+ )}
+ setShowProgressPanel(false)}
+ className="inline-flex h-8 w-8 items-center justify-center rounded-full border border-gray-200 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-white"
+ aria-label="Hide progress panel"
+ >
+
+
+
+
+
+
+
+
+ {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) => (
+ handleJumpToCourse(course.sectionName, course.code)}
+ className="cursor-pointer rounded-full border border-gray-300 bg-white px-3 py-1 text-[13px] font-medium text-gray-800 transition-colors hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-900/80 dark:text-gray-100 dark:hover:bg-gray-800"
+ title={`Jump to ${course.sectionName}`}
+ >
+ {course.code}
+
+ ))
+ ) : (
+
+ 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' },