Skip to content

Commit

Permalink
Schedules builder now has a calendar where you can visualize your sch…
Browse files Browse the repository at this point in the history
…edule (#184)

* Calendar MVP

* Add tabs for schedule view

* Fixed bug in getting time

* Temporary fix to scrolling

* Added colors

* Added colors

* Shifted position of tabs

* Refactored handling of selecting sessions

* Removed line-clamp which is now included by default

* Fix CSS

* Added nullish check, removed console.log

* Updated margins

* Added nothing in your schedule yet

* Added no lectures

* Added dropdown to add automatically to schedule

* Added dropdown to add automatically to schedule

* Sort semesters

* Fix color issue

* Fixed compatibility issues

* Change location of show/hide calendar

* Make show button more consistent

* Changed schedules course search bar to make sense in small screens

* Made sidebar smaller when in small screen

* Select lecture based on section

* Added remove button in section selector

* Scrolling for sidebar

* Scrolling for sidebar

* Give space at bottom of overflow

* Changed from listbox to radiogroup

* Changed to flushedbutton

* Remove unused imports

* Refactored some code

* Made sure text sizes are ok

* Cosmetic change

* Cosmetic change

* Show possible on hover

* Light colors for hover

* Show message when course not in selected semester

* Remove existing lecture in calendar on hover

---------

Co-authored-by: xavilien <“[email protected]”>
  • Loading branch information
Xavilien and xavilien authored Dec 1, 2024
1 parent 54b8f23 commit f6a3a35
Show file tree
Hide file tree
Showing 15 changed files with 720 additions and 66 deletions.
2 changes: 2 additions & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"passlink": "^1.1.0",
"posthog-js": "^1.181.0",
"react": "^18.2.0",
"react-big-calendar": "^1.15.0",
"react-dom": "^18.2.0",
"react-headless-pagination": "^0.1.0",
"react-hot-toast": "^2.2.0",
Expand All @@ -50,6 +51,7 @@
"@types/jest": "^27.0.1",
"@types/node": "^16.9.1",
"@types/react": "^17.0.21",
"@types/react-big-calendar": "^1.8.12",
"@types/react-dom": "^17.0.9",
"@types/react-redux": "^7.1.18",
"autoprefixer": "^10.4.0",
Expand Down
31 changes: 30 additions & 1 deletion apps/frontend/src/app/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,33 @@ export const GENED_SOURCES = {
Dietrich: "https://www.cmu.edu/dietrich/gened/fall-2021-and-beyond/course-options/index.html",
};

export const STALE_TIME = 1000 * 60 * 60 * 24; // 1 day
export const STALE_TIME = 1000 * 60 * 60 * 24; // 1 day

export const CAL_VIEW = "cal";
export const SCHED_VIEW = "sched";

export const CALENDAR_COLORS = [
"#FFB3BA", // Red
"#FFDFBA", // Orange
"#FFFFBA", // Yellow
"#BAFFC9", // Green
"#BAE1FF", // Blue
"#D4BAFF", // Purple
"#FFC4E1", // Pink
"#C4E1FF", // Sky Blue
"#E1FFC4", // Lime
"#FFF4BA" // Cream
];

export const CALENDAR_COLORS_LIGHT = [
"#FFE1E3", // Light Red
"#FFF2E3", // Light Orange
"#FFFFE3", // Light Yellow
"#E3FFE9", // Light Green
"#E3F3FF", // Light Blue
"#EEE3FF", // Light Purple
"#FFE7F3", // Light Pink
"#E7F3FF", // Light Sky Blue
"#F3FFE7", // Light Lime
"#FFFBE3" // Light Cream
];
18 changes: 10 additions & 8 deletions apps/frontend/src/app/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type Semester = "fall" | "spring" | "summer";
export type Semester = "fall" | "spring" | "summer" | "";
export type SummerSession =
| "summer one"
| "summer two"
Expand Down Expand Up @@ -32,17 +32,19 @@ export interface Course {
fces?: FCE[];
}

export interface Time {
days: number[];
begin: string;
end: string;
building: string;
room: string;
}

interface Lesson {
instructors: string[];
name: string;
location: string;
times: {
days: number[];
begin: string;
end: string;
building: string;
room: string;
}[];
times: Time[];
}

export type Lecture = Lesson;
Expand Down
9 changes: 7 additions & 2 deletions apps/frontend/src/app/user.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { addToSet, removeFromSet } from "./utils";
import { SEMESTERS_COUNTED } from "./constants";
import {CAL_VIEW, SCHED_VIEW, SEMESTERS_COUNTED} from "./constants";
import { Semester } from "./types";

export interface UserState {
Expand All @@ -24,6 +24,7 @@ export interface UserState {
};
selectedSchool: string;
selectedTags: string[];
scheduleView: string;
}

const initialState: UserState = {
Expand All @@ -47,6 +48,7 @@ const initialState: UserState = {
},
selectedSchool: "SCS",
selectedTags: [],
scheduleView: "cal",
};

export const userSlice = createSlice({
Expand Down Expand Up @@ -102,7 +104,10 @@ export const userSlice = createSlice({
},
setSelectedTags: (state, action: PayloadAction<string[]>) => {
state.selectedTags = action.payload;
}
},
toggleScheduleView: (state) => {
state.scheduleView = state.scheduleView === CAL_VIEW ? SCHED_VIEW : CAL_VIEW;
},
},
});

Expand Down
105 changes: 85 additions & 20 deletions apps/frontend/src/app/userSchedules.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { addToSet, removeFromSet } from "./utils";
import { addToSet, getCalendarColor, removeFromSet, sessionToString } from "./utils";
import { Session } from "./types";
import { v4 as uuidv4 } from "uuid";
import { RootState } from "./store";

export interface CourseSessions {
[courseID: string]: {
[sessionType: string]: string;
Color: string;
};
}

export interface HoverSession {
courseID: string;
[sessionType: string]: string;
}

export interface UserSchedule {
name: string;
courses: string[];
selected: string[];
id: string;
session?: Session;
session: Session;
courseSessions: CourseSessions;
numColors: number;
hoverSession?: HoverSession;
}

export interface UserSchedulesState {
Expand All @@ -22,6 +37,24 @@ const initialState: UserSchedulesState = {
saved: {},
};

const getNewUserSchedule = (courseIDs: string[], id: string) : UserSchedule => {
return {
name: "My Schedule",
courses: courseIDs,
selected: courseIDs,
id: id,
session: {
year: "",
semester: "",
},
courseSessions: courseIDs.reduce((acc: CourseSessions, courseID, i: number) => {
acc[courseID] = {Lecture: "", Section: "", Color: getCalendarColor(i)};
return acc;
}, {}),
numColors: courseIDs.length,
};
}

export const userSchedulesSlice = createSlice({
name: "userSchedules",
initialState,
Expand All @@ -32,12 +65,7 @@ export const userSchedulesSlice = createSlice({
addCourseToActiveSchedule: (state, action: PayloadAction<string>) => {
if (state.active === null) {
const newId = uuidv4();
state.saved[newId] = {
name: "My Schedule",
courses: [],
selected: [],
id: newId,
};
state.saved[newId] = getNewUserSchedule([], newId);
state.active = newId;
}
state.saved[state.active].courses = addToSet(
Expand All @@ -48,6 +76,8 @@ export const userSchedulesSlice = createSlice({
state.saved[state.active].selected,
action.payload
);
state.saved[state.active].courseSessions[action.payload] = {Lecture: "", Section: "", Color: getCalendarColor(state.saved[state.active].numColors)};
state.saved[state.active].numColors += 1;
},
removeCourseFromActiveSchedule: (state, action: PayloadAction<string>) => {
if (state.active === null) return;
Expand All @@ -59,6 +89,8 @@ export const userSchedulesSlice = createSlice({
state.saved[state.active].selected,
action.payload
);

delete state.saved[state.active].courseSessions[action.payload];
},
selectCourseInActiveSchedule: (state, action: PayloadAction<string>) => {
if (state.active === null) return;
Expand Down Expand Up @@ -88,22 +120,12 @@ export const userSchedulesSlice = createSlice({
},
createEmptySchedule: (state) => {
const newId = uuidv4();
state.saved[newId] = {
name: "My Schedule",
selected: [],
courses: [],
id: newId,
};
state.saved[newId] = getNewUserSchedule([], newId);
state.active = newId;
},
createSharedSchedule: (state, action: PayloadAction<string[]>) => {
const newId = uuidv4();
state.saved[newId] = {
name: "Shared Schedule",
selected: action.payload,
courses: action.payload,
id: newId,
};
state.saved[newId] = getNewUserSchedule(action.payload, newId);
state.active = newId;
},
deleteSchedule: (state, action: PayloadAction<string>) => {
Expand All @@ -123,6 +145,26 @@ export const userSchedulesSlice = createSlice({
state.saved[state.active].name = action.payload;
}
},
updateActiveScheduleSession: (state, action: PayloadAction<Session>) => {
if (state.active !== null) {
state.saved[state.active].session = action.payload;
}
},
updateActiveScheduleCourseSession: (state, action: PayloadAction<{ courseID: string, sessionType: string, session: string }>) => {
if (state.active !== null) {
state.saved[state.active].courseSessions[action.payload.courseID][action.payload.sessionType] = action.payload.session
}
},
setHoverSession: (state, action: PayloadAction<{ courseID: string, [sessionType: string]: string }>) => {
if (state.active !== null) {
state.saved[state.active].hoverSession = action.payload;
}
},
clearHoverSession: (state) => {
if (state.active !== null) {
state.saved[state.active].hoverSession = undefined;
}
},
},
});

Expand All @@ -138,4 +180,27 @@ export const selectSelectedCoursesInActiveSchedule = (
return state.schedules.saved[state.schedules.active].selected;
};

export const selectSessionInActiveSchedule = (
state: RootState
): string => {
if (state.schedules.active === null) return "";
const session = state.schedules.saved[state.schedules.active].session;
if (session?.semester === "") return "";
return sessionToString(session);
};

export const selectCourseSessionsInActiveSchedule = (
state: RootState
): CourseSessions => {
if (state.schedules.active === null) return {};
return state.schedules.saved[state.schedules.active].courseSessions;
};

export const selectHoverSessionInActiveSchedule = (
state: RootState
): { courseID: string, [sessionType: string]: string } | undefined => {
if (state.schedules.active === null) return undefined;
return state.schedules.saved[state.schedules.active].hoverSession;
};

export const reducer = userSchedulesSlice.reducer;
53 changes: 48 additions & 5 deletions apps/frontend/src/app/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import reactStringReplace from "react-string-replace";
import Link from "~/components/Link";
import { FCE, Schedule, Session, Time } from "./types";
import { AggregateFCEsOptions, filterFCEs } from "./fce";
import { DEPARTMENT_MAP_NAME, DEPARTMENT_MAP_SHORTNAME } from "./constants";
import { DEPARTMENT_MAP_NAME, DEPARTMENT_MAP_SHORTNAME, CALENDAR_COLORS, CALENDAR_COLORS_LIGHT } from "./constants";
import namecase from "namecase";

export const courseIdRegex = /([0-9]{2}-?[0-9]{3})/g;
Expand All @@ -20,7 +20,9 @@ export const standardizeIdsInString = (str: string) => {
};

export const sessionToString = (sessionInfo: Session | FCE | Schedule) => {
const semester = sessionInfo.semester;
if (!sessionInfo) return "";

const semester = sessionInfo?.semester || "";

const sessionStrings = {
"summer one": "Summer One",
Expand All @@ -38,12 +40,14 @@ export const sessionToString = (sessionInfo: Session | FCE | Schedule) => {
if (semester === "summer" && sessionInfo.session) {
return `${sessionStrings[sessionInfo.session]} ${sessionInfo.year}`;
} else {
return `${semesterStrings[sessionInfo.semester]} ${sessionInfo.year}`;
return `${semesterStrings[semester]} ${sessionInfo.year}`;
}
};

export const sessionToShortString = (sessionInfo: Session | FCE | Schedule) => {
const semester = sessionInfo.semester;
if (!sessionInfo) return "";

const semester = sessionInfo?.semester || "";

const sessionStrings = {
"summer one": "M1",
Expand All @@ -61,10 +65,42 @@ export const sessionToShortString = (sessionInfo: Session | FCE | Schedule) => {
if (semester === "summer" && sessionInfo.session) {
return `${sessionStrings[sessionInfo.session]} ${sessionInfo.year}`;
} else {
return `${semesterStrings[sessionInfo.semester]} ${sessionInfo.year}`;
return `${semesterStrings[semester]} ${sessionInfo.year}`;
}
};

export const stringToSession = (sessionString: string): Session => {
const sessionStringSplit = sessionString.split(" ");

if (sessionStringSplit.length === 2) {
const [semester, year] = sessionStringSplit;
return {
semester: semester?.toLowerCase() as any,
year: year as string,
};
} else if (sessionStringSplit.length === 3) {
const [semester, session, year] = sessionStringSplit;

if (semester?.includes("Q")) {
return {
semester: "summer",
year: year as string,
session: "qatar summer",
};
}
return {
semester: semester?.toLowerCase() as any,
year: year as string,
session: `${semester} ${session}`.toLowerCase() as any,
};
}

return {
semester: "",
year: "",
};
}

export const compareSessions = (
session1: Session | FCE,
session2: Session | FCE
Expand Down Expand Up @@ -236,3 +272,10 @@ export function parseUnits(units: string): number {
}
return 0.0;
}

export const getCalendarColor = (i: number) => CALENDAR_COLORS[i % CALENDAR_COLORS.length] || "";

export const getCalendarColorLight = (color: string) => {
const index = CALENDAR_COLORS.indexOf(color);
return CALENDAR_COLORS_LIGHT[index];
}
Loading

0 comments on commit f6a3a35

Please sign in to comment.