"use client"; import { useEffect, useMemo, useState } from "react"; import { ArrowDownZA, ArrowUpAZ, BookOpen, Clock, ExternalLink, FileText, Github, Grid2X2, Info, List, CirclePlay, Search, Users, X, Youtube, } from "lucide-react"; import coursesData from "@/data/courses.json"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { cn } from "@/lib/utils"; const ALL = "all"; const EXCLUDED_COURSE_IDS = new Set([ "cs-302-theory-of-computation", "cs-602-theory-of-computation", ]); type Course = (typeof coursesData.courses)[number]; type SortDirection = "asc" | "desc"; type ViewMode = "list" | "cards"; type FeaturedResource = { courseId: string; title: string; eyebrow: string; meta: string; url: string; thumbnail?: string; type: "playlist" | "repository" | "notes"; description: string; }; const FEATURED_RESOURCES: FeaturedResource[] = [ { courseId: "es-335-machine-learning", title: "Machine Learning @ IIT Gandhinagar 2024", eyebrow: "ES 335", meta: "33 lessons", url: "https://www.youtube.com/watch?v=XuDrvFmvk2U&list=PLftoLyLEwECDqanAL7LkZNnZG9VdNCiJF", thumbnail: "/images/course-resources/machine-learning.png", type: "playlist", description: "Recorded lectures by Nipun Batra.", }, { courseId: "es-661-probabilistic-machine-learning", title: "Probabilistic Machine Learning", eyebrow: "ES 661", meta: "24 lessons", url: "https://www.youtube.com/watch?v=UCYaVS9_jGU&list=PLftoLyLEwECBEJyfRBJoSBd0UaTjEcs3I", thumbnail: "/images/course-resources/probabilistic-machine-learning.png", type: "playlist", description: "Recorded lectures by Nipun Batra.", }, { courseId: "cs-330-operating-systems", title: "Operating Systems Fall 2018 IITGN", eyebrow: "CS 330", meta: "31 lessons", url: "https://www.youtube.com/watch?v=whp34MZbG6o&list=PLftoLyLEwECB3NsNfQ1oxtt8IoBNRWcO5", thumbnail: "/images/course-resources/operating-systems.png", type: "playlist", description: "Recorded lectures by Nipun Batra.", }, { courseId: "cs-201-theory-of-computing", title: "balu/toc", eyebrow: "CS 201", meta: "GitHub repository", url: "https://github.com/balu/toc", thumbnail: "/images/course-resources/theory-of-computing.png", type: "repository", description: "Repository of Theory of Computation course material and references by Balagopal Komarath.", }, { courseId: "cs-614-advanced-algorithms", title: "Parameterized Algorithms", eyebrow: "CS 614", meta: "NPTEL playlist", url: "https://www.youtube.com/watch?v=9x9pAn5DkZI&list=PLyqSpQzTE6M_0KLR2_FVBN4Rnvq_PlcrA", thumbnail: "/images/course-resources/parameterized-algorithms.png", type: "playlist", description: "NPTEL course on Parameterized Algorithms by Neeldhara Misra and Saket Saurabh, closely aligned with the Advanced Algorithms course.", }, { courseId: "cs-392-i-special-topics-in-computer-science-introduction-to-competitive-programming", title: "Introduction to Competitive Programming", eyebrow: "CS 392-I", meta: "NPTEL playlist", url: "https://www.youtube.com/playlist?list=PLyqSpQzTE6M9p9pKxFGpskf4voY45T2DZ", thumbnail: "/images/course-resources/competitive-programming.png", type: "playlist", description: "NPTEL course on Competitive Programming by Neeldhara Misra and Arjun Arul, closely aligned with the Competitive Programming elective.", }, { courseId: "cs-328-introduction-to-data-science", title: "Scalable Data Science", eyebrow: "CS 328", meta: "NPTEL playlist", url: "https://www.youtube.com/playlist?list=PLbRMhDVUMngekIHyLt8b_3jQR7C0KUCul", thumbnail: "/images/course-resources/scalable-data-science.png", type: "playlist", description: "A NPTEL course by Anirban Dasgupta and Souransghu Bhattacharya, closely aligned with the Introduction to Data Science Course.", }, { courseId: "em-614-advanced-probability-and-statistics", title: "Probability and Random Processes Lecture Notes", eyebrow: "EM 614", meta: "Lecture notes", url: "https://shanmuga.people.iitgn.ac.in/Data/ProbabilityRPLectureNotes_Shanmuga.pdf", thumbnail: "/images/course-resources/probability-lecture-notes.png", type: "notes", description: "Lecture notes by Shanmuganathan Raman.", }, ]; function getFeaturedResourcesForCourse(courseId: string) { return FEATURED_RESOURCES.filter((resource) => resource.courseId === courseId); } function ResourceIcon({ type, className, }: { type: FeaturedResource["type"]; className?: string; }) { if (type === "repository") return ; if (type === "notes") return ; return ; } function ResourceSourceIcon({ type, className, }: { type: FeaturedResource["type"]; className?: string; }) { if (type === "repository") return ; if (type === "notes") return ; return ; } function resourceActionLabel(type: FeaturedResource["type"]) { if (type === "notes") return "Open notes"; return type === "repository" ? "Open repository" : "Watch playlist"; } function formatCredits(course: Course) { if (!course.credits.length) return "Credits not specified"; return `${course.creditLabel} ${ course.credits.length === 1 && course.credits[0] === 1 ? "credit" : "credits" }`; } function courseSearchText(course: Course) { return [ course.code, course.title, course.creditLabel, course.discipline, course.program, course.courseType, course.level, course.semester, course.instructors.join(" "), course.prerequisites.join(" "), course.content, course.textbooks, ] .filter(Boolean) .join(" ") .toLowerCase(); } function truncateText(value: string | null, maxLength: number) { if (!value) return null; if (value.length <= maxLength) return value; return `${value.slice(0, maxLength).trimEnd()}...`; } function optionLabel(value: string, count?: number) { return count === undefined ? value : `${value} (${count})`; } function programTone(program: string) { if (program === "Data Science for Decision Making") { return { card: "border-l-sky-500/45 bg-sky-500/[0.025]", badge: "border-sky-500/25 bg-sky-500/10 text-sky-700 dark:text-sky-300", }; } if (program === "Integrated Circuit Design and Technology") { return { card: "border-l-emerald-500/45 bg-emerald-500/[0.025]", badge: "border-emerald-500/25 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300", }; } return { card: "border-l-primary/45 bg-primary/[0.025]", badge: "border-primary/20 bg-primary/10 text-primary", }; } function countBy(courses: Course[], key: keyof Course) { const counts = new Map(); for (const course of courses) { const value = course[key]; if (typeof value === "string" && value) { counts.set(value, (counts.get(value) ?? 0) + 1); } } return counts; } function CourseBadges({ course }: { course: Course }) { return (
{formatCredits(course)} {course.level} {course.courseType} {course.semester && {course.semester}}
); } function CourseSummary({ course, view, onOpen, }: { course: Course; view: ViewMode; onOpen: (course: Course) => void; }) { const contentPreview = truncateText(course.content, view === "cards" ? 180 : 260) ?? "Detailed syllabus text is not available in the current catalogue extract."; const instructors = course.instructors.length ? course.instructors.join(", ") : "To be announced"; return (
{course.code} {course.program} {course.ltpLabel && ( L-T-P {course.ltpLabel} )}

{course.title}

{contentPreview}

{instructors}
); } function DetailRow({ label, value, }: { label: string; value: string | null | undefined; }) { return ( <>
{label}
{value || "Not specified"}
); } function CourseDetailModal({ course, onClose, }: { course: Course | undefined; onClose: () => void; }) { useEffect(() => { if (!course) return; const previousOverflow = document.body.style.overflow; function onKeyDown(event: KeyboardEvent) { if (event.key === "Escape") onClose(); } document.body.style.overflow = "hidden"; document.addEventListener("keydown", onKeyDown); return () => { document.body.style.overflow = previousOverflow; document.removeEventListener("keydown", onKeyDown); }; }, [course, onClose]); if (!course) return null; const resources = getFeaturedResourcesForCourse(course.id); return (
); } function FeaturedResources({ courses, onOpenCourse, }: { courses: Course[]; onOpenCourse: (course: Course) => void; }) { const coursesById = new Map(courses.map((course) => [course.id, course])); return (
Featured resources

Featured course resources

Selected public resources are linked to their corresponding course catalogue entries.

{FEATURED_RESOURCES.map((resource) => { const course = coursesById.get(resource.courseId); if (!course) return null; return ( {resource.thumbnail ? ( ) : (
)}
{resource.meta}
{resource.eyebrow} {course.title}

{resource.title}

{resource.description}

); })}
); } export default function CourseCatalog() { const courses = useMemo( () => coursesData.courses.filter( (course) => !EXCLUDED_COURSE_IDS.has(course.id), ), [], ); const [query, setQuery] = useState(""); const [selectedProgram, setSelectedProgram] = useState(ALL); const [selectedLevel, setSelectedLevel] = useState(ALL); const [selectedSemester, setSelectedSemester] = useState(ALL); const [selectedCredit, setSelectedCredit] = useState(ALL); const [sortDirection, setSortDirection] = useState("asc"); const [view, setView] = useState("list"); const [selectedCourseId, setSelectedCourseId] = useState(null); const searchIndex = useMemo( () => new Map(courses.map((course) => [course.id, courseSearchText(course)])), [courses], ); const programCounts = useMemo(() => countBy(courses, "program"), [courses]); const levelCounts = useMemo(() => countBy(courses, "level"), [courses]); const semesterCounts = useMemo(() => countBy(courses, "semester"), [courses]); const selectedCourse = useMemo( () => courses.find((course) => course.id === selectedCourseId), [courses, selectedCourseId], ); const filteredCourses = useMemo(() => { const normalizedQuery = query.trim().toLowerCase(); return courses.filter((course) => { if ( normalizedQuery && !searchIndex.get(course.id)?.includes(normalizedQuery) ) { return false; } if (selectedProgram !== ALL && course.program !== selectedProgram) { return false; } if (selectedLevel !== ALL && course.level !== selectedLevel) { return false; } if (selectedSemester !== ALL && course.semester !== selectedSemester) { return false; } if ( selectedCredit !== ALL && !course.credits.some((credit) => String(credit) === selectedCredit) ) { return false; } return true; }); }, [ courses, query, searchIndex, selectedCredit, selectedLevel, selectedProgram, selectedSemester, ]); const sortedCourses = useMemo(() => { return [...filteredCourses].sort((a, b) => { const titleOrder = a.title.localeCompare(b.title, "en", { sensitivity: "base", }); const codeOrder = a.code.localeCompare(b.code, "en", { numeric: true, sensitivity: "base", }); const order = titleOrder || codeOrder; return sortDirection === "asc" ? order : -order; }); }, [filteredCourses, sortDirection]); const filtersActive = query || selectedProgram !== ALL || selectedLevel !== ALL || selectedSemester !== ALL || selectedCredit !== ALL; function clearFilters() { setQuery(""); setSelectedProgram(ALL); setSelectedLevel(ALL); setSelectedSemester(ALL); setSelectedCredit(ALL); } const SortIcon = sortDirection === "asc" ? ArrowUpAZ : ArrowDownZA; return (
{coursesData.metadata.sourceLabel}

Courses

Browse the CSE course catalogue by course code, title, level, program, credit count, and semester format.

setSelectedCourseId(course.id)} />
setQuery(event.target.value)} placeholder="Search code, title, instructor, content" className="pl-9" />

Showing{" "} {filteredCourses.length} {" "} of {courses.length} catalogue entries

{sortedCourses.map((course) => ( setSelectedCourseId(nextCourse.id)} /> ))}
{sortedCourses.length === 0 && (

No courses found

Adjust the filters or search terms.

)}
setSelectedCourseId(null)} />
); }