mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-11 21:56:11 -05:00
New Date entry on the new Calendar page is now based off the previous one (#1070)
* Issue 858: On the new calendar page adding a new Date entry with the "Add" button will now insert a new date with an offset of +24 hours of the previous row's date value. * Added comment to ignore the TSLint "object can be null" error * Changed `var` to `const` * Fixed Prettier checks in CI pipeline * Fixed Typecheck CI pipeline error * Moved getDateWithHoursOffset() function to app/utils/dates.ts * Added new line at end of file * Added getValidNewDateIfInvalid() function. This retrieves a valid date. If invalid, get a new Date object. - So now, if we intentionally/accidentally delete the data in the Calendar's DateInput element, it will be reset to the current Date/Time * Refactored DateInput component's update state mechanism to be handled by an onChange() function defined in the parent component that is passed to the child * Prettier formatting so that the new CI pipeline won't output errors at me * Removed unused imported types * Removed the datesCount React Hook & refactored accordingly * Removed unused loader-related variables * DateInput onChange prop is now optional * Instead of generating a new Array, iterate over DateInput's inputState's array instead * Fix potential undefined error * DatesInputState: refactored to remove index & access the index during iteration with map() 2nd arg * Properly initialized state for pre-existing events =) - Also added TODO comments for improving date input handling (1082) * Prettier formatting * Uncommented console.warn() * Touched up comment
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||
import { dateToYearMonthDayHourMinuteString } from "~/utils/dates";
|
||||
import { dateToYearMonthDayHourMinuteString, isValidDate } from "~/utils/dates";
|
||||
import * as React from "react";
|
||||
|
||||
export function DateInput({
|
||||
@@ -9,6 +9,7 @@ export function DateInput({
|
||||
min,
|
||||
max,
|
||||
required,
|
||||
onChange,
|
||||
}: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
@@ -16,6 +17,7 @@ export function DateInput({
|
||||
min?: Date;
|
||||
max?: Date;
|
||||
required?: boolean;
|
||||
onChange?: (newDate: Date) => void;
|
||||
}) {
|
||||
const [date, setDate] = React.useState(defaultValue ?? new Date());
|
||||
const isMounted = useIsMounted();
|
||||
@@ -41,7 +43,20 @@ export function DateInput({
|
||||
value={dateToYearMonthDayHourMinuteString(date)}
|
||||
min={min ? dateToYearMonthDayHourMinuteString(min) : undefined}
|
||||
max={max ? dateToYearMonthDayHourMinuteString(max) : undefined}
|
||||
onChange={(e) => setDate(new Date(e.target.value))}
|
||||
onChange={(e) => {
|
||||
//TODO: fix invalid Date Input handling: https://github.com/Sendouc/sendou.ink/issues/1082
|
||||
const updatedDate = new Date(e.target.value);
|
||||
if (!isValidDate(updatedDate)) {
|
||||
console.warn("Invalid date");
|
||||
// throw new RangeError("Invalid Date");
|
||||
}
|
||||
setDate(updatedDate);
|
||||
|
||||
// Update the correct entry in the React hook from the parent via the passed on callback function
|
||||
if (typeof onChange !== "undefined") {
|
||||
onChange(updatedDate);
|
||||
}
|
||||
}}
|
||||
required={required}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -31,8 +31,9 @@ import { canEditCalendarEvent } from "~/permissions";
|
||||
import calendarNewStyles from "~/styles/calendar-new.css";
|
||||
import mapsStyles from "~/styles/maps.css";
|
||||
import {
|
||||
databaseTimestampToDate,
|
||||
dateToDatabaseTimestamp,
|
||||
databaseTimestampToDate,
|
||||
getDateWithHoursOffset,
|
||||
} from "~/utils/dates";
|
||||
import {
|
||||
badRequestIfFalsy,
|
||||
@@ -288,14 +289,31 @@ function DescriptionTextarea() {
|
||||
function DatesInput() {
|
||||
const { t } = useTranslation(["common", "calendar"]);
|
||||
const { eventToEdit } = useLoaderData<typeof loader>();
|
||||
const [datesCount, setDatesCount] = React.useState(
|
||||
eventToEdit?.startTimes.length ?? 1
|
||||
);
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
// Initialize datesInputState by retrieving pre-existing events if they exist
|
||||
let eventDatesInputState = null;
|
||||
if (typeof eventToEdit?.startTimes !== "undefined") {
|
||||
eventDatesInputState = eventToEdit.startTimes.map((t) => {
|
||||
return { finalDateInputDate: databaseTimestampToDate(t) };
|
||||
});
|
||||
}
|
||||
|
||||
// React hook that keeps contains an array of parameters that corresponds to each DateInput child object generated
|
||||
const [datesInputState, setDatesInputState] = React.useState(
|
||||
eventDatesInputState ?? [
|
||||
{
|
||||
finalDateInputDate: new Date(),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
const datesCount = datesInputState.length;
|
||||
|
||||
const isMounted = useIsMounted();
|
||||
const usersTimeZone = isMounted
|
||||
? Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
: "";
|
||||
const NEW_CALENDAR_EVENT_HOURS_OFFSET = 24;
|
||||
|
||||
return (
|
||||
<div className="stack md items-start">
|
||||
@@ -304,38 +322,58 @@ function DatesInput() {
|
||||
{t("calendar:forms.dates")}
|
||||
</Label>
|
||||
<div className="stack sm">
|
||||
{new Array(datesCount).fill(null).map((_, i) => {
|
||||
const defaultStartTime = eventToEdit?.startTimes[i];
|
||||
|
||||
{datesInputState.map((inputState, i) => {
|
||||
return (
|
||||
<div key={i} className="stack horizontal sm items-center">
|
||||
<DateInput
|
||||
id="date"
|
||||
name="date"
|
||||
defaultValue={
|
||||
defaultStartTime
|
||||
? databaseTimestampToDate(defaultStartTime)
|
||||
: undefined
|
||||
}
|
||||
defaultValue={inputState.finalDateInputDate ?? new Date()}
|
||||
min={MIN_DATE}
|
||||
max={MAX_DATE}
|
||||
required
|
||||
onChange={(newDate: Date) => {
|
||||
setDatesInputState((current) =>
|
||||
current.map((obj, objIndex) => {
|
||||
if (objIndex === i) {
|
||||
return { ...obj, finalDateInputDate: newDate };
|
||||
}
|
||||
|
||||
return obj;
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{i === datesCount - 1 && (
|
||||
<>
|
||||
{/* "Add" button */}
|
||||
<Button
|
||||
tiny
|
||||
disabled={
|
||||
datesCount === CALENDAR_EVENT.MAX_AMOUNT_OF_DATES
|
||||
}
|
||||
onClick={() => setDatesCount((count) => count + 1)}
|
||||
onClick={() => {
|
||||
setDatesInputState((current) => [
|
||||
...current,
|
||||
{
|
||||
finalDateInputDate: getDateWithHoursOffset(
|
||||
inputState.finalDateInputDate,
|
||||
NEW_CALENDAR_EVENT_HOURS_OFFSET
|
||||
),
|
||||
},
|
||||
]);
|
||||
}}
|
||||
>
|
||||
{t("common:actions.add")}
|
||||
</Button>
|
||||
|
||||
{/* "Remove" button */}
|
||||
{datesCount > 1 && (
|
||||
<Button
|
||||
tiny
|
||||
onClick={() => setDatesCount((count) => count - 1)}
|
||||
onClick={() => {
|
||||
setDatesInputState((current) => current.slice(0, -1));
|
||||
}}
|
||||
variant="destructive"
|
||||
>
|
||||
{t("common:actions.remove")}
|
||||
|
||||
@@ -34,13 +34,32 @@ export function weekNumberToDate({
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a date is valid or not.
|
||||
*
|
||||
* Returns:
|
||||
* - True if date is valid
|
||||
* - False otherwise
|
||||
*/
|
||||
export function isValidDate(date: Date) {
|
||||
return !isNaN(date.getTime());
|
||||
}
|
||||
|
||||
/** Returns date as a string with the format YYYY-MM-DDThh:mm in user's time zone */
|
||||
export function dateToYearMonthDayHourMinuteString(date: Date) {
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const hour = date.getHours();
|
||||
const minute = date.getMinutes();
|
||||
const copiedDate = new Date(date.getTime());
|
||||
|
||||
//TODO: fix invalid Date Input handling: https://github.com/Sendouc/sendou.ink/issues/1082
|
||||
if (!isValidDate(copiedDate)) {
|
||||
console.warn("Invalid date");
|
||||
// throw new RangeError("Invalid Date");
|
||||
}
|
||||
|
||||
const year = copiedDate.getFullYear();
|
||||
const month = copiedDate.getMonth() + 1;
|
||||
const day = copiedDate.getDate();
|
||||
const hour = copiedDate.getHours();
|
||||
const minute = copiedDate.getMinutes();
|
||||
|
||||
return `${year}-${prefixZero(month)}-${prefixZero(day)}T${prefixZero(
|
||||
hour
|
||||
@@ -50,3 +69,15 @@ export function dateToYearMonthDayHourMinuteString(date: Date) {
|
||||
function prefixZero(number: number) {
|
||||
return number < 10 ? `0${number}` : number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a new Date object that is offset by several hours.
|
||||
*
|
||||
* NOTE: it is important that we work with & return a copy of the date here,
|
||||
* otherwise we will just be mutating the original date passed into this function.
|
||||
*/
|
||||
export function getDateWithHoursOffset(date: Date, hoursOffset: number) {
|
||||
const copiedDate = new Date(date.getTime());
|
||||
copiedDate.setHours(date.getHours() + hoursOffset);
|
||||
return copiedDate;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user