1- import React , { createContext , useContext , useReducer , useEffect , ReactNode , useRef , useMemo , useCallback } from 'react' ;
1+ import React , { createContext , useContext , useReducer , useEffect , ReactNode , useRef , useMemo , useCallback , useState } from 'react' ;
22import { Course , AppState , PaletteConfig } from '../types/course' ;
33import { defaultData } from '../data/defaultData' ;
44import { optData } from '../data/optData' ;
@@ -51,21 +51,29 @@ const PALETTES: Record<string, PaletteConfig> = {
5151 }
5252} ;
5353
54+ // Define available generations
55+ export const GENERATIONS = [
56+ { id : 'genLegacy' , name : 'Malla 2023-2024' } ,
57+ { id : 'genNew' , name : 'Malla 2025' }
58+ ] ;
59+
5460interface CoursePlannerContextType {
5561 state : AppState ;
5662 dispatch : React . Dispatch < CoursePlannerAction > ;
5763 allCourses : Course [ ] ;
5864 findCourseData : ( courseId : string ) => Course | undefined ;
5965 getCurrentPalette : ( ) => PaletteConfig ;
6066 getAvailablePalettes : ( ) => PaletteConfig [ ] ;
67+ currentGeneration : string ;
68+ switchGeneration : ( genId : string ) => void ;
6169}
6270
6371type CoursePlannerAction =
6472 | { type : 'MOVE_COURSE' ; payload : { courseId : string ; fromContainer : string ; toContainer : string ; toIndex ?: number } }
6573 | { type : 'TOGGLE_COURSE_TAKEN' ; payload : { courseId : string } }
6674 | { type : 'ADD_SEMESTER' }
6775 | { type : 'DELETE_SEMESTER' ; payload : { semesterNumber : number } }
68- | { type : 'RESET_PLANNER' }
76+ | { type : 'RESET_PLANNER' ; payload : { genId : string } }
6977 | { type : 'TOGGLE_COURSE_POOL' }
7078 | { type : 'CREATE_CUSTOM_COURSE' ; payload : Course }
7179 | { type : 'UPDATE_COURSE' ; payload : { originalId : string ; course : Course } }
@@ -74,8 +82,9 @@ type CoursePlannerAction =
7482
7583const CoursePlannerContext = createContext < CoursePlannerContextType | undefined > ( undefined ) ;
7684
77- const STORAGE_KEY = 'coursePlannerState' ;
78- const DATA_VERSION = 1 ;
85+ const STORAGE_KEY_PREFIX = 'coursePlannerState' ;
86+ const CURRENT_GEN_KEY = 'coursePlannerCurrentGen' ;
87+ const DATA_VERSION = 2 ;
7988
8089const initialState : AppState = {
8190 semesters : [ ] ,
@@ -186,7 +195,7 @@ function coursePlannerReducer(state: AppState, action: CoursePlannerAction): App
186195 }
187196
188197 case 'RESET_PLANNER' : {
189- return initializeDefaultState ( ) ;
198+ return initializeDefaultState ( action . payload . genId ) ;
190199 }
191200
192201 case 'TOGGLE_COURSE_POOL' : {
@@ -244,10 +253,68 @@ function findCourseState(courseId: string, state: AppState) {
244253 return state . coursePool . find ( c => c . id === courseId ) ;
245254}
246255
247- function initializeDefaultState ( ) : AppState {
248- const semesters = defaultData . map ( sem => ( {
256+ function initializeDefaultState ( genId : string = 'genLegacy' ) : AppState {
257+ // Deep copy default data
258+ let semestersData = JSON . parse ( JSON . stringify ( defaultData ) ) ;
259+
260+ // Modify for 2025 Generation
261+ if ( genId === 'genNew' ) {
262+ const courseMoves = [
263+ { id : 'IIC2343' , toSem : 4 } , // Arqui: Sem 2 -> Sem 4
264+ { id : 'IIC2333' , toSem : 5 } , // SO: Sem 4 -> Sem 5
265+ { id : 'OPTC1' , toSem : 2 } // Opt Ciencia: Sem 5 -> Sem 2
266+ ] ;
267+
268+ // Helper to find and remove course from any semester
269+ const extractCourse = ( id : string ) => {
270+ for ( const sem of semestersData ) {
271+ const idx = sem . courses . findIndex ( ( c : any ) => c . id === id ) ;
272+ if ( idx !== - 1 ) {
273+ return sem . courses . splice ( idx , 1 ) [ 0 ] ;
274+ }
275+ }
276+ return null ;
277+ } ;
278+
279+ // Store extracted courses
280+ const extractedCourses : Record < string , any > = { } ;
281+ courseMoves . forEach ( move => {
282+ extractedCourses [ move . id ] = extractCourse ( move . id ) ;
283+ } ) ;
284+
285+ // Insert into new locations maintaining order (before OFG/Teo)
286+ courseMoves . forEach ( move => {
287+ const course = extractedCourses [ move . id ] ;
288+ if ( course ) {
289+ const targetSem = semestersData . find ( ( s : any ) => s . sem === move . toSem ) ;
290+ if ( targetSem ) {
291+ // Find index of OFG or Teologico to insert before
292+ // Usually they are the last ones or have specific IDs like TEO123, OFG2, OFG3
293+ const lastCourseIndex = targetSem . courses . length > 0 ? targetSem . courses . length - 1 : 0 ;
294+ const lastCourse = targetSem . courses [ lastCourseIndex ] ;
295+
296+ if ( lastCourse && ( lastCourse . type === 'ofg' || lastCourse . id . startsWith ( 'OFG' ) || lastCourse . id === 'TEO123' ) ) {
297+ targetSem . courses . splice ( lastCourseIndex , 0 , course ) ;
298+ } else {
299+ targetSem . courses . push ( course ) ;
300+ }
301+ }
302+ }
303+ } ) ;
304+
305+ // Update Practice Credits (IIC2002)
306+ // Find IIC2002 and update cred to 10
307+ semestersData . forEach ( ( sem : any ) => {
308+ const practice = sem . courses . find ( ( c : any ) => c . id === 'IIC2002' ) ;
309+ if ( practice ) {
310+ practice . cred = "10" ;
311+ }
312+ } ) ;
313+ }
314+
315+ const semesters = semestersData . map ( ( sem : any ) => ( {
249316 number : sem . sem ,
250- courses : sem . courses . map ( course => ( {
317+ courses : sem . courses . map ( ( course : any ) => ( {
251318 id : course . id ,
252319 type : course . type ,
253320 taken : false
@@ -275,9 +342,15 @@ function initializeDefaultState(): AppState {
275342 } ;
276343}
277344
278- function loadStateFromStorage ( ) : AppState | null {
345+ function getStorageKey ( genId : string ) {
346+ if ( genId === 'genLegacy' ) return STORAGE_KEY_PREFIX ;
347+ return `${ STORAGE_KEY_PREFIX } _${ genId } ` ;
348+ }
349+
350+ function loadStateFromStorage ( genId : string ) : AppState | null {
279351 try {
280- const savedState = localStorage . getItem ( STORAGE_KEY ) ;
352+ const key = getStorageKey ( genId ) ;
353+ const savedState = localStorage . getItem ( key ) ;
281354 if ( ! savedState ) return null ;
282355
283356 const parsedState = JSON . parse ( savedState ) ;
@@ -292,7 +365,7 @@ function loadStateFromStorage(): AppState | null {
292365 typeof parsedState . coursePoolVisible === 'boolean'
293366 ) {
294367 if ( parsedState . version !== DATA_VERSION ) {
295- console . warn ( 'State version mismatch (expected ' + DATA_VERSION + '), resetting state to apply updates .' ) ;
368+ console . warn ( 'State version mismatch (expected ' + DATA_VERSION + '), resetting state.' ) ;
296369 return null ;
297370 }
298371
@@ -313,44 +386,77 @@ function loadStateFromStorage(): AppState | null {
313386 }
314387}
315388
316- function saveStateToStorage ( state : AppState ) : void {
389+ function saveStateToStorage ( state : AppState , genId : string ) : void {
317390 try {
318- localStorage . setItem ( STORAGE_KEY , JSON . stringify ( state ) ) ;
391+ const key = getStorageKey ( genId ) ;
392+ localStorage . setItem ( key , JSON . stringify ( state ) ) ;
319393 } catch ( error ) {
320394 console . error ( 'Failed to save state to localStorage:' , error ) ;
321395 }
322396}
323397
324398export function CoursePlannerProvider ( { children } : { children : ReactNode } ) {
325399 const hasLoadedFromStorage = useRef ( false ) ;
400+ const [ currentGeneration , setCurrentGeneration ] = useState ( ( ) => {
401+ return localStorage . getItem ( CURRENT_GEN_KEY ) || 'genLegacy' ;
402+ } ) ;
326403
327404 const [ state , dispatch ] = useReducer (
328405 coursePlannerReducer ,
329406 initialState ,
330407 ( _initial ) => {
331- const savedState = loadStateFromStorage ( ) ;
408+ const initialGen = localStorage . getItem ( CURRENT_GEN_KEY ) || 'genLegacy' ;
409+ const savedState = loadStateFromStorage ( initialGen ) ;
332410 if ( savedState ) {
333411 hasLoadedFromStorage . current = true ;
334412 return savedState ;
335413 }
336- return initializeDefaultState ( ) ;
414+ return initializeDefaultState ( initialGen ) ;
337415 }
338416 ) ;
339417
340418 const allCourses = useMemo ( ( ) : Course [ ] => {
419+ // Determine base data based on current generation?
420+ // Ideally we should reflect the changes in allCourses too if we want tooltips to be correct regarding semesters/credits?
421+ // However, defaultData is static.
422+ // For now, modifiedCourses will handle overrides if we treated them as such, but here we are moving them in semesters.
423+ // The "allCourses" is mainly used for palette and looking up static info.
424+ // The Credits change for IIC2002 needs to be reflected here too.
425+
426+ let baseCourses = [ ...defaultData . flatMap ( sem => sem . courses ) ] ;
427+
428+ if ( currentGeneration === 'genNew' ) {
429+ // Update IIC2002 credits in this view as well
430+ baseCourses = baseCourses . map ( c => {
431+ if ( c . id === 'IIC2002' ) return { ...c , cred : "10" } ;
432+ return c ;
433+ } ) ;
434+ }
435+
341436 return [
342- ...defaultData . flatMap ( sem => sem . courses ) ,
437+ ...baseCourses ,
343438 ...optData [ 0 ] . courses ,
344439 ...engData [ 0 ] . courses ,
345440 ...state . customCourses
346441 ] ;
347- } , [ state . customCourses ] ) ;
442+ } , [ state . customCourses , currentGeneration ] ) ;
348443
349444 const findCourseData = useCallback ( ( courseId : string ) : Course | undefined => {
350- if ( state . modifiedCourses [ courseId ] ) {
351- return state . modifiedCourses [ courseId ] ;
445+ const defaultCourse = allCourses . find ( course => course . id === courseId ) ;
446+ const modifiedCourse = state . modifiedCourses [ courseId ] ;
447+
448+ if ( modifiedCourse ) {
449+ if ( defaultCourse ) {
450+ return {
451+ ...modifiedCourse ,
452+ parity : defaultCourse . parity ,
453+ prereq : defaultCourse . prereq ,
454+ cred : defaultCourse . cred // Ensure credit updates propagate if they are static updates
455+ } ;
456+ }
457+ return modifiedCourse ;
352458 }
353- return allCourses . find ( course => course . id === courseId ) ;
459+ return defaultCourse ;
354460 } , [ allCourses , state . modifiedCourses ] ) ;
355461
356462 const getCurrentPalette = useCallback ( ( ) : PaletteConfig => {
@@ -360,23 +466,37 @@ export function CoursePlannerProvider({ children }: { children: ReactNode }) {
360466 const getAvailablePalettes = useCallback ( ( ) : PaletteConfig [ ] => {
361467 return Object . values ( PALETTES ) ;
362468 } , [ ] ) ;
469+
470+ const switchGeneration = useCallback ( ( newGenId : string ) => {
471+ if ( newGenId === currentGeneration ) return ;
472+
473+ saveStateToStorage ( state , currentGeneration ) ;
474+
475+ const newState = loadStateFromStorage ( newGenId ) || initializeDefaultState ( newGenId ) ;
476+ dispatch ( { type : 'LOAD_STATE' , payload : newState } ) ;
477+
478+ setCurrentGeneration ( newGenId ) ;
479+ localStorage . setItem ( CURRENT_GEN_KEY , newGenId ) ;
480+ } , [ currentGeneration , state ] ) ;
363481
364482 useEffect ( ( ) => {
365483 if ( hasLoadedFromStorage . current ) {
366- saveStateToStorage ( state ) ;
484+ saveStateToStorage ( state , currentGeneration ) ;
367485 } else {
368486 hasLoadedFromStorage . current = true ;
369487 }
370- } , [ state ] ) ;
488+ } , [ state , currentGeneration ] ) ;
371489
372490 const value = useMemo ( ( ) => ( {
373491 state,
374492 dispatch,
375493 allCourses,
376494 findCourseData,
377495 getCurrentPalette,
378- getAvailablePalettes
379- } ) , [ state , allCourses , findCourseData , getCurrentPalette , getAvailablePalettes ] ) ;
496+ getAvailablePalettes,
497+ currentGeneration,
498+ switchGeneration
499+ } ) , [ state , allCourses , findCourseData , getCurrentPalette , getAvailablePalettes , currentGeneration , switchGeneration ] ) ;
380500
381501 return (
382502 < CoursePlannerContext . Provider value = { value } >
0 commit comments