import { ELECTIVE_SETS } from '../data/electiveSets'; import { SPECIALIZATIONS } from '../data/specializations'; import { coursesBySet } from '../data/lookups'; import type { Term } from '../data/types'; import type { SetAnalysis } from '../solver/decisionTree'; // Reverse map: courseId → specialization names that require it const requiredForSpec: Record = {}; for (const spec of SPECIALIZATIONS) { if (spec.requiredCourseId) { (requiredForSpec[spec.requiredCourseId] ??= []).push(spec.name); } } interface CourseSelectionProps { pinnedCourses: Record; treeResults: SetAnalysis[]; treeLoading: boolean; disabledCourseIds: Set; onPin: (setId: string, courseId: string) => void; onUnpin: (setId: string) => void; onClearAll: () => void; } function ElectiveSet({ setId, setName, pinnedCourseId, analysis, loading, disabledCourseIds, onPin, onUnpin, }: { setId: string; setName: string; pinnedCourseId: string | null | undefined; analysis?: SetAnalysis; loading: boolean; disabledCourseIds: Set; onPin: (courseId: string) => void; onUnpin: () => void; }) { const courses = coursesBySet[setId]; const isPinned = pinnedCourseId != null; const pinnedCourse = isPinned ? courses.find((c) => c.id === pinnedCourseId) : null; // Build a map from courseId to ceiling choice data const ceilingMap = new Map( (analysis?.choices ?? []).map((ch) => [ch.courseId, ch]), ); const hasHighImpact = analysis && analysis.impact > 0; return (

{setName} {!isPinned && hasHighImpact && ( high impact )}

{isPinned && ( )}
{/* Pinned view */}
{pinnedCourse?.name}
{/* Course list view */}
{courses.map((course) => { const isCancelled = !!course.cancelled; const isDisabled = disabledCourseIds.has(course.id); const isUnavailable = isCancelled || isDisabled; const ceiling = ceilingMap.get(course.id); const reqFor = requiredForSpec[course.id]; const showSkeleton = loading && !analysis; return ( ); })}
); } const skeletonStyle = `@keyframes skeleton-pulse { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }`; export function CourseSelection({ pinnedCourses, treeResults, treeLoading, disabledCourseIds, onPin, onUnpin, onClearAll }: CourseSelectionProps) { const terms: Term[] = ['Spring', 'Summer', 'Fall']; const hasPinned = Object.keys(pinnedCourses).length > 0; // Index tree results by setId for O(1) lookup const treeBySet = new Map(treeResults.map((a) => [a.setId, a])); return (

Course Selection

Select one course per elective slot. Analysis shows how each choice affects your specializations.

{hasPinned && ( )}
{terms.map((term) => (

{term}

{ELECTIVE_SETS.filter((s) => s.term === term).map((set) => ( onPin(set.id, courseId)} onUnpin={() => onUnpin(set.id)} /> ))}
))}
); }