Skip to content

Commit

Permalink
Modify all datetime displays to use consistent formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
smartspot2 committed Aug 5, 2023
1 parent 44bca40 commit fe923bb
Show file tree
Hide file tree
Showing 18 changed files with 421 additions and 206 deletions.
2 changes: 1 addition & 1 deletion csm_web/frontend/src/components/CourseMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const CourseMenu = () => {
if (userInfoLoaded) {
let priorityEnrollment = undefined;
if (jsonUserInfo.priorityEnrollment) {
priorityEnrollment = DateTime.fromISO(jsonUserInfo.priorityEnrollment);
priorityEnrollment = DateTime.fromISO(jsonUserInfo.priorityEnrollment, { zone: DEFAULT_TIMEZONE });
}
const convertedUserInfo: UserInfo = {
...jsonUserInfo,
Expand Down
3 changes: 2 additions & 1 deletion csm_web/frontend/src/components/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useCourses } from "../utils/queries/courses";
import { ROLES } from "./section/Section";
import { Profile, Course } from "../utils/types";
import LoadingSpinner from "./LoadingSpinner";
import { formatSpacetimeInterval } from "../utils/datetime";

const Home = () => {
const { data: profiles, isSuccess: profilesLoaded, isError: profilesLoadError } = useProfiles();
Expand Down Expand Up @@ -91,7 +92,7 @@ const CourseCard = ({ profiles }: CourseCardProps): React.ReactElement => {
<Link key={profile.id} to={`/sections/${profile.sectionId}`} className="section-link">
{profile.sectionSpacetimes.map(spacetime => (
<div key={spacetime.id} className="course-card-section-time">
{spacetime.time}
{formatSpacetimeInterval(spacetime)}
</div>
))}
</Link>
Expand Down
4 changes: 2 additions & 2 deletions csm_web/frontend/src/components/course/Course.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DateTime } from "luxon";
import React, { useState } from "react";
import { useParams } from "react-router-dom";
import { useCourseSections } from "../../utils/queries/courses";
Expand All @@ -8,10 +9,9 @@ import { DataExportModal } from "./DataExportModal";
import { SectionCard } from "./SectionCard";
import { WhitelistModal } from "./WhitelistModal";
import { SettingsModal } from "./SettingsModal";
import { DEFAULT_LONG_LOCALE_OPTIONS } from "../../utils/datetime";

import PencilIcon from "../../../static/frontend/img/pencil.svg";
import { DateTime } from "luxon";
import { DEFAULT_LONG_LOCALE_OPTIONS } from "../../utils/datetime";

const DAY_OF_WEEK_ABREVIATIONS: { [day: string]: string } = Object.freeze({
Monday: "M",
Expand Down
66 changes: 43 additions & 23 deletions csm_web/frontend/src/components/course/CreateSectionModal.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React, { useState } from "react";
import { dayOfWeekToEnglishString, DAYS_OF_WEEK } from "../../utils/datetime";
import { useUserEmails } from "../../utils/queries/base";
import { useSectionCreateMutation } from "../../utils/queries/sections";
import { Spacetime } from "../../utils/types";
import Modal from "../Modal";
import { DAYS_OF_WEEK } from "../section/utils";
import TimeInput from "../TimeInput";

const makeSpacetime = (): Spacetime => {
return { dayOfWeek: "", startTime: "", location: "" } as Spacetime;
return { id: -1, duration: 0, dayOfWeek: 1, startTime: "", location: "" };
};

interface CreateSectionModalProps {
Expand Down Expand Up @@ -57,17 +57,18 @@ export const CreateSectionModal = ({ courseId, closeModal, reloadSections }: Cre
/**
* Handle the change of a form field.
*/
const handleChange = ({ target: { name, value } }: React.ChangeEvent<HTMLSelectElement | HTMLInputElement>): void => {
if (name.startsWith("location") || name.startsWith("startTime") || name.startsWith("dayOfWeek")) {
// Funny JavaScript scoping workaround (let [name, index] = name.split("|") doesn't work)
let index;
[name, index] = name.split("|");
index = Number(index);
setSpacetimes([
...spacetimes.slice(0, index),
{ ...spacetimes[index], [name]: value },
...spacetimes.slice(index + 1)
]);
const handleChange = (index: number, name: string, value: string): void => {
if (name === "location" || name === "startTime" || name === "dayOfWeek" || name === "duration") {
setSpacetimes(oldSpacetimes => {
const newSpacetimes = [...oldSpacetimes];
if (name === "dayOfWeek" || name === "duration") {
// day of week is int
newSpacetimes[index][name] = parseInt(value);
} else {
newSpacetimes[index][name] = value;
}
return newSpacetimes;
});
} else {
switch (name) {
case "mentorEmail":
Expand Down Expand Up @@ -115,7 +116,7 @@ export const CreateSectionModal = ({ courseId, closeModal, reloadSections }: Cre
<label>
Mentor Email
<input
onChange={handleChange}
onChange={e => handleChange(-1, "mentorEmail", e.target.value)}
type="email"
list="user-email-list"
required
Expand All @@ -139,22 +140,27 @@ export const CreateSectionModal = ({ courseId, closeModal, reloadSections }: Cre
inputMode="numeric"
pattern="[0-9]*"
value={capacity}
onChange={handleChange}
onChange={e => handleChange(-1, "capacity", e.target.value)}
/>
</label>
<label>
Description
<input name="description" type="text" value={description} onChange={handleChange} />
<input
name="description"
type="text"
value={description}
onChange={e => handleChange(-1, "description", e.target.value)}
/>
</label>
</div>
{spacetimes.map(({ dayOfWeek, startTime, location }, index) => (
{spacetimes.map(({ dayOfWeek, startTime, location, duration }, index) => (
<React.Fragment key={index}>
<h4 className="spacetime-fields-header">Weekly occurence {index + 1}</h4>
<div className="spacetime-fields">
<label>
Location
<input
onChange={handleChange}
onChange={e => handleChange(index, "location", e.target.value)}
required
title="You cannot leave this field blank"
pattern=".*[^\s]+.*"
Expand All @@ -165,23 +171,37 @@ export const CreateSectionModal = ({ courseId, closeModal, reloadSections }: Cre
</label>
<label>
Day
<select onChange={handleChange} name={`dayOfWeek|${index}`} value={dayOfWeek} required>
{["---"].concat(DAYS_OF_WEEK).map(day => (
<option key={day} value={day === "---" ? "" : day} disabled={day === "---"}>
{day}
<select
onChange={e => handleChange(index, "dayOfWeek", e.target.value)}
name={`dayOfWeek|${index}`}
value={dayOfWeek}
required
>
{[["---", ""], ...Array.from(DAYS_OF_WEEK)].map(([label, value]) => (
<option key={value} value={value} disabled={value === "---"}>
{label}
</option>
))}
</select>
</label>
<label>
Time
<TimeInput
onChange={handleChange as React.FormEventHandler<HTMLInputElement>}
onChange={e => handleChange(index, "startTime", e.target.value)}
required
name={`startTime|${index}`}
value={startTime}
/>
</label>
<label>
Duration (min)
<input
type="number"
name={`duration|${index}`}
value={duration}
onChange={e => handleChange(index, "duration", e.target.value)}
/>
</label>
{index === spacetimes.length - 1 && (
<button className="csm-btn" id="add-occurence-btn" onClick={appendSpacetime}>
Add another occurence
Expand Down
9 changes: 5 additions & 4 deletions csm_web/frontend/src/components/course/SectionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import GroupIcon from "../../../static/frontend/img/group.svg";
import LocationIcon from "../../../static/frontend/img/location.svg";
import UserIcon from "../../../static/frontend/img/user.svg";
import XCircle from "../../../static/frontend/img/x_circle.svg";
import { formatSpacetimeInterval } from "../../utils/datetime";

interface SectionCardProps {
id: number;
Expand Down Expand Up @@ -151,16 +152,16 @@ export const SectionCard = ({
)}
</p>
<p title="Time">
<ClockIcon width={iconWidth} height={iconHeight} /> {spacetimes[0].time}
<ClockIcon width={iconWidth} height={iconHeight} /> {formatSpacetimeInterval(spacetimes[0])}
{spacetimes.length > 1 && (
<span className="section-card-additional-times">
{spacetimes.slice(1).map(({ time, id }) => (
<React.Fragment key={id}>
{spacetimes.slice(1).map(spacetime => (
<React.Fragment key={spacetime.id}>
<span
className="section-card-icon-placeholder"
style={{ minWidth: iconWidth, minHeight: iconHeight }}
/>{" "}
{time}
{formatSpacetimeInterval(spacetime)}
</React.Fragment>
))}
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Resource } from "./ResourceTypes";

import Pencil from "../../../static/frontend/img/pencil.svg";
import Trash from "../../../static/frontend/img/trash-alt.svg";
import { formatDate } from "../../utils/datetime";
import { DEFAULT_TIMEZONE, formatDate } from "../../utils/datetime";
import { DateTime } from "luxon";

interface ResourceRowRenderProps {
Expand Down Expand Up @@ -60,7 +60,7 @@ const ResourceRowRender = ({ resource, canEdit, onSetEdit, onDelete }: ResourceR
<div>Week {resource.weekNum}</div>
</div>
<div className="resourceInfo dateCell">
<div>{formatDate(DateTime.fromISO(resource.date))}</div>
<div>{formatDate(DateTime.fromISO(resource.date, { zone: DEFAULT_TIMEZONE }))}</div>
</div>
<div className="resourceInfo resourceTopics">
<ResourceTopics topics={resource.topics} />
Expand Down
14 changes: 5 additions & 9 deletions csm_web/frontend/src/components/section/Section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import MentorSection from "./MentorSection";
import { Override, Spacetime } from "../../utils/types";
import { useSection } from "../../utils/queries/sections";
import LoadingSpinner from "../LoadingSpinner";
import { formatSpacetimeInterval } from "../../utils/datetime";

export const ROLES = Object.freeze({ COORDINATOR: "COORDINATOR", STUDENT: "STUDENT", MENTOR: "MENTOR" });

Expand Down Expand Up @@ -89,24 +90,19 @@ interface SectionSpacetimeProps {
override?: Override;
}

export function SectionSpacetime({
manySpacetimes,
index,
spacetime: { location, time },
override,
children
}: SectionSpacetimeProps) {
export function SectionSpacetime({ manySpacetimes, index, spacetime, override, children }: SectionSpacetimeProps) {
const location = spacetime.location;
return (
<InfoCard title={`Time and Location${manySpacetimes ? ` ${index + 1}` : ""}`}>
{children}
<Location location={location} />
<h5>{time}</h5>
<h5>{formatSpacetimeInterval(spacetime)}</h5>
{override && (
<React.Fragment>
<div className="divider" />
<div className="override-label">Adjusted for {override.date}</div>
<Location location={override.spacetime.location} />
<h5>{override.spacetime.time}</h5>
<h5>{formatSpacetimeInterval(override.spacetime)}</h5>
</React.Fragment>
)}
</InfoCard>
Expand Down
17 changes: 8 additions & 9 deletions csm_web/frontend/src/components/section/SpacetimeEditModal.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { DateTime } from "luxon";
import React, { useState } from "react";
import { DAYS_OF_WEEK } from "../../utils/datetime";
import { useSpacetimeModifyMutation, useSpacetimeOverrideMutation } from "../../utils/queries/spacetime";
import { Spacetime } from "../../utils/types";
import LoadingSpinner from "../LoadingSpinner";
import Modal from "../Modal";
import TimeInput from "../TimeInput";
import { DAYS_OF_WEEK } from "./utils";

interface SpacetimeEditModalProps {
sectionId: number;
Expand All @@ -20,10 +20,9 @@ const SpaceTimeEditModal = ({
defaultSpacetime: { id: spacetimeId, startTime: timeString, location: prevLocation, dayOfWeek },
editingOverride
}: SpacetimeEditModalProps): React.ReactElement => {
const sliceIndex = timeString.split(":").length < 3 ? timeString.indexOf(":") : timeString.lastIndexOf(":");
const [location, setLocation] = useState<Spacetime["location"]>(prevLocation);
const [day, setDay] = useState<Spacetime["dayOfWeek"]>(dayOfWeek);
const [time, setTime] = useState<Spacetime["time"]>(timeString.slice(0, sliceIndex));
const [time, setTime] = useState<Spacetime["startTime"]>(timeString);
const [isPermanent, setIsPermanent] = useState<boolean>(false);
const [date, setDate] = useState<string>("");
const [mode, setMode] = useState<string>(prevLocation && prevLocation.startsWith("http") ? "virtual" : "inperson");
Expand All @@ -38,13 +37,13 @@ const SpaceTimeEditModal = ({
setShowSaveSpinner(true);
isPermanent
? spacetimeModifyMutation.mutate({
day_of_week: day,
dayOfWeek,
location: location,
start_time: `${time}:00`
startTime: time
})
: spacetimeOverrideMutation.mutate({
location: location,
start_time: `${time}:00`,
startTime: time,
date: date
});
closeModal();
Expand Down Expand Up @@ -100,15 +99,15 @@ const SpaceTimeEditModal = ({
<label>
Day
<select
onChange={e => setDay(e.target.value)}
onChange={e => setDay(parseInt(e.target.value))}
required={!!isPermanent}
name="day"
disabled={!isPermanent}
value={isPermanent ? day : "---"}
>
{["---"].concat(Array.from(DAYS_OF_WEEK)).map(value => (
{[["---", ""], ...Array.from(DAYS_OF_WEEK)].map(([label, value]) => (
<option key={value} value={value} disabled={value === "---"}>
{value}
{label}
</option>
))}
</select>
Expand Down
19 changes: 6 additions & 13 deletions csm_web/frontend/src/components/section/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DateTime } from "luxon";
import { DEFAULT_TIMEZONE } from "../../utils/datetime";

export const MONTH_NUMBERS: Readonly<{ [month: string]: number }> = Object.freeze({
Jan: 1,
Expand All @@ -15,24 +16,14 @@ export const MONTH_NUMBERS: Readonly<{ [month: string]: number }> = Object.freez
Dec: 12
});

export const DAYS_OF_WEEK: Readonly<string[]> = Object.freeze([
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
]);

/**
* Convert date from ISO to mm/dd/yy.
*
* Example:
* formatDate("2022-01-06") --> "1/6/22"
*/
export function formatDateLocaleShort(dateString: string): string {
return DateTime.fromISO(dateString).toLocaleString({
return DateTime.fromISO(dateString, { zone: DEFAULT_TIMEZONE }).toLocaleString({
...DateTime.DATE_SHORT,
// use 2-digit year
year: "2-digit"
Expand All @@ -46,14 +37,16 @@ export function formatDateLocaleShort(dateString: string): string {
* formatDate("2022-01-06") --> "Jan 6, 2022"
*/
export function formatDateAbbrevWord(dateString: string): string {
return DateTime.fromISO(dateString).toLocaleString(DateTime.DATE_MED);
return DateTime.fromISO(dateString, { zone: DEFAULT_TIMEZONE }).toLocaleString(DateTime.DATE_MED);
}

/**
* Sort two dates in ISO.
*/
export function dateSortISO(date1: string, date2: string) {
return DateTime.fromISO(date2).diff(DateTime.fromISO(date1)).as("milliseconds");
const datetime1 = DateTime.fromISO(date1, { zone: DEFAULT_TIMEZONE });
const datetime2 = DateTime.fromISO(date2, { zone: DEFAULT_TIMEZONE });
return datetime2.diff(datetime1).as("milliseconds");
}

export function zeroPadTwoDigit(num: number) {
Expand Down
Loading

0 comments on commit fe923bb

Please sign in to comment.