BankPanel.jsx UI component
The right sidebar. Toggles between two views: a searchable course bank (unplaced courses) and the graduation requirements panel. Also contains the placed-out section, substitution UI, and a user-resizable width handle.
View structure
Course bank — filtering and sorting
course.id (e.g. "CS3500") and course.title. Stored in PlannerContext so the search state persists when switching tabs.Courses are grouped by subject code (e.g., all "CS" courses under one header) when in "all" mode. In "search" and "starred" modes, they're shown as a flat list in the selected sort order.
Co-op and internship templates
Fixed blocks at the bottom of the bank, always visible regardless of search. Defined in constants.js → COOP_TERMS / INTERNSHIP_TERMS. Dragging one of these into a semester creates a work/internship block rather than a course placement. They're identified by a special prefix in their ID ("coop-" or "int-").
Placed-out section
Courses in placedOut are shown in a collapsible list at the bottom. They can be dragged back into semesters or back into the bank. Clicking the ✕ on a placed-out card calls onDropBank(courseId) to return it.
Substitution UI
Two search fields: "From course" and "To course." Setting both calls addSubstitution({ from, to }) in context. The substitution means "I took FROM, let it count as TO in graduation requirements." Stored as substitutions array in plan state.
Resizable width
A drag handle on the left edge of BankPanel lets users adjust the sidebar width. The width is stored in bankWidth (context state, persisted to localStorage). Min: 200px, max: 500px. Implemented with a mousedown / mousemove listener on the handle div.