Repository: icssc/AntAlmanac
Pull request #918 (2024-02-21 02:22:12)

fix: show recruitment banner again
## Summary
1. The change to pass search queries as lowercase in #755 resulted in the Recruitment Banner never showing up, as the banner checked for *uppercase* queries, which are now *lowercase*.
2. Use `.toUpperCase()` on the value we grab from `RightPaneStore`

## Test Plan
1. Go into Chrome Dev Tools and the application tab to clear your `recruitmentDismissalTime`. Then search for COMPSCI and see if it pops up. 

<!-- [Optional]
## Future Followup
-->



Patch:
diff --git a/apps/antalmanac/src/components/Calendar/CalendarRoot.tsx b/apps/antalmanac/src/components/Calendar/CalendarRoot.tsx
index b0f3d5f2..b1dccb66 100644
--- a/apps/antalmanac/src/components/Calendar/CalendarRoot.tsx
+++ b/apps/antalmanac/src/components/Calendar/CalendarRoot.tsx
@@ -278,6 +278,7 @@ export default function ScheduleCalendar(props: ScheduleCalendarProps) {
                     step={15}
                     timeslots={2}
                     date={date}
+                    onNavigate={() => undefined}
                     min={getStartTime()}
                     max={new Date(2018, 0, 1, 23)}
                     events={events}
diff --git a/apps/antalmanac/src/components/Calendar/CourseCalendarEvent.tsx b/apps/antalmanac/src/components/Calendar/CourseCalendarEvent.tsx
index 91780d68..15ea5eca 100644
--- a/apps/antalmanac/src/components/Calendar/CourseCalendarEvent.tsx
+++ b/apps/antalmanac/src/components/Calendar/CourseCalendarEvent.tsx
@@ -304,7 +304,7 @@ const CourseCalendarEvent = (props: CourseCalendarEventProps) => {
                             to={`/map?location=${building ?? 0}`}
                             onClick={focusMap}
                         >
-                            {buildingCatalogue[+building].name}
+                            {buildingCatalogue[+building]?.name ?? ''}
                         </Link>
                     </div>
                 )}
diff --git a/apps/antalmanac/src/components/Calendar/Toolbar/CustomEventDialog/CustomEventDialog.tsx b/apps/antalmanac/src/components/Calendar/Toolbar/CustomEventDialog/CustomEventDialog.tsx
index 4fd5978d..95efe376 100644
--- a/apps/antalmanac/src/components/Calendar/Toolbar/CustomEventDialog/CustomEventDialog.tsx
+++ b/apps/antalmanac/src/components/Calendar/Toolbar/CustomEventDialog/CustomEventDialog.tsx
@@ -226,8 +226,8 @@ function CustomEventDialogs(props: CustomEventDialogProps) {
                         {disabled
                             ? 'Schedule and day must be checked'
                             : props.customEvent
-                            ? 'Save Changes'
-                            : 'Add Event'}
+                              ? 'Save Changes'
+                              : 'Add Event'}
                     </Button>
                 </DialogActions>
             </Dialog>
diff --git a/apps/antalmanac/src/components/Map/Map.tsx b/apps/antalmanac/src/components/Map/Map.tsx
index 58c93c8a..d222015c 100644
--- a/apps/antalmanac/src/components/Map/Map.tsx
+++ b/apps/antalmanac/src/components/Map/Map.tsx
@@ -78,7 +78,7 @@ export function getCoursesPerBuilding() {
 }
 
 export function getCustomEventPerBuilding() {
-    const customEvents = AppStore.getCustomEvents();
+    const customEvents = AppStore.getCustomEventsInCalendar();
 
     const customEventBuildings = customEvents.map((e) => e.building).filter(notNull);
 
@@ -98,9 +98,9 @@ export function getCustomEventPerBuilding() {
 
     interface localCustomEventType {
         title: string;
-        start: string;
-        end: string;
-        days: boolean[];
+        start: Date;
+        end: Date;
+        days: string[];
         customEventID: number;
         color?: string | undefined;
         building?: string | undefined;
@@ -251,20 +251,24 @@ export default function CourseMap() {
 
         const markersToday =
             today === 'All' ? markerValues : markerValues.filter((course) => course.start.toString().includes(today));
+
         return markersToday
             .sort((a, b) => a.start.getTime() - b.start.getTime())
             .filter((marker, i, arr) => arr.findIndex((other) => other.sectionCode === marker.sectionCode) === i);
     }, [markers, today]);
 
     const customEventMarkersToDisplay = useMemo(() => {
-        const markerValues = Object.keys(customEventMarkers).flatMap((markerKey) => customEventMarkers[markerKey]);
+        const markerValues = Object.keys(customEventMarkers)
+            .flatMap((markerKey) => customEventMarkers[markerKey])
+            .filter((marker, i, arr) => arr.findIndex((other) => other.key === marker.key) === i);
 
         const markersToday =
             today === 'All'
                 ? markerValues
                 : markerValues.filter((event) => {
-                      return event.days.some((day, index) => day && WORK_WEEK[index] === today);
+                      return event.days.some((day) => day && today.includes(day));
                   });
+
         return markersToday.sort((a, b) => {
             const startDateA = new Date(`1970-01-01T${a.start}`);
             const startDateB = new Date(`1970-01-01T${b.start}`);
@@ -277,13 +281,16 @@ export default function CourseMap() {
      */
     const startDestPairs = useMemo(() => {
         const allEvents = [...markersToDisplay, ...customEventMarkersToDisplay];
-        return allEvents.reduce((acc, cur, index) => {
-            acc.push([cur]);
-            if (index > 0) {
-                acc[index - 1].push(cur);
-            }
-            return acc;
-        }, [] as (typeof allEvents)[]);
+        return allEvents.reduce(
+            (acc, cur, index) => {
+                acc.push([cur]);
+                if (index > 0) {
+                    acc[index - 1].push(cur);
+                }
+                return acc;
+            },
+            [] as (typeof allEvents)[]
+        );
     }, [markersToDisplay, customEventMarkersToDisplay]);
 
     return (
diff --git a/apps/antalmanac/src/components/RightPane/AddedCourses/CustomEventDetailView.tsx b/apps/antalmanac/src/components/RightPane/AddedCourses/CustomEventDetailView.tsx
index 9f7876ac..3c08df2c 100644
--- a/apps/antalmanac/src/components/RightPane/AddedCourses/CustomEventDetailView.tsx
+++ b/apps/antalmanac/src/components/RightPane/AddedCourses/CustomEventDetailView.tsx
@@ -74,7 +74,7 @@ const CustomEventDetailView = (props: CustomEventDetailViewProps) => {
             />
             <Box sx={{ margin: '0.75rem', color: '#bbbbbb', fontSize: '1rem' }}>
                 <Link to={`/map?location=${customEvent.building ?? 0}`} onClick={focusMap}>
-                    {customEvent.building ? buildingCatalogue[+customEvent.building].name : ''}
+                    {(customEvent.building && buildingCatalogue[+customEvent.building]?.name) || ''}
                 </Link>
             </Box>
 
diff --git a/apps/antalmanac/src/components/RightPane/CoursePane/CourseRenderPane.tsx b/apps/antalmanac/src/components/RightPane/CoursePane/CourseRenderPane.tsx
index 33b49df8..70f99aec 100644
--- a/apps/antalmanac/src/components/RightPane/CoursePane/CourseRenderPane.tsx
+++ b/apps/antalmanac/src/components/RightPane/CoursePane/CourseRenderPane.tsx
@@ -1,7 +1,7 @@
 import { useCallback, useEffect, useState } from 'react';
 import LazyLoad from 'react-lazyload';
 
-import { Alert, Box, GlobalStyles, IconButton } from '@mui/material';
+import { Alert, Box, GlobalStyles, IconButton, useMediaQuery } from '@mui/material';
 import { Close } from '@mui/icons-material';
 import { AACourse, AASection } from '@packages/antalmanac-types';
 import { WebsocDepartment, WebsocSchool, WebsocAPIResponse, GE } from 'peterportal-api-next-types';
@@ -27,7 +27,7 @@ function getColors() {
             accumulator[section.sectionCode] = section.color;
             return accumulator;
         },
-        {} as { [key: string]: string }
+        {} as Record<string, string>
     );
 
     return courseColors;
@@ -63,11 +63,15 @@ const RecruitmentBanner = () => {
     const dismissedRecently =
         recruitmentDismissalTime !== null &&
         Date.now() - parseInt(recruitmentDismissalTime) < 11 * 7 * 24 * 3600 * 1000;
-    const isSearchCS = ['COMPSCI', 'IN4MATX', 'I&C SCI', 'STATS'].includes(RightPaneStore.getFormData().deptValue);
+    const isSearchCS = ['COMPSCI', 'IN4MATX', 'I&C SCI', 'STATS'].includes(
+        RightPaneStore.getFormData().deptValue.toUpperCase()
+    );
     const displayRecruitmentBanner = bannerVisibility && !dismissedRecently && isSearchCS;
 
+    const isMobileScreen = useMediaQuery('(max-width: 750px)');
+
     return (
-        <Box sx={{ position: 'fixed', bottom: 5, right: 5, zIndex: 999 }}>
+        <Box sx={{ position: 'fixed', bottom: 5, right: isMobileScreen ? 5 : 75, zIndex: 999 }}>
             {displayRecruitmentBanner ? (
                 <Alert
                     icon={false}
diff --git a/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/HelpBox.tsx b/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/HelpBox.tsx
index 1fab92e8..e291c1dc 100644
--- a/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/HelpBox.tsx
+++ b/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/HelpBox.tsx
@@ -1,4 +1,16 @@
-import { Paper, ImageList, ImageListItem, Typography, Link, List, ListItemText, ListItem } from '@mui/material';
+import { Close } from '@mui/icons-material';
+import {
+    Paper,
+    ImageList,
+    ImageListItem,
+    Typography,
+    Link,
+    List,
+    ListItemText,
+    ListItem,
+    Box,
+    IconButton,
+} from '@mui/material';
 
 const images = [
     {
@@ -15,12 +27,21 @@ const images = [
     },
 ];
 
-function HelpBox() {
+interface HelpBoxProps {
+    onDismiss: () => void;
+}
+
+function HelpBox({ onDismiss }: HelpBoxProps) {
     return (
         <Paper variant="outlined" sx={{ padding: 2, marginBottom: '10px', marginRight: '5px' }}>
-            <Typography variant="h5" fontWeight="bold">
-                Need help planning your schedule?
-            </Typography>
+            <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
+                <Typography variant="h5" fontWeight="bold">
+                    Need help planning your schedule?
+                </Typography>
+                <IconButton aria-label="close" size="large" color="inherit" onClick={onDismiss}>
+                    <Close fontSize="inherit" />
+                </IconButton>
+            </Box>
 
             <List component="ol" sx={{ listStyle: 'decimal', pl: 2, pb: 0 }}>
                 <ListItem sx={{ display: 'list-item', p: 0 }}>
diff --git a/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/SearchForm.tsx b/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/SearchForm.tsx
index a3433549..2bdc6f56 100644
--- a/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/SearchForm.tsx
+++ b/apps/antalmanac/src/components/RightPane/CoursePane/SearchForm/SearchForm.tsx
@@ -50,6 +50,7 @@ const styles: Styles<Theme, object> = {
 const SearchForm = (props: { classes: ClassNameMap; toggleSearch: () => void }) => {
     const { classes, toggleSearch } = props;
     const { manualSearchEnabled, toggleManualSearch } = useCoursePaneStore();
+    const [helpBoxVisibility, setHelpBoxVisibility] = useState(true);
 
     const onFormSubmit = (event: FormEvent) => {
         event.preventDefault();
@@ -57,7 +58,21 @@ const SearchForm = (props: { classes: ClassNameMap; toggleSearch: () => void })
     };
 
     const currentMonthIndex = new Date().getMonth(); // 0=Jan
-    const activeMonthIndices = [false, false, false, false, false, false, false, false, true, true, false, false];
+    // Active months: February/March for Spring planning, May/June for Fall planning, July/August for Summer planning,
+    // and November/December for Winter planning
+    const activeMonthIndices = [false, true, true, false, true, true, true, true, false, false, true, true];
+
+    // Display the help box only if more than 30 days has passed since the last dismissal and
+    // the current month is an active month
+    const helpBoxDismissalTime = window.localStorage.getItem('helpBoxDismissalTime');
+    const dismissedRecently =
+        helpBoxDismissalTime !== null && Date.now() - parseInt(helpBoxDismissalTime) < 30 * 24 * 3600 * 1000;
+    const displayHelpBox = helpBoxVisibility && !dismissedRecently && activeMonthIndices[currentMonthIndex];
+
+    const onHelpBoxDismiss = () => {
+        window.localStorage.setItem('helpBoxDismissalTime', Date.now().toString());
+        setHelpBoxVisibility(false);
+    };
 
     return (
         <div className={classes.rightPane}>
@@ -95,7 +110,7 @@ const SearchForm = (props: { classes: ClassNameMap; toggleSearch: () => void })
                 </div>
             </form>
 
-            {activeMonthIndices[currentMonthIndex] && <HelpBox />}
+            {displayHelpBox && <HelpBox onDismiss={onHelpBoxDismiss} />}
             <PrivacyPolicyBanner />
         </div>
     );
diff --git a/apps/antalmanac/src/components/RightPane/SectionTable/EnrollmentHistoryPopup.tsx b/apps/antalmanac/src/components/RightPane/SectionTable/EnrollmentHistoryPopup.tsx
index deab64e6..03f02b87 100644
--- a/apps/antalmanac/src/components/RightPane/SectionTable/EnrollmentHistoryPopup.tsx
+++ b/apps/antalmanac/src/components/RightPane/SectionTable/EnrollmentHistoryPopup.tsx
@@ -1,18 +1,82 @@
-import { useState, useEffect, useMemo } from 'react';
-import { LineChart, Line, CartesianGrid, ResponsiveContainer, XAxis, YAxis, Tooltip, Legend } from 'recharts';
-import { Box, Link, Typography, Skeleton, useMediaQuery } from '@mui/material';
+import { useState, useEffect, useMemo, useCallback } from 'react';
+import {
+    LineChart,
+    Line,
+    CartesianGrid,
+    ResponsiveContainer,
+    XAxis,
+    YAxis,
+    Tooltip as RechartsTooltip,
+    Legend,
+} from 'recharts';
+import { ArrowBack, ArrowForward } from '@mui/icons-material';
+import { Box, IconButton, Link, Typography, Skeleton, Tooltip, useMediaQuery } from '@mui/material';
 import { MOBILE_BREAKPOINT } from '../../../globals';
 import { DepartmentEnrollmentHistory, EnrollmentHistory } from '$lib/enrollmentHistory';
 import { useThemeStore } from '$stores/SettingsStore';
 
-export interface EnrollmentHistoryPopupProps {
+type PopupHeaderCallback = () => void;
+
+interface PopupHeaderProps {
+    graphWidth: number;
+    graphIndex: number;
+    handleForward: PopupHeaderCallback;
+    handleBack: PopupHeaderCallback;
+    popupTitle: string;
+    enrollmentHistory: EnrollmentHistory[];
+}
+
+function PopupHeader({
+    graphWidth,
+    graphIndex,
+    handleForward,
+    handleBack,
+    popupTitle,
+    enrollmentHistory,
+}: PopupHeaderProps) {
+    const isMobileScreen = useMediaQuery(`(max-width: ${MOBILE_BREAKPOINT})`);
+
+    return (
+        <Box
+            sx={{
+                display: 'flex',
+                justifyContent: 'space-between',
+                alignItems: 'center',
+                width: graphWidth,
+            }}
+        >
+            <Tooltip title="Newer Graph">
+                {/* In order for a tooltip to work properly with disabled buttons, we need to wrap the button in a span */}
+                <span>
+                    <IconButton onClick={handleBack} disabled={graphIndex === 0}>
+                        <ArrowBack />
+                    </IconButton>
+                </span>
+            </Tooltip>
+            <Typography sx={{ fontWeight: 500, fontSize: isMobileScreen ? '0.8rem' : '1rem', textAlign: 'center' }}>
+                {popupTitle}
+            </Typography>
+            <Tooltip title="Older Graph">
+                <span>
+                    <IconButton onClick={handleForward} disabled={graphIndex === enrollmentHistory.length - 1}>
+                        <ArrowForward />
+                    </IconButton>
+                </span>
+            </Tooltip>
+        </Box>
+    );
+}
+
+interface EnrollmentHistoryPopupProps {
     department: string;
     courseNumber: string;
 }
 
 export function EnrollmentHistoryPopup({ department, courseNumber }: EnrollmentHistoryPopupProps) {
     const [loading, setLoading] = useState(true);
-    const [enrollmentHistory, setEnrollmentHistory] = useState<EnrollmentHistory>();
+    const [enrollmentHistory, setEnrollmentHistory] = useState<EnrollmentHistory[]>();
+    const [graphIndex, setGraphIndex] = useState(0);
+
     const isMobileScreen = useMediaQuery(`(max-width: ${MOBILE_BREAKPOINT})`);
 
     const deptEnrollmentHistory = useMemo(() => new DepartmentEnrollmentHistory(department), [department]);
@@ -24,15 +88,24 @@ export function EnrollmentHistoryPopup({ department, courseNumber }: EnrollmentH
             return 'No past enrollment data found for this course';
         }
 
-        return `${department} ${courseNumber} | ${enrollmentHistory.year} ${
-            enrollmentHistory.quarter
-        } | ${enrollmentHistory.instructors.join(', ')}`;
-    }, [courseNumber, department, enrollmentHistory]);
+        const currEnrollmentHistory = enrollmentHistory[graphIndex];
+        return `${department} ${courseNumber} | ${currEnrollmentHistory.year} ${
+            currEnrollmentHistory.quarter
+        } | ${currEnrollmentHistory.instructors.join(', ')}`;
+    }, [courseNumber, department, enrollmentHistory, graphIndex]);
     const isDark = useThemeStore((state) => state.isDark);
     const encodedDept = useMemo(() => encodeURIComponent(department), [department]);
     const axisColor = isDark ? '#fff' : '#111';
     const tooltipDateColor = '#111';
 
+    const handleBack = useCallback(() => {
+        setGraphIndex((prev) => prev - 1);
+    }, []);
+
+    const handleForward = useCallback(() => {
+        setGraphIndex((prev) => prev + 1);
+    }, []);
+
     useEffect(() => {
         if (!loading) {
             return;
@@ -41,6 +114,7 @@ export function EnrollmentHistoryPopup({ department, courseNumber }: EnrollmentH
         deptEnrollmentHistory.find(courseNumber).then((data) => {
             if (data) {
                 setEnrollmentHistory(data);
+                setGraphIndex(0);
             }
             setLoading(false);
         });
@@ -64,20 +138,18 @@ export function EnrollmentHistoryPopup({ department, courseNumber }: EnrollmentH
         );
     }
 
+    const lineChartData = enrollmentHistory[graphIndex].days;
+
     return (
         <Box sx={{ padding: 0.5 }}>
-            <Typography
-                sx={{
-                    marginTop: '.5rem',
-                    textAlign: 'center',
-                    fontWeight: 500,
-                    marginRight: '2rem',
-                    marginLeft: '2rem',
-                    marginBottom: '.5rem',
-                }}
-            >
-                {popupTitle}
-            </Typography>
+            <PopupHeader
+                graphWidth={graphWidth}
+                graphIndex={graphIndex}
+                handleForward={handleForward}
+                handleBack={handleBack}
+                popupTitle={popupTitle}
+                enrollmentHistory={enrollmentHistory}
+            />
             <Link
                 href={`https://zot-tracker.herokuapp.com/?dept=${encodedDept}&number=${courseNumber}&courseType=all`}
                 target="_blank"
@@ -85,11 +157,11 @@ export function EnrollmentHistoryPopup({ department, courseNumber }: EnrollmentH
                 sx={{ display: 'flex', height: graphHeight, width: graphWidth }}
             >
                 <ResponsiveContainer width="95%" height="95%">
-                    <LineChart data={enrollmentHistory.days} style={{ cursor: 'pointer' }}>
+                    <LineChart data={lineChartData} style={{ cursor: 'pointer' }}>
                         <CartesianGrid strokeDasharray="3 3" />
                         <XAxis dataKey="date" tick={{ fontSize: 12, fill: axisColor }} />
                         <YAxis tick={{ fontSize: 12, fill: axisColor }} width={40} />
-                        <Tooltip labelStyle={{ color: tooltipDateColor }} />
+                        <RechartsTooltip labelStyle={{ color: tooltipDateColor }} />
                         <Legend />
                         <Line type="monotone" dataKey="totalEnrolled" stroke="#8884d8" name="Enrolled" dot={{ r: 2 }} />
                         <Line type="monotone" dataKey="maxCapacity" stroke="#82ca9d" name="Max" dot={{ r: 2 }} />
diff --git a/apps/antalmanac/src/components/RightPane/SectionTable/GradesPopup.tsx b/apps/antalmanac/src/components/RightPane/SectionTable/GradesPopup.tsx
index 70515eee..223feac4 100644
--- a/apps/antalmanac/src/components/RightPane/SectionTable/GradesPopup.tsx
+++ b/apps/antalmanac/src/components/RightPane/SectionTable/GradesPopup.tsx
@@ -1,9 +1,8 @@
 import { useState, useEffect, useMemo } from 'react';
-import { Bar, BarChart, CartesianGrid, ResponsiveContainer, XAxis, YAxis } from 'recharts';
+import { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
 import { Box, Link, Typography, Skeleton } from '@mui/material';
 import { useThemeStore } from '$stores/SettingsStore';
 import GradesHelper, { type Grades } from '$lib/grades';
-
 export interface GradeData {
     grades: {
         name: string;
@@ -26,6 +25,11 @@ async function getGradeData(
         return undefined;
     }
 
+    const totalGrades = Object.values(Object.entries(courseGrades).filter(([key]) => key !== 'averageGPA')).reduce(
+        (acc, [_, value]) => acc + value,
+        0
+    );
+
     /**
      * Format data for displaying in chart.
      *
@@ -36,7 +40,7 @@ async function getGradeData(
         .map(([key, value]) => {
             return {
                 name: key.replace('grade', '').replace('Count', ''),
-                all: value,
+                all: Number(((value / totalGrades) * 100).toFixed(2)),
             };
         });
 
@@ -134,7 +138,18 @@ function GradesPopup(props: GradesPopupProps) {
                     <BarChart data={gradeData.grades} style={{ cursor: 'pointer' }}>
                         <CartesianGrid strokeDasharray="3 3" />
                         <XAxis dataKey="name" tick={{ fontSize: 12, fill: axisColor }} />
-                        <YAxis tick={{ fontSize: 12, fill: axisColor }} width={40} />
+                        <YAxis tick={{ fontSize: 12, fill: axisColor }} width={40} unit="%" />
+                        <Tooltip
+                            content={({ active, payload, label }) => (
+                                <GradeTooltip
+                                    active={active ?? false}
+                                    payload={(payload as Array<Payload>) ?? null}
+                                    label={label ?? ''}
+                                />
+                            )}
+                            position={{ y: 100 }}
+                            offset={-5}
+                        />
                         <Bar dataKey="all" fill="#5182ed" />
                     </BarChart>
                 </ResponsiveContainer>
@@ -143,4 +158,40 @@ function GradesPopup(props: GradesPopupProps) {
     );
 }
 
+const GradeTooltip = (props: GradeTooltipProps) => {
+    const { active, payload, label } = props;
+    if (active && payload && payload.length) {
+        return (
+            <>
+                <Box
+                    sx={{
+                        backgroundColor: '#5182ed',
+                        padding: '5px',
+                        border: '1px solid #000',
+                        borderRadius: '5px',
+                        boxShadow: '0 0 5px 0 rgba(0, 0, 0, 0.5)',
+                    }}
+                >
+                    <Typography variant="body1" align="center" sx={{ color: '#fff', fontWeight: 500 }}>
+                        {`${label}: ${payload[0].value}%`}
+                    </Typography>
+                </Box>
+            </>
+        );
+    }
+
+    return null;
+};
+
+export interface GradeTooltipProps {
+    active: boolean;
+    payload: Array<Payload> | null;
+    label: string;
+}
+
+export interface Payload {
+    name: string;
+    value: number;
+}
+
 export default GradesPopup;
diff --git a/apps/antalmanac/src/lib/buildingCatalogue.ts b/apps/antalmanac/src/lib/buildingCatalogue.ts
index fc8a8411..e93fe32f 100644
--- a/apps/antalmanac/src/lib/buildingCatalogue.ts
+++ b/apps/antalmanac/src/lib/buildingCatalogue.ts
@@ -168,60 +168,24 @@ export default {
         lng: -117.849297,
         imageURLs: ['463/vista-field.jpg'],
     },
-    83057: {
-        name: 'Biological Sciences III (BS3)',
-        lat: 33.645363,
-        lng: -117.846222,
-        imageURLs: ['463/biological-sciences-iii.jpg'],
-    },
     83058: {
         name: 'Bison Modular (BIMO)',
         lat: 33.643814,
         lng: -117.84639,
         imageURLs: ['463/bison-modular.jpg'],
     },
-    83059: {
-        name: 'Bonney Research Laboratory (BRL)',
-        lat: 33.646461,
-        lng: -117.846169,
-        imageURLs: ['463/bonney-research-laboratory.jpg'],
-    },
-    83060: {
-        name: 'Center for the Neurobiology of Learning & Memory Annex (CNLM)',
-        lat: 33.646629,
-        lng: -117.846428,
-        imageURLs: ['463/center-for-the-neurobiology-of-learning-and-memory-annex.jpg'],
-    },
     83061: {
         name: 'Greenhouse',
         lat: 33.64732,
         lng: -117.845367,
         imageURLs: ['463/greenhouse.jpg'],
     },
-    83062: {
-        name: 'McGaugh Hall (MH)',
-        lat: 33.645271,
-        lng: -117.844887,
-        imageURLs: ['463/mcgaugh-hall.jpg'],
-    },
-    83063: {
-        name: 'Qureshey Research Laboratory (QRL)',
-        lat: 33.646648,
-        lng: -117.845764,
-        imageURLs: ['463/qureshey-research-laboratory.jpg'],
-    },
     83064: {
         name: 'Howard Schneiderman Lecture Hall (HSLH)',
         lat: 33.645618,
         lng: -117.844666,
         imageURLs: ['463/howard-schneiderman-lecture-hall.jpg'],
     },
-    83065: {
-        name: 'Steinhaus Hall (SH)',
-        lat: 33.646347,
-        lng: -117.844887,
-        imageURLs: ['463/steinhaus-hall.jpg'],
-    },
     83068: {
         name: 'Arroyo Vista',
         lat: 33.646317,
@@ -241,7 +205,7 @@ export default {
         imageURLs: [],
     },
     83072: {
-        name: 'Mesa Court ',
+        name: 'Mesa Court',
         lat: 33.651924,
         lng: -117.844528,
         imageURLs: [],
@@ -378,12 +342,6 @@ export default {
         lng: -117.840149,
         imageURLs: ['463/engineering-and-comuting-trailer.jpg'],
     },
-    83102: {
-        name: 'Engineering Gateway (EG)',
-        lat: 33.643215,
-        lng: -117.840157,
-        imageURLs: ['463/engineering-gateway.jpg'],
-    },
     83103: {
         name: 'Engineering Hall (EH)',
         lat: 33.643799,
@@ -618,12 +576,6 @@ export default {
         lng: -117.835938,
         imageURLs: ['463/law.jpg'],
     },
-    83167: {
-        name: 'Multipurpose Academic & Administrative Building (MPAA)',
-        lat: 33.647156,
-        lng: -117.837013,
-        imageURLs: ['463/multipurpose-academic-and-admin-building.jpg'],
-    },
     83168: {
         name: 'Science Library (SLIB)',
         lat: 33.645908,
@@ -648,12 +600,6 @@ export default {
         lng: -117.844681,
         imageURLs: ['463/croul-hall.jpg'],
     },
-    83372: {
-        name: 'Multipurpose Science & Technology Building (MSTB)',
-        lat: 33.64209,
-        lng: -117.844467,
-        imageURLs: ['463/multipurpose-science-and-technology.jpg'],
-    },
     83373: {
         name: 'Natural Sciences I (NS1)',
         lat: 33.644348,
@@ -726,12 +672,6 @@ export default {
         lng: -117.835938,
         imageURLs: ['463/continuing-education-6.jpg'],
     },
-    83544: {
-        name: 'Social Ecology I (SE)',
-        lat: 33.646194,
-        lng: -117.838844,
-        imageURLs: ['463/social-ecology-i.jpg'],
-    },
     83545: {
         name: 'Social Ecology II (SE2)',
         lat: 33.64666,
@@ -756,12 +696,6 @@ export default {
         lng: -117.847198,
         imageURLs: ['463/kuci-radio-station.jpg'],
     },
-    83559: {
-        name: 'Student Center (SC)',
-        lat: 33.649323,
-        lng: -117.842255,
-        imageURLs: ['463/student-center_1.jpg'],
-    },
     83560: {
         name: 'Student Health Center (SHC)',
         lat: 33.64555,
@@ -774,12 +708,6 @@ export default {
         lng: -117.842316,
         imageURLs: ['463/student-services-i.jpg'],
     },
-    83565: {
-        name: 'Social & Behavioral Sciences Gateway (SBSG)',
-        lat: 33.647488,
-        lng: -117.839165,
-        imageURLs: ['463/social-and-behavioral-sciences-gateway.jpg'],
-    },
     83566: {
         name: 'Social Science Hall (SSH)',
         lat: 33.646236,
@@ -810,12 +738,6 @@ export default {
         lng: -117.839096,
         imageURLs: ['463/social-science-plaza-b.jpg'],
     },
-    83571: {
-        name: 'Social Science Tower (SST)',
-        lat: 33.646591,
-        lng: -117.840294,
-        imageURLs: ['463/social-science-tower.jpg'],
-    },
     83572: {
         name: 'Social Science Trailer (SSTR)',
         lat: 33.647007,
@@ -888,12 +810,6 @@ export default {
         lng: -117.838409,
         imageURLs: ['463/university-tower-2.jpg'],
     },
-    99610: {
-        name: 'Interim Classroom Facility (ICF)',
-        lat: 33.644463,
-        lng: -117.840012,
-        imageURLs: ['463/interim-classroom-facility.jpg'],
-    },
     101047: {
         name: 'University Hills Community Center',
         lat: 33.636307,
@@ -924,18 +840,6 @@ export default {
         lng: -117.838112,
         imageURLs: ['463/business-ii.jpg'],
     },
-    114585: {
-        name: '100 Theory',
-        lat: 33.644245,
-        lng: -117.855675,
-        imageURLs: [],
-    },
-    114586: {
-        name: 'Anteater Instruction & Research Building (AIRB)',
-        lat: 33.643009,
-        lng: -117.838013,
-        imageURLs: ['463/anteater-instruction-and-research-building.jpg'],
-    },
     114588: {
         name: 'Beckman Center of the National Academies of Sciences & Engineering',
         lat: 33.649414,
@@ -972,12 +876,6 @@ export default {
         lng: -117.846428,
         imageURLs: ['463/center-for-the-neurobiology-of-learning-and-memory-annex.jpg'],
     },
-    114597: {
-        name: 'Greenhouse',
-        lat: 33.64732,
-        lng: -117.845367,
-        imageURLs: ['463/greenhouse.jpg'],
-    },
     114598: {
         name: 'Merage School of Business (SB1)',
         lat: 33.647072,
@@ -996,54 +894,24 @@ export default {
         lng: -117.840157,
         imageURLs: ['463/engineering-gateway.jpg'],
     },
-    114601: {
-        name: 'Engineering Laboratory Facility (ELF)',
-        lat: 33.643822,
-        lng: -117.839622,
-        imageURLs: ['463/engineering-laboratory-facility.jpg'],
-    },
     114602: {
         name: 'Little Theatre',
         lat: 33.647213,
         lng: -117.844063,
         imageURLs: ['463/little-theatre.jpg'],
     },
-    114603: {
-        name: 'Humanities Interim Classroom Facility (HICF) | Studio Art Trailer',
-        lat: 33.646919,
-        lng: -117.84687,
-        imageURLs: ['463/humanities-interim-classroom-facility.jpg'],
-    },
     114604: {
         name: 'Multipurpose Academic & Administrative Building (MPAA)',
         lat: 33.647156,
         lng: -117.837013,
         imageURLs: ['463/multipurpose-academic-and-admin-building.jpg'],
     },
-    114605: {
-        name: 'Beckman Laser Institute (BLI)',
-        lat: 33.644192,
-        lng: -117.849915,
-        imageURLs: ['463/beckman-laser-institute.jpg'],
-    },
-    114606: {
-        name: 'Gillespie Neuroscience Research Facility (GNRF)',
-        lat: 33.644009,
-        lng: -117.851265,
-        imageURLs: ['463/gillespie-neuroscience-research-facility.jpg'],
-    },
     114607: {
         name: 'Sue & Bill Gross Hall: A CIRM Institute',
         lat: 33.643555,
         lng: -117.851501,
         imageURLs: ['463/gross-hall.jpg'],
     },
-    114608: {
-        name: 'Hewitt Research Hall (HRH)',
-        lat: 33.643505,
-        lng: -117.851921,
-        imageURLs: ['463/hewitt-hall.jpg'],
-    },
     114609: {
         name: 'Plumwood House (PH)',
         lat: 33.644718,
@@ -1062,54 +930,24 @@ export default {
         lng: -117.844887,
         imageURLs: ['463/steinhaus-hall.jpg'],
     },
-    114612: {
-        name: 'Bison Modular (BIMO)',
-        lat: 33.643814,
-        lng: -117.84639,
-        imageURLs: ['463/bison-modular.jpg'],
-    },
     114613: {
         name: 'Multipurpose Science & Technology Building (MSTB)',
         lat: 33.64209,
         lng: -117.844467,
         imageURLs: ['463/multipurpose-science-and-technology.jpg'],
     },
-    114614: {
-        name: 'Physical Sciences High Bay',
-        lat: 33.64362,
-        lng: -117.845291,
-        imageURLs: ['463/physical-sciences-high-bay.jpg'],
-    },
-    114615: {
-        name: 'Rowland Hall (RH)',
-        lat: 33.644531,
-        lng: -117.844162,
-        imageURLs: ['463/rowland-hall.jpg'],
-    },
     114616: {
         name: 'Anteater Instruction & Research Building (AIRB)',
         lat: 33.643009,
         lng: -117.838013,
         imageURLs: ['463/anteater-instruction-and-research-building.jpg'],
     },
-    114618: {
-        name: 'Sprague Hall (SPH)',
-        lat: 33.644062,
-        lng: -117.852066,
-        imageURLs: ['463/sprague-hall.jpg'],
-    },
     114621: {
         name: 'Facilities Management Building',
         lat: 33.664715,
         lng: -117.854805,
         imageURLs: [],
     },
-    114622: {
-        name: 'Humanities Gateway (HG)',
-        lat: 33.648266,
-        lng: -117.844406,
-        imageURLs: ['463/humanities-gateway.jpg'],
-    },
     114623: {
         name: 'Social Ecology I (SE)',
         lat: 33.646194,
@@ -1170,12 +1008,6 @@ export default {
         lng: -117.840294,
         imageURLs: ['463/social-science-tower.jpg'],
     },
-    114879: {
-        name: 'Gavin Herbert Eye Institute',
-        lat: 33.641743,
-        lng: -117.852249,
-        imageURLs: ['463/gavin-herbert-eye-institute.jpg'],
-    },
     114880: {
         name: 'Child Development School (CD)',
         lat: 33.662647,
@@ -1620,12 +1452,6 @@ export default {
         lng: -117.850998,
         imageURLs: ['463/gottschalk-pod.jpg'],
     },
-    255880: {
-        name: 'Student Center',
-        lat: 33.648888,
-        lng: -117.8424,
-        imageURLs: ['463/student-center_2.jpg'],
-    },
     255884: {
         name: 'Anteater Learning Pavilion (ALP)',
         lat: 33.647213,
@@ -1698,24 +1524,12 @@ export default {
         lng: -117.849152,
         imageURLs: [],
     },
-    364043: {
-        name: 'Student Services West',
-        lat: 33.646072,
-        lng: -117.849152,
-        imageURLs: [],
-    },
     407361: {
         name: 'ANTrepreneur Center',
         lat: 33.64621,
         lng: -117.837051,
         imageURLs: ['463/banning-house.jpg'],
     },
-    407362: {
-        name: 'Gateway Study Center (GC)',
-        lat: 33.647388,
-        lng: -117.841873,
-        imageURLs: ['463/gateway-study-center.jpg'],
-    },
     444368: {
         name: 'Plaza Verde',
         lat: 33.648224,
@@ -1752,14 +1566,8 @@ export default {
         lng: -117.848709,
         imageURLs: ['463/grounds-maintenance-facility.jpg'],
     },
-    444506: {
-        name: 'Facilities Management Building',
-        lat: 33.66478,
-        lng: -117.854935,
-        imageURLs: [],
-    },
     444507: {
-        name: 'Passport Services ',
+        name: 'Passport Services',
         lat: 33.664795,
         lng: -117.854523,
         imageURLs: [],
@@ -1812,42 +1620,12 @@ export default {
         lng: -117.844414,
         imageURLs: [],
     },
-    454486: {
-        name: 'Medical Surge I (MS1)',
-        lat: 33.646484,
-        lng: -117.850174,
-        imageURLs: ['463/medical-surge-i.jpg'],
-    },
-    454487: {
-        name: 'Donald Bren Hall (DBH)',
-        lat: 33.643288,
-        lng: -117.841965,
-        imageURLs: ['463/donald-bren-hall.jpg'],
-    },
-    454488: {
-        name: 'Engineering Hall (EH)',
-        lat: 33.643799,
-        lng: -117.841202,
-        imageURLs: ['463/engineering-hall.jpg'],
-    },
-    454489: {
-        name: 'Air Pollution Health Effects Laboratory (APHEL)',
-        lat: 33.664837,
-        lng: -117.853844,
-        imageURLs: [],
-    },
     454490: {
         name: 'Student Center South',
         lat: 33.648724,
         lng: -117.842705,
         imageURLs: [],
     },
-    454491: {
-        name: 'Student Center South',
-        lat: 33.648724,
-        lng: -117.842705,
-        imageURLs: [],
-    },
     454492: {
         name: 'Jao Family Sculpture Garden',
         lat: 33.647762,
@@ -1962,4 +1740,52 @@ export default {
         lng: -117.843788,
         imageURLs: ['463/1R7A7978.jpg'],
     },
+    363937: {
+        name: 'Mesa Parking Structure (MPS)',
+        lat: 33.6506,
+        lng: -117.84629,
+        imageURLs: [],
+    },
+    114576: {
+        name: 'Air Pollution Labs (APL)',
+        lat: 33.6648,
+        lng: -117.85382,
+        imageURLs: [],
+    },
+    363936: {
+        name: 'Student Center Parking Structure (SCPS)',
+        lat: 33.64971,
+        lng: -117.84189,
+        imageURLs: [],
+    },
+    363935: {
+        name: 'East Campus Parking Structure (ECPS)',
+        lat: 33.6451,
+        lng: -117.8282,
+        imageURLs: [],
+    },
+    363934: {
+        name: 'Social Science Parking Structure (SSPS)',
+        lat: 33.64768,
+        lng: -117.83801,
+        imageURLs: [],
+    },
+    363932: {
+        name: 'Anteater Parking Structure (APS)',
+        lat: 33.64314,
+        lng: -117.83783,
+        imageURLs: [],
+    },
+    367615: {
+        name: "Dean's Office",
+        lat: 33.64857,
+        lng: -117.84265,
+        imageURLs: [],
+    },
+    83062: {
+        name: 'McGaugh Hall (MH)',
+        lat: 33.64517,
+        lng: -117.84476,
+        imageURLs: [],
+    },
 } as Record<number, Building>;
diff --git a/apps/antalmanac/src/lib/enrollmentHistory.ts b/apps/antalmanac/src/lib/enrollmentHistory.ts
index bea9c847..ad14ba5a 100644
--- a/apps/antalmanac/src/lib/enrollmentHistory.ts
+++ b/apps/antalmanac/src/lib/enrollmentHistory.ts
@@ -44,7 +44,7 @@ export interface EnrollmentHistoryDay {
 
 export class DepartmentEnrollmentHistory {
     // Each key in the cache will be the department and courseNumber concatenated
-    static enrollmentHistoryCache: Record<string, EnrollmentHistory | null> = {};
+    static enrollmentHistoryCache: Record<string, EnrollmentHistory[] | null> = {};
     static termShortNames: string[] = termData.map((term) => term.shortName);
     static QUERY_TEMPLATE = `{
         enrollmentHistory(department: "$$DEPARTMENT$$", courseNumber: "$$COURSE_NUMBER$$", sectionType: Lec) {
@@ -68,30 +68,25 @@ export class DepartmentEnrollmentHistory {
         this.partialQueryString = DepartmentEnrollmentHistory.QUERY_TEMPLATE.replace('$$DEPARTMENT$$', department);
     }
 
-    async find(courseNumber: string): Promise<EnrollmentHistory | null> {
+    async find(courseNumber: string): Promise<EnrollmentHistory[] | null> {
         const cacheKey = this.department + courseNumber;
-        return (DepartmentEnrollmentHistory.enrollmentHistoryCache[cacheKey] ??= await this.queryEnrollmentHistory(
-            courseNumber
-        ));
+        return (DepartmentEnrollmentHistory.enrollmentHistoryCache[cacheKey] ??=
+            await this.queryEnrollmentHistory(courseNumber));
     }
 
-    async queryEnrollmentHistory(courseNumber: string): Promise<EnrollmentHistory | null> {
+    async queryEnrollmentHistory(courseNumber: string): Promise<EnrollmentHistory[] | null> {
         // Query for the enrollment history of all lecture sections that were offered
         const queryString = this.partialQueryString.replace('$$COURSE_NUMBER$$', courseNumber);
 
         const res = (await queryGraphQL<EnrollmentHistoryGraphQLResponse>(queryString))?.data?.enrollmentHistory;
 
-        if (res?.length) {
-            const parsedEnrollmentHistory = DepartmentEnrollmentHistory.parseEnrollmentHistoryResponse(res);
-            DepartmentEnrollmentHistory.sortEnrollmentHistory(parsedEnrollmentHistory);
-
-            // For now, just return the enrollment history of the most recent quarter
-            // instead of the entire array of enrollment histories
-            const latestEnrollmentHistory = parsedEnrollmentHistory[0];
-            return latestEnrollmentHistory;
+        if (!res?.length) {
+            return null;
         }
 
-        return null;
+        const parsedEnrollmentHistory = DepartmentEnrollmentHistory.parseEnrollmentHistoryResponse(res);
+        DepartmentEnrollmentHistory.sortEnrollmentHistory(parsedEnrollmentHistory);
+        return parsedEnrollmentHistory;
     }
 
     /**
diff --git a/apps/antalmanac/src/lib/location_ids.ts b/apps/antalmanac/src/lib/location_ids.ts
index 56e452a9..2fce39f6 100644
--- a/apps/antalmanac/src/lib/location_ids.ts
+++ b/apps/antalmanac/src/lib/location_ids.ts
@@ -10,7 +10,7 @@ const locationIds: Record<string, number> = {
     ICS: 83096,
     BRL: 114594,
     BLI: 83134,
-    'Mesa Court': 367611,
+    'Mesa Court': 83072,
     DBH: 83095,
     MS2: 83151,
     SC: 114624,
@@ -34,12 +34,12 @@ const locationIds: Record<string, number> = {
     MS1: 83150,
     LAW: 83166,
     ELF: 83104,
-    HT: 367490,
+    // HT: 367490,
     AIRB: 114616,
     CRCC: 83551,
     ET: 83106,
     EDUC: 114599,
-    CP: 114580,
+    CP: 444502,
     SB1: 114598,
     MAB: 83033,
     CRH: 83371,
@@ -50,12 +50,11 @@ const locationIds: Record<string, number> = {
     AITR: 83024,
     CDDC: 115051,
     MH: 83062,
-    'Student Center': 367612,
     PSLH: 83378,
     SCPS: 363936,
     ELH: 83105,
     SS2: 255997,
-    'Hitachi, PH': 114609,
+    PH: 114609,
     PSCB: 83376,
     HG: 83160,
     HSLH: 83064,
@@ -64,9 +63,9 @@ const locationIds: Record<string, number> = {
     CTT: 83026,
     CAC: 83027,
     ACT: 83022,
-    'Middle Earth': 367613,
+    'Middle Earth': 83073,
     SHC: 83560,
-    CNLMA: 114596,
+    CNLM: 114596,
     IH: 83140,
     FRF: 114575,
     Calit2: 114579,
@@ -90,7 +89,7 @@ const locationIds: Record<string, number> = {
     SE: 114623,
     'MS-C': 83146,
     SH: 114611,
-    'UCI Health': 367616,
+    //'UCI Health': 367616,
     SBSG: 114858,
     GNRF: 83136,
     SSPB: 83570,
@@ -103,7 +102,7 @@ const locationIds: Record<string, number> = {
     ICF: 83099,
     ALP: 255884,
     MSTB: 114613,
-    AH: 83619,
+    //AH: 83619,
     CH: 83050,
     ALH: 83618,
     SSH: 83566,
@@ -111,6 +110,195 @@ const locationIds: Record<string, number> = {
     'MS-E': 83149,
     APS: 363932,
     HH: 83161,
+    'Choral Studio': 83025,
+    'Production Studio': 83036,
+    DS: 83037,
+    'Anteater Ballpark': 83043,
+    'Anteater Stadium': 83045,
+    'Bren Events Center': 83046,
+    'Crawford Clubhouse': 83048,
+    'Crawford Field': 83049,
+    'Crawford Pool': 83051,
+    'Anteater Tennis Stadium': 83054,
+    'Track & Field House': 83055,
+    'Vista Field': 83056,
+    BIMO: 83058,
+    Greenhouse: 83061,
+    'Arroyo Vista': 83068,
+    'Camino del Sol': 83069,
+    'Campus Village': 83070,
+    'Palo Verde': 83074,
+    'Puerta del Sol': 83075,
+    'University Hills': 83076,
+    'Verano Place': 83077,
+    'Vista del Campo': 83078,
+    'Vista del Campo Norte': 83079,
+    'CAC G021 Arts Instructional Lab Classroom': 83081,
+    'OIT@CCC Drop-In Lab': 83083,
+    'Gateway Study Center Drop-In Lab': 83084,
+    'OIT@SST Drop-In Lab': 83086,
+    'OIT@Student Center Drop-In Lab': 83087,
+    'Humanities Studio Labs': 83090,
+    'Langson Library Labs': 83091,
+    'MSTB 210 Instructional Lab Classroom': 83092,
+    'NS1 2144 Instructional Lab Classroom': 83093,
+    'Science Library Labs': 83094,
+    "BC's Cavern": 83120,
+    'POD Social Sciences': 83122,
+    'Cafe Espresso': 83123,
+    'The Green Room': 83125,
+    'Phoenix Food Court': 83128,
+    'Starbucks @ Biological Sciences': 83130,
+    'Gottschalk Medical Plaza': 83137,
+    'Medical Education Building': 83141,
+    'Medical Sciences A Administration': 83143,
+    'Medical Sciences A Annex': 83144,
+    'Medical Sciences Classroom Facility': 83147,
+    'MS-D': 83148,
+    'MRI 3T Trailer': 83152,
+    'MRI 4T Trailer': 83153,
+    'Tamkin Student Lecture Building': 83156,
+    'Physical Sciences High Bay': 83377,
+    'Continuing Education 4': 83384,
+    'Continuing Education 3': 83385,
+    'Continuing Education 5': 83386,
+    'Continuing Education 6': 83387,
+    'Disability Services Center': 83552,
+    'KUCI Radio Station': 83558,
+    '100 Theory': 83609,
+    '111 Theory': 83611,
+    '5251 California': 83612,
+    '101 Academy': 83615,
+    'Administrative Modular': 83617,
+    'Faculty and Staff Housing Office': 83622,
+    'Newkirk Alumni Center': 83624,
+    'University Hills Community Center': 101047,
+    'Beckman Center of the National Academies of Sciences & Engineering': 114588,
+    'Farm School/Red Barn': 114590,
+    'Little Theatre': 114602,
+    'Gavin Herbert Eye Institute': 114610,
+    'Facilities Management Building': 114621,
+    'Student Health Center II': 114626,
+    'Court Building': 114628,
+    'Field Building': 114629,
+    'Irvine Barclay Theatre': 114659,
+    'Early Childhood Education Center': 114881,
+    "Children's Center": 114882,
+    'Extended Day Center': 114883,
+    'Infant Toddler Center I': 114884,
+    'Infant Toddler Center II': 114885,
+    'Verano Preschool': 114887,
+    'University Montessori School': 114888,
+    'American Heart Association': 114890,
+    'Yurt Dance Studio': 114891,
+    '20/20 Cafe': 114958,
+    'Au Bon Pain': 114959,
+    'Espress Yourself': 114960,
+    'Starbucks @ Paul Merage School of Business': 114961,
+    'Java City Kiosk': 114962,
+    'Med Ed Cafe': 114963,
+    'Neuropsychiatric Center': 115040,
+    'Douglas Hospital': 115041,
+    'University Hospital Tower': 115042,
+    'Manchester Pavilion': 115043,
+    'Faculty / Administration Offices': 115044,
+    'Pavilion 3': 115045,
+    'Chao Family Comprehensive Cancer Center': 115046,
+    'Imaging Services': 115047,
+    'Burn & Wound Therapy': 115048,
+    'Pavilion 4': 115049,
+    'Grunigen Medical Library': 115052,
+    'Laboratory Medicine Building': 115053,
+    'Parking & Security': 115054,
+    'Pavilion 1': 115055,
+    'Nikken Center': 115056,
+    'Pavilion 2': 115057,
+    Orthopaedics: 115058,
+    'Visitor Parking': 115059,
+    'Edward Shanbrom, MD Hall': 115060,
+    'Central Plant': 115061,
+    'Facilities Services': 115062,
+    'Administrative / Academic Offices': 115063,
+    'Home Dialysis': 115064,
+    'Courtyard Study Lounge Drop-In Lab': 123779,
+    'Watson Bridge': 126143,
+    'Aldrich Flag Poles': 126144,
+    'Ring Mall': 126145,
+    'Aldrich Park': 126146,
+    'Arts Bridge': 126147,
+    'Physical Sciences Plaza': 126148,
+    'Biological Sciences Plaza': 126149,
+    'Engineering Plaza': 126150,
+    'Social Sciences Plaza': 126151,
+    'Student Center Plaza': 126152,
+    'Humanities Plaza': 126153,
+    'Bostick Bridge': 126154,
+    'Athletics Bridge': 126155,
+    'Alumni Grove': 126156,
+    'Anteater Statue': 126157,
+    'Laurel L. Wilkening Rose Garden': 126158,
+    'Cesario Memorial': 126159,
+    'Bren Center Flag Poles': 126999,
+    'Claire Trevor Oscar/Emmy Display': 127000,
+    'Mesa Parking Bridge': 127001,
+    'Student Center Courtyard Stage': 127052,
+    'Palo Verde Bridge': 127246,
+    '120 Theory': 171183,
+    'Student Center Bridge': 186008,
+    'Continuing Education Bridge': 186011,
+    'Bridge Cafe': 186012,
+    'The Anteatery': 186013,
+    'Summer Session A': 197840,
+    'Summer Session B': 197841,
+    '131 Innovation': 255739,
+    '141 Innovation': 255740,
+    'Gottschalk P.O.D. Express': 255878,
+    'Anteater Community Resource Center': 255992,
+    'Engineering Computing Labs': 257153,
+    'Grunigen Medical Library Lab': 257154,
+    'MSTB 226 Instructional Lab Classroom': 257155,
+    'NS1 3112 Instructional Lab Classroom': 257156,
+    Taza: 264222,
+    'ALP 2800 Computer Focused Classroom': 363319,
+    'ALP 3600 Computer Focused Classroom': 363320,
+    'ALP 3610 Computer Focused Classroom': 363321,
+    'Student Services West': 364042,
+    'ANTrepreneur Center': 407361,
+    'Plaza Verde': 444368,
+    'Building Services Building': 444501,
+    'Electrical Substation': 444503,
+    'Facilities Management Shops': 444504,
+    'Grounds Maintenance Facility': 444505,
+    'Passport Services': 444507,
+    Receiving: 444508,
+    'Recycling Center': 444509,
+    'Thermal Energy Storage Tank': 444510,
+    APHEL: 445384,
+    Brandywine: 446803,
+    'Faculty Research Facility': 447392,
+    '5270 California': 451260,
+    UAG: 454485,
+    'Student Center South': 454490,
+    'Jao Family Sculpture Garden': 454492,
+    'Science Library Mesquite Garden': 454493,
+    'Anteater Plaza': 454494,
+    'Founders Court': 454495,
+    'Campus Village Community Center': 464544,
+    'Campus Village Housing Office': 464545,
+    'Mesa Court Community Center': 464546,
+    'Mesa Court Housing Office': 464547,
+    'Arroyo Vista Community Center & Housing Office': 464548,
+    'Middle Earth Housing Office': 464549,
+    'Palo Verde Housing Office': 464550,
+    'Palo Verde Community Center': 464551,
+    'Verano Place Housing Office': 464552,
+    'Infinity Fountain': 471175,
+    'Stacey Nicholas Tech Pavilion': 471176,
+    'Willed Body Program Dedication Monument': 471177,
+    'Schonfeld Fountain': 471178,
+    'Maya Lin Water Table': 471180,
+    'Gloria Gellman Sculpture': 471202,
+    'Sue & Bill Gross Hall: A CIRM Institute': 114607,
 };
 
 export default locationIds;
diff --git a/apps/antalmanac/src/stores/AppStore.ts b/apps/antalmanac/src/stores/AppStore.ts
index e6bae484..dd645188 100644
--- a/apps/antalmanac/src/stores/AppStore.ts
+++ b/apps/antalmanac/src/stores/AppStore.ts
@@ -90,6 +90,10 @@ class AppStore extends EventEmitter {
         return this.schedule.getCalendarizedCourseEvents();
     }
 
+    getCustomEventsInCalendar() {
+        return this.schedule.getCalendarizedCustomEvents();
+    }
+
     getFinalEventsInCalendar() {
         return this.schedule.getCalendarizedFinals();
     }
diff --git a/apps/antalmanac/src/stores/Schedules.ts b/apps/antalmanac/src/stores/Schedules.ts
index 1179bb16..93566ebc 100644
--- a/apps/antalmanac/src/stores/Schedules.ts
+++ b/apps/antalmanac/src/stores/Schedules.ts
@@ -402,6 +402,13 @@ export class Schedules {
         return calendarizeCourseEvents(this.getCurrentCourses());
     }
 
+    /**
+     * Convert just custom events into calendar compatible format.
+     */
+    getCalendarizedCustomEvents() {
+        return calendarizeCustomEvents(this.getCurrentCustomEvents());
+    }
+
     /**
      * Convert finals into calendar friendly format
      */


Test patch:
diff --git a/apps/antalmanac/tests/locations.test.ts b/apps/antalmanac/tests/locations.test.ts
new file mode 100644
index 00000000..7afb6885
--- /dev/null
+++ b/apps/antalmanac/tests/locations.test.ts
@@ -0,0 +1,45 @@
+import { describe, test, expect } from 'vitest';
+import locationIds from '$lib/location_ids';
+import buildingCatalogue from '$lib/buildingCatalogue';
+
+describe('locationIds', () => {
+    test('no duplicate names', () => {
+        const names = Object.keys(locationIds);
+        const uniqueNames = new Set(names);
+        expect(names.length).toBe(uniqueNames.size);
+    });
+});
+
+describe('buildingCatalogue', () => {
+    test('no duplicate names', () => {
+        const names = Object.values(buildingCatalogue).map((building) => building.name);
+        const uniqueNames = new Set(names);
+        expect(names.length).toBe(uniqueNames.size);
+    });
+});
+
+describe('locationIds and buildingCatalogue', () => {
+    test('all locationIds are in buildingCatalogue', () => {
+        const locationNames = Object.keys(locationIds);
+        const buildingNames = Object.values(buildingCatalogue).map((building) => {
+            const buildingName = building.name.includes('(')
+                ? building.name.substring(building.name.indexOf('(') + 1, building.name.indexOf(')'))
+                : building.name;
+            return buildingName;
+        });
+        for (const locationName of locationNames) {
+            expect(buildingNames).toContain(locationName);
+        }
+    });
+
+    test('all buildingCatalogue are in locationIds', () => {
+        const locationNames = Object.keys(locationIds);
+        const buildingNames = Object.values(buildingCatalogue).map((building) => building.name);
+        for (const buildingName of buildingNames) {
+            const name = buildingName.includes('(')
+                ? buildingName.substring(buildingName.indexOf('(') + 1, buildingName.indexOf(')'))
+                : buildingName;
+            expect(locationNames).toContain(name);
+        }
+    });
+});