From 66a7686cdae656626136dc178806f42d96d49161 Mon Sep 17 00:00:00 2001 From: norioriroiron <97643161+norioriroiron@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:11:21 -0400 Subject: [PATCH 1/3] scaffold layout and styling --- src/pages/Planner.tsx | 58 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index bfda4c9..b878eda 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -18,11 +18,40 @@ // - In-planner prereq highlighting (click a course, highlight its prereqs in earlier terms and unlocks in later terms) import { useMemo } from 'react'; -import { usePlannerStore, termLabel } from '@/store/plannerStore'; +import { usePlannerStore, termLabel, type Term } from '@/store/plannerStore'; import { courses } from '@/data/loadCourses'; import { validatePlan } from '@/lib/validatePlan'; import TermCell from '@/components/TermCell'; import ViolationList from '@/components/ViolationList'; +import type { Season } from '@/types/planner'; + +interface YearGrouping { + year: number; + seasons: { season: Season; term: Term }[]; +} + +function createYearGrouping(terms: Term[]): YearGrouping[] { + const years = new Map>(); + + for (const term of terms) { + let seasonMap = years.get(term.year); + if (!seasonMap) { + const map = new Map(); + years.set(term.year, map); + seasonMap = map; + } + + seasonMap.set(term.season, term); + } + + return [...years.entries()].map(([year, seasons]) => ({ + year, + seasons: [...seasons.entries()].map(([season, term]) => ({ + season, + term, + })), + })); +} export default function Planner() { const terms = usePlannerStore((s) => s.terms); @@ -40,6 +69,8 @@ export default function Planner() { [terms], ); + const grouping = useMemo(() => createYearGrouping(terms), [terms]); + return (
@@ -50,14 +81,23 @@ export default function Planner() { registrar.

-
- {terms.map((term) => ( - +
+ {grouping.map((yearGrouping) => ( +
+

+ YEAR {yearGrouping.year} +

+ +
+ {yearGrouping.seasons.map((seasonGrouping) => ( +
+

+ {seasonGrouping.season.toUpperCase()} +

+
+ ))} +
+
))}
From 0dee4defe4b5fa22f72102b31f4392304d9eec1e Mon Sep 17 00:00:00 2001 From: norioriroiron <97643161+norioriroiron@users.noreply.github.com> Date: Thu, 18 Jun 2026 03:18:13 -0400 Subject: [PATCH 2/3] add input boxes --- src/pages/Planner.tsx | 144 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 133 insertions(+), 11 deletions(-) diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index b878eda..7fc59db 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -17,13 +17,12 @@ // - In-planner course detail panel (shared component with Explorer) // - In-planner prereq highlighting (click a course, highlight its prereqs in earlier terms and unlocks in later terms) -import { useMemo } from 'react'; +import { useMemo, useState, type FormEvent } from 'react'; import { usePlannerStore, termLabel, type Term } from '@/store/plannerStore'; import { courses } from '@/data/loadCourses'; import { validatePlan } from '@/lib/validatePlan'; -import TermCell from '@/components/TermCell'; import ViolationList from '@/components/ViolationList'; -import type { Season } from '@/types/planner'; +import type { PlannerEntry, Season } from '@/types/planner'; interface YearGrouping { year: number; @@ -53,6 +52,130 @@ function createYearGrouping(terms: Term[]): YearGrouping[] { })); } +interface CourseRow { + termId: string; + course: PlannerEntry | null; + index: number | null; +} + +function createCourseRows(term: Term): CourseRow[] { + const entries = term.entries; + const rows = Array.from({ length: 5 }, (_, i) => { + const entry = entries[i]; + return { + termId: term.id, + course: entry ?? null, + index: entry ? i : null, + }; + }); + + return rows; +} + +function SeasonColumn({ term }: { term: Term }) { + const [courseRows, setCourseRows] = useState( + createCourseRows(term), + ); + + function updateRow(updatedRow: CourseRow, rowIndex: number) { + setCourseRows((currentRows) => + currentRows.map((row, i) => (i === rowIndex ? updatedRow : row)), + ); + } + + return ( +
+

+ {term.season.toUpperCase()} +

+ +
+ {courseRows.map((row, i) => ( + + ))} +
+
+ ); +} + +function CourseInput({ + index, + courseRow, + term, + onUpdate, +}: { + index: number; + courseRow: CourseRow; + term: Term; + onUpdate: (row: CourseRow, index: number) => void; +}) { + const { addCourse, removeEntry } = usePlannerStore(); + const [input, setInput] = useState( + courseRow.course?.kind === 'course' ? courseRow.course.code : '', + ); + + function submit(e: FormEvent) { + e.preventDefault(); + const code = input.trim().toUpperCase().replace(/\s+/g, ' '); + + if (code === '' && courseRow.index !== null) { + removeEntry(courseRow.termId, courseRow.index); + onUpdate( + { + ...courseRow, + course: null, + index: null, + }, + index, + ); + return; + } + + if (!courses.has(code)) { + return; + } + + if (courseRow.index) { + removeEntry(courseRow.termId, courseRow.index); + } + + addCourse(courseRow.termId, code); + + const updatedRow = { + ...courseRow, + course: term.entries[term.entries.length], + index: term.entries.length, + }; + + onUpdate(updatedRow, index); + } + + return ( +
+ setInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + submit(e); + e.currentTarget.blur(); + } + }} + onBlur={submit} + placeholder="e.g. COMP 1405" + className="flex-1 rounded border border-gray-300 px-2 py-1 text-base" + /> +
+ ); +} + export default function Planner() { const terms = usePlannerStore((s) => s.terms); @@ -82,19 +205,18 @@ export default function Planner() {

- {grouping.map((yearGrouping) => ( -
+ {grouping.map((yearGrouping, i) => ( +

YEAR {yearGrouping.year}

- {yearGrouping.seasons.map((seasonGrouping) => ( -
-

- {seasonGrouping.season.toUpperCase()} -

-
+ {yearGrouping.seasons.map((seasonGrouping, i) => ( + ))}
From 2f0328c4fc1fb1833c00fcf61c5e53f07c997a64 Mon Sep 17 00:00:00 2001 From: norioriroiron <97643161+norioriroiron@users.noreply.github.com> Date: Fri, 19 Jun 2026 02:00:48 -0400 Subject: [PATCH 3/3] update styling --- src/pages/Planner.tsx | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/pages/Planner.tsx b/src/pages/Planner.tsx index 7fc59db..fda1575 100644 --- a/src/pages/Planner.tsx +++ b/src/pages/Planner.tsx @@ -84,12 +84,12 @@ function SeasonColumn({ term }: { term: Term }) { } return ( -
-

- {term.season.toUpperCase()} +
+

+

{term.season}

-
+
{courseRows.map((row, i) => ( +
createYearGrouping(terms), [terms]); return ( @@ -204,17 +206,16 @@ export default function Planner() { registrar.

-
+
{grouping.map((yearGrouping, i) => ( -
-

- YEAR {yearGrouping.year} +
+

+

+ {yearToWord[yearGrouping.year - 1]} YEAR +

-
+
{yearGrouping.seasons.map((seasonGrouping, i) => ( ))}