feat: calendar header formatting and data displaying (#160)
This commit is contained in:
@@ -16,6 +16,7 @@ export default async function addCourse(scheduleName: string, course: Course): P
|
|||||||
}
|
}
|
||||||
|
|
||||||
activeSchedule.courses.push(course);
|
activeSchedule.courses.push(course);
|
||||||
|
activeSchedule.updatedAt = Date.now();
|
||||||
|
|
||||||
await UserScheduleStore.set('schedules', schedules);
|
await UserScheduleStore.set('schedules', schedules);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,5 +12,7 @@ export default async function clearCourses(scheduleName: string): Promise<void>
|
|||||||
throw new Error(`Schedule ${scheduleName} does not exist`);
|
throw new Error(`Schedule ${scheduleName} does not exist`);
|
||||||
}
|
}
|
||||||
schedule.courses = [];
|
schedule.courses = [];
|
||||||
|
schedule.updatedAt = Date.now();
|
||||||
|
|
||||||
await UserScheduleStore.set('schedules', schedules);
|
await UserScheduleStore.set('schedules', schedules);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export default async function createSchedule(scheduleName: string): Promise<stri
|
|||||||
name: scheduleName,
|
name: scheduleName,
|
||||||
courses: [],
|
courses: [],
|
||||||
hours: 0,
|
hours: 0,
|
||||||
|
updatedAt: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
await UserScheduleStore.set('schedules', schedules);
|
await UserScheduleStore.set('schedules', schedules);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export default async function removeCourse(scheduleName: string, course: Course)
|
|||||||
}
|
}
|
||||||
|
|
||||||
activeSchedule.courses = activeSchedule.courses.filter(c => c.uniqueId !== course.uniqueId);
|
activeSchedule.courses = activeSchedule.courses.filter(c => c.uniqueId !== course.uniqueId);
|
||||||
|
activeSchedule.updatedAt = Date.now();
|
||||||
|
|
||||||
await UserScheduleStore.set('schedules', schedules);
|
await UserScheduleStore.set('schedules', schedules);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export default async function renameSchedule(scheduleName: string, newName: stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
schedules[scheduleIndex].name = newName;
|
schedules[scheduleIndex].name = newName;
|
||||||
|
schedules[scheduleIndex].updatedAt = Date.now();
|
||||||
|
|
||||||
await UserScheduleStore.set('schedules', schedules);
|
await UserScheduleStore.set('schedules', schedules);
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export default async function switchSchedule(scheduleName: string): Promise<void
|
|||||||
if (scheduleIndex === -1) {
|
if (scheduleIndex === -1) {
|
||||||
throw new Error(`Schedule ${scheduleName} does not exist`);
|
throw new Error(`Schedule ${scheduleName} does not exist`);
|
||||||
}
|
}
|
||||||
|
schedules[scheduleIndex].updatedAt = Date.now();
|
||||||
|
|
||||||
await UserScheduleStore.set('activeIndex', scheduleIndex);
|
await UserScheduleStore.set('activeIndex', scheduleIndex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const UserScheduleStore = createLocalStore<IUserScheduleStore>({
|
|||||||
courses: [],
|
courses: [],
|
||||||
name: 'Schedule 1',
|
name: 'Schedule 1',
|
||||||
hours: 0,
|
hours: 0,
|
||||||
|
updatedAt: Date.now(),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
activeIndex: 0,
|
activeIndex: 0,
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export class UserSchedule {
|
|||||||
courses: Course[];
|
courses: Course[];
|
||||||
name: string;
|
name: string;
|
||||||
hours: number;
|
hours: number;
|
||||||
|
/** Unix timestamp of when the schedule was last updated */
|
||||||
|
updatedAt: number;
|
||||||
|
|
||||||
constructor(schedule: Serialized<UserSchedule>) {
|
constructor(schedule: Serialized<UserSchedule>) {
|
||||||
this.courses = schedule.courses.map(c => new Course(c));
|
this.courses = schedule.courses.map(c => new Course(c));
|
||||||
@@ -17,6 +19,7 @@ export class UserSchedule {
|
|||||||
for (const course of this.courses) {
|
for (const course of this.courses) {
|
||||||
this.hours += course.creditHours;
|
this.hours += course.creditHours;
|
||||||
}
|
}
|
||||||
|
this.updatedAt = schedule.updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
containsCourse(course: Course): boolean {
|
containsCourse(course: Course): boolean {
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ const schedules = [
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
name: 'Main Schedule',
|
name: 'Main Schedule',
|
||||||
hours: 0, // Add the missing 'hours' property
|
hours: 0,
|
||||||
|
updatedAt: Date.now(),
|
||||||
}),
|
}),
|
||||||
new UserSchedule({
|
new UserSchedule({
|
||||||
courses: [
|
courses: [
|
||||||
@@ -135,7 +136,8 @@ const schedules = [
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
name: 'Backup #3',
|
name: 'Backup #3',
|
||||||
hours: 0, // Add the missing 'hours' property
|
hours: 0,
|
||||||
|
updatedAt: Date.now(),
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const ClosedCourse: Story = {
|
|||||||
...MikeScottCS314Course,
|
...MikeScottCS314Course,
|
||||||
status: Status.CLOSED,
|
status: Status.CLOSED,
|
||||||
} as Course,
|
} as Course,
|
||||||
activeSchedule: new UserSchedule({ courses: [], name: '', hours: 0 }),
|
activeSchedule: new UserSchedule({ courses: [], name: '', hours: 0, updatedAt: Date.now() }),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export const exampleSchedule: UserSchedule = new UserSchedule({
|
|||||||
courses: [exampleCourse],
|
courses: [exampleCourse],
|
||||||
name: 'Example Schedule',
|
name: 'Example Schedule',
|
||||||
hours: 3,
|
hours: 3,
|
||||||
|
updatedAt: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const bevoCourse: Course = new Course({
|
export const bevoCourse: Course = new Course({
|
||||||
@@ -108,6 +109,7 @@ export const bevoScheule: UserSchedule = new UserSchedule({
|
|||||||
courses: [bevoCourse],
|
courses: [bevoCourse],
|
||||||
name: 'Bevo Schedule',
|
name: 'Bevo Schedule',
|
||||||
hours: 3,
|
hours: 3,
|
||||||
|
updatedAt: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const MikeScottCS314Course: Course = new Course({
|
export const MikeScottCS314Course: Course = new Course({
|
||||||
@@ -160,4 +162,5 @@ export const MikeScottCS314Schedule: UserSchedule = new UserSchedule({
|
|||||||
courses: [MikeScottCS314Course],
|
courses: [MikeScottCS314Course],
|
||||||
name: 'Mike Scott CS314 Schedule',
|
name: 'Mike Scott CS314 Schedule',
|
||||||
hours: 3,
|
hours: 3,
|
||||||
|
updatedAt: Date.now(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import List from '@views/components/common/List/List';
|
|||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
import { handleOpenCalendar } from '@views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions';
|
import { handleOpenCalendar } from '@views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions';
|
||||||
import useSchedules, { getActiveSchedule, replaceSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
import useSchedules, { getActiveSchedule, replaceSchedule, switchSchedule } from '@views/hooks/useSchedules';
|
||||||
|
import { getUpdatedAtDateTimeString } from '@views/lib/getUpdatedAtDateTimeString';
|
||||||
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
|
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
@@ -120,7 +121,7 @@ export default function PopupMain(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
<div className='inline-flex items-center self-center gap-1'>
|
<div className='inline-flex items-center self-center gap-1'>
|
||||||
<Text variant='mini' className='text-ut-gray'>
|
<Text variant='mini' className='text-ut-gray'>
|
||||||
DATA UPDATED ON: 12:00 AM 02/01/2024
|
LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)}
|
||||||
</Text>
|
</Text>
|
||||||
<button
|
<button
|
||||||
className='h-4 w-4 bg-transparent p-0 btn'
|
className='h-4 w-4 bg-transparent p-0 btn'
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import Divider from '@views/components/common/Divider/Divider';
|
|||||||
import { LogoIcon } from '@views/components/common/LogoIcon';
|
import { LogoIcon } from '@views/components/common/LogoIcon';
|
||||||
import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotalHoursAndCourses/ScheduleTotalHoursAndCourses';
|
import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotalHoursAndCourses/ScheduleTotalHoursAndCourses';
|
||||||
import Text from '@views/components/common/Text/Text';
|
import Text from '@views/components/common/Text/Text';
|
||||||
|
import useSchedules from '@views/hooks/useSchedules';
|
||||||
|
import { getUpdatedAtDateTimeString } from '@views/lib/getUpdatedAtDateTimeString';
|
||||||
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
|
import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@@ -27,9 +29,11 @@ const handleOpenOptions = async (): Promise<void> => {
|
|||||||
* @returns The JSX element representing the calendar header.
|
* @returns The JSX element representing the calendar header.
|
||||||
*/
|
*/
|
||||||
export default function CalendarHeader(): JSX.Element {
|
export default function CalendarHeader(): JSX.Element {
|
||||||
|
const [activeSchedule] = useSchedules();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='min-h-79px min-w-672px w-full flex px-0 py-5'>
|
<div className='min-h-79px min-w-672px w-full flex px-0 py-5'>
|
||||||
<div className='flex flex-row gap-20'>
|
<div className='flex flex-row gap-20 w-full'>
|
||||||
<div className='flex gap-10'>
|
<div className='flex gap-10'>
|
||||||
<div className='flex gap-1'>
|
<div className='flex gap-1'>
|
||||||
<Button className='self-center' variant='single' icon={MenuIcon} color='ut-gray' />
|
<Button className='self-center' variant='single' icon={MenuIcon} color='ut-gray' />
|
||||||
@@ -43,13 +47,17 @@ export default function CalendarHeader(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
<Divider className='self-center' size='2.5rem' orientation='vertical' />
|
<Divider className='self-center' size='2.5rem' orientation='vertical' />
|
||||||
<div className='flex flex-col self-center'>
|
<div className='flex flex-col self-center'>
|
||||||
<ScheduleTotalHoursAndCourses scheduleName='SCHEDULE' totalHours={22} totalCourses={8} />
|
<ScheduleTotalHoursAndCourses
|
||||||
|
scheduleName={activeSchedule.name}
|
||||||
|
totalHours={activeSchedule.hours}
|
||||||
|
totalCourses={activeSchedule.courses.length}
|
||||||
|
/>
|
||||||
<Text variant='h4' className='text-xs text-gray font-medium leading-normal'>
|
<Text variant='h4' className='text-xs text-gray font-medium leading-normal'>
|
||||||
DATA UPDATED ON: 12:00 AM 02/01/2024
|
LAST UPDATED: {getUpdatedAtDateTimeString(activeSchedule.updatedAt)}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-row items-center justify-end space-x-8'>
|
<div className='flex flex-row items-center justify-end space-x-8 ml-auto'>
|
||||||
<div className='flex flex-row space-x-4'>
|
<div className='flex flex-row space-x-4'>
|
||||||
<CourseStatus size='small' status={Status.WAITLISTED} />
|
<CourseStatus size='small' status={Status.WAITLISTED} />
|
||||||
<CourseStatus size='small' status={Status.CLOSED} />
|
<CourseStatus size='small' status={Status.CLOSED} />
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ export default function ScheduleTotalHoursAndCourses({
|
|||||||
{`${scheduleName}: `}
|
{`${scheduleName}: `}
|
||||||
</Text>
|
</Text>
|
||||||
<Text variant='h3' as='div' className='flex flex-row items-center gap-2 text-theme-black'>
|
<Text variant='h3' as='div' className='flex flex-row items-center gap-2 text-theme-black'>
|
||||||
{totalHours} HOURS
|
{totalHours} {totalHours === 1 ? 'HOUR' : 'HOURS'}
|
||||||
<Text variant='h4' as='span' className='text-ut-black'>
|
<Text variant='h4' as='span' className='text-ut-black'>
|
||||||
{totalCourses} courses
|
{totalCourses} {totalCourses === 1 ? 'COURSE' : 'COURSES'}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export function useFlattenedCourseSchedule(): FlattenedCourseSchedule {
|
|||||||
courses: [],
|
courses: [],
|
||||||
name: 'Something may have went wrong',
|
name: 'Something may have went wrong',
|
||||||
hours: 0,
|
hours: 0,
|
||||||
|
updatedAt: Date.now(),
|
||||||
}),
|
}),
|
||||||
} satisfies FlattenedCourseSchedule;
|
} satisfies FlattenedCourseSchedule;
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/views/lib/getUpdatedAtDateTimeString.ts
Normal file
21
src/views/lib/getUpdatedAtDateTimeString.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param updatedAt {number} - The time in milliseconds since the epoch when the schedule was last updated.
|
||||||
|
* @returns {string} - DateTime formatted as HH:MM AM/PM MM/DD/YYYY
|
||||||
|
*/
|
||||||
|
export function getUpdatedAtDateTimeString(updatedAt: number): string {
|
||||||
|
const updatedAtDate = new Date(updatedAt);
|
||||||
|
|
||||||
|
const timeFormat = new Intl.DateTimeFormat('en-US', {
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
hour12: true,
|
||||||
|
}).format(updatedAtDate);
|
||||||
|
const dateFormat = new Intl.DateTimeFormat('en-US', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
}).format(updatedAtDate);
|
||||||
|
|
||||||
|
return `${timeFormat} ${dateFormat}`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user