Skip to content

Commit d9073f7

Browse files
authored
πŸ› Fix: Change Dates Using Keyboard and DatePicker (#287)
* πŸ› Fix: prevent form submission after pressing ENTER on date picker input * ✨ feat: allow ENTER and arrows to work correctly on date picker Focus Trap was preventing the date picker's keyboard events from being captured. * 🧹chore: Remove focus-trap-react Removed unnecessary dependencies related to focus-trap, eslint-plugin-import, and other unused packages to clean up project dependencies. * 🎨 Fix: Set default background color for DatePicker * πŸ› Fix: Prevent multiple clicks needed for selecting date - Remove index files for DatePicker and Input components - Update import paths for DatePicker and Input - Modify Focusable component to use setIsFocused instead of toggleFocused - Adjust DatePicker input to use StyledInput * πŸ› Fix: Prevent date changes from being overwritten after other changes When changing a date and then changing another form field (like title), the date change was being lost. This makes sure that everytime the date is changed, the draft is updated. * πŸ› Fix: Prevent mismatch between displayed date and internal date Tracking these separately makes it possible to display user-friendly dates, while also letting the date picker and backend use backend-friendly dates, which save an all-day event like so: start: 2025-03-03, end 2025-03-04 * πŸ› fix(checkpoint): prevent runtime error when moving start after end date * 🧹 Chore: move date utils to web.datetime.util * πŸ› fix(checkpoint): prevent runtime error when moving end before start remove unnecessary adjustComplimentDateIfNeeded, preferring to do this logic on the onSelect handlers * ✨ Feat: makes dates same after moving end before start * 🧹 Chore: Cleanup comments and DatePickers code * ♻️ Refactor: Simplify event form field updates Modify event form field update logic to use a more concise and consistent approach: - Simplify onSetEventField to accept a partial event object - Update type definitions to match new implementation - Adjust event field updates across multiple components to use new method
1 parent bbc68ea commit d9073f7

File tree

20 files changed

+341
-413
lines changed

20 files changed

+341
-413
lines changed

β€Žpackages/web/package.jsonβ€Ž

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
"classnames": "^2.3.1",
2020
"css-loader": "^6.3.0",
2121
"dayjs": "^1.10.7",
22-
"focus-trap-react": "^10.3.0",
2322
"html-webpack-plugin": "^5.3.2",
2423
"http-server": "^14.1.0",
2524
"immutability-helper": "^3.1.1",

packages/web/src/__tests__/utils/date.util/date.adjust.test.ts renamed to packages/web/src/common/utils/datetime/web.datetime.util.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
22
shouldAdjustComplimentDate,
33
shouldAdjustComplimentTime,
4-
} from "@web/common/utils/web.date.util";
4+
} from "@web/common/utils/datetime/web.datetime.util";
55

66
describe("Dates", () => {
77
it("recognizes date adjustment after changing start", () => {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* Utility functions for working with dates and times that are
3+
* specific to the web app.
4+
* Datetime utilities that apply to both backend and web
5+
* should go in @core/
6+
*/
7+
import dayjs, { Dayjs } from "dayjs";
8+
import { YMDHAM_FORMAT } from "@core/constants/date.constants";
9+
import {
10+
Params_DateChange,
11+
Params_TimeChange,
12+
} from "@web/common/types/util.types";
13+
14+
export const shouldAdjustComplimentDate = (
15+
changed: "start" | "end",
16+
vals: Params_DateChange,
17+
) => {
18+
const { start, end } = vals;
19+
const _start = dayjs(start);
20+
const _end = dayjs(end);
21+
22+
let shouldAdjust = false;
23+
let compliment = start;
24+
25+
if (changed === "start") {
26+
shouldAdjust = _start.isAfter(_end);
27+
if (shouldAdjust) {
28+
compliment = start;
29+
}
30+
}
31+
32+
if (changed === "end") {
33+
shouldAdjust = _end.isBefore(_start);
34+
35+
if (shouldAdjust) {
36+
compliment = end;
37+
}
38+
}
39+
40+
return { shouldAdjust, compliment };
41+
};
42+
43+
export const shouldAdjustComplimentTime = (
44+
changed: "start" | "end",
45+
vals: Params_TimeChange,
46+
) => {
47+
let shouldAdjust: boolean;
48+
let duration: number;
49+
let step: number;
50+
let compliment: Dayjs;
51+
52+
const { oldStart, oldEnd, start, end } = vals;
53+
54+
const _start = dayjs(`2000-01-01 ${start}`, YMDHAM_FORMAT);
55+
const _end = dayjs(`2000-01-01 ${end}`, YMDHAM_FORMAT);
56+
const isSame = _start.isSame(_end);
57+
58+
if (changed === "start") {
59+
shouldAdjust = _start.isAfter(_end) || isSame;
60+
61+
if (shouldAdjust) {
62+
const _oldStart = dayjs(`2000-01-01 ${oldStart}`, YMDHAM_FORMAT);
63+
const _oldEnd = dayjs(`2000-01-01 ${oldEnd}`, YMDHAM_FORMAT);
64+
duration = Math.abs(_oldStart.diff(_oldEnd, "minutes"));
65+
66+
step = Math.abs(_start.diff(_end, "minutes"));
67+
68+
compliment = dayjs(`2000-01-01 ${end}`, YMDHAM_FORMAT);
69+
}
70+
}
71+
72+
if (changed === "end") {
73+
shouldAdjust = _end.isBefore(_start) || isSame;
74+
75+
if (shouldAdjust) {
76+
const _oldStart = dayjs(`2000-01-01 ${oldStart}`, YMDHAM_FORMAT);
77+
const _oldEnd = dayjs(`2000-01-01 ${oldEnd}`, YMDHAM_FORMAT);
78+
duration = Math.abs(_oldStart.diff(_oldEnd, "minutes"));
79+
80+
step = Math.abs(_start.diff(_end, "minutes"));
81+
82+
compliment = dayjs(`2000-01-01 ${start}`, YMDHAM_FORMAT);
83+
}
84+
}
85+
86+
const adjustment = duration + step;
87+
88+
return { shouldAdjust, adjustment, compliment };
89+
};

β€Žpackages/web/src/common/utils/web.date.util.tsβ€Ž

Lines changed: 2 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@ import {
88
YMDHM_FORMAT,
99
} from "@core/constants/date.constants";
1010
import { Categories_Event, Direction_Migrate } from "@core/types/event.types";
11-
import {
12-
Option_Time,
13-
Params_DateChange,
14-
Params_TimeChange,
15-
} from "@web/common/types/util.types";
11+
import { Option_Time } from "@web/common/types/util.types";
1612
import { GRID_TIME_STEP } from "@web/views/Calendar/layout.constants";
1713
import { roundToNext } from ".";
1814
import { ACCEPTED_TIMES, OPTIONS_RECURRENCE } from "../constants/web.constants";
@@ -237,11 +233,9 @@ export const getCalendarHeadingLabel = (
237233

238234
export const mapToBackend = (s: Schema_SelectedDates) => {
239235
if (s.isAllDay) {
240-
const adjustedEnd = dayjs(s.endDate).add(1, "day");
241-
242236
return {
243237
startDate: dayjs(s.startDate).format(YEAR_MONTH_DAY_FORMAT),
244-
endDate: adjustedEnd.format(YEAR_MONTH_DAY_FORMAT),
238+
endDate: dayjs(s.endDate).format(YEAR_MONTH_DAY_FORMAT),
245239
};
246240
}
247241

@@ -250,83 +244,6 @@ export const mapToBackend = (s: Schema_SelectedDates) => {
250244
return { startDate, endDate };
251245
};
252246

253-
export const shouldAdjustComplimentDate = (
254-
changed: "start" | "end",
255-
vals: Params_DateChange,
256-
) => {
257-
let shouldAdjust: boolean;
258-
let compliment: Date;
259-
260-
const { start, end } = vals;
261-
const _start = dayjs(start);
262-
const _end = dayjs(end);
263-
264-
if (changed === "start") {
265-
shouldAdjust = _start.isAfter(_end);
266-
if (shouldAdjust) {
267-
compliment = start;
268-
}
269-
}
270-
271-
if (changed === "end") {
272-
shouldAdjust = _end.isBefore(_start);
273-
274-
if (shouldAdjust) {
275-
compliment = end;
276-
}
277-
}
278-
279-
return { shouldAdjust, compliment };
280-
};
281-
282-
export const shouldAdjustComplimentTime = (
283-
changed: "start" | "end",
284-
vals: Params_TimeChange,
285-
) => {
286-
let shouldAdjust: boolean;
287-
let duration: number;
288-
let step: number;
289-
let compliment: Dayjs;
290-
291-
const { oldStart, oldEnd, start, end } = vals;
292-
293-
const _start = dayjs(`2000-01-01 ${start}`, YMDHAM_FORMAT);
294-
const _end = dayjs(`2000-01-01 ${end}`, YMDHAM_FORMAT);
295-
const isSame = _start.isSame(_end);
296-
297-
if (changed === "start") {
298-
shouldAdjust = _start.isAfter(_end) || isSame;
299-
300-
if (shouldAdjust) {
301-
const _oldStart = dayjs(`2000-01-01 ${oldStart}`, YMDHAM_FORMAT);
302-
const _oldEnd = dayjs(`2000-01-01 ${oldEnd}`, YMDHAM_FORMAT);
303-
duration = Math.abs(_oldStart.diff(_oldEnd, "minutes"));
304-
305-
step = Math.abs(_start.diff(_end, "minutes"));
306-
307-
compliment = dayjs(`2000-01-01 ${end}`, YMDHAM_FORMAT);
308-
}
309-
}
310-
311-
if (changed === "end") {
312-
shouldAdjust = _end.isBefore(_start) || isSame;
313-
314-
if (shouldAdjust) {
315-
const _oldStart = dayjs(`2000-01-01 ${oldStart}`, YMDHAM_FORMAT);
316-
const _oldEnd = dayjs(`2000-01-01 ${oldEnd}`, YMDHAM_FORMAT);
317-
duration = Math.abs(_oldStart.diff(_oldEnd, "minutes"));
318-
319-
step = Math.abs(_start.diff(_end, "minutes"));
320-
321-
compliment = dayjs(`2000-01-01 ${start}`, YMDHAM_FORMAT);
322-
}
323-
}
324-
325-
const adjustment = duration + step;
326-
327-
return { shouldAdjust, adjustment, compliment };
328-
};
329-
330247
// uses inferred timezone and shortened string to
331248
// convert to a string format that the backend/gcal/mongo accepts:
332249
// '2022-02-04 12:15' -> '2022-02-04T12:15:00-06:00'

β€Žpackages/web/src/components/DatePicker/DatePicker.tsxβ€Ž

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { isDark } from "@core/util/color.utils";
77
import { theme } from "@web/common/styles/theme";
88
import { Flex } from "@web/components/Flex";
99
import { AlignItems, JustifyContent } from "@web/components/Flex/styled";
10-
import { Input } from "@web/components/Input";
1110
import { Text } from "@web/components/Text";
11+
import { StyledInput } from "../Input/styled";
1212
import {
1313
ChangeDayButtonsStyledFlex,
1414
MonthContainerStyled,
@@ -20,6 +20,7 @@ import {
2020
export interface Props extends ReactDatePickerProps {
2121
animationOnToggle?: boolean;
2222
bgColor?: string;
23+
displayDate?: Date;
2324
inputColor?: string;
2425
isOpen?: boolean;
2526
onInputBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
@@ -34,7 +35,7 @@ export interface CalendarRef extends HTMLDivElement {
3435
export const DatePicker: React.FC<Props> = ({
3536
animationOnToggle = true,
3637
autoFocus: _autoFocus = false,
37-
bgColor,
38+
bgColor = theme.color.bg.primary,
3839
calendarClassName,
3940
inputColor,
4041
isOpen = true,
@@ -50,7 +51,7 @@ export const DatePicker: React.FC<Props> = ({
5051
const headerColor =
5152
view === "sidebar"
5253
? theme.color.text.light
53-
: isDark(bgColor)
54+
: isDark(bgColor || "")
5455
? theme.color.text.lighter
5556
: theme.color.text.dark;
5657

@@ -79,7 +80,7 @@ export const DatePicker: React.FC<Props> = ({
7980
view={view}
8081
/>
8182
)}
82-
customInput={<Input bgColor={inputColor} onBlurCapture={onInputBlur} />}
83+
customInput={<StyledInput bgColor={inputColor} />}
8384
dateFormat={"M-d-yyyy"}
8485
formatWeekDay={(day) => day[0]}
8586
open={isOpen}
@@ -96,7 +97,6 @@ export const DatePicker: React.FC<Props> = ({
9697
onSelect(date, event);
9798
}}
9899
portalId="root"
99-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
100100
ref={datepickerRef as any}
101101
showPopperArrow={false}
102102
renderCustomHeader={({

β€Žpackages/web/src/components/DatePicker/index.tsβ€Ž

Lines changed: 0 additions & 3 deletions
This file was deleted.

β€Žpackages/web/src/components/Focusable/Focusable.tsxβ€Ž

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export type Props<T extends ClassNamedComponent> = T &
1212
underlineColor?: string;
1313
};
1414

15-
const _Focusable = <T,>(
15+
const _Focusable = <T extends ClassNamedComponent>(
1616
{
1717
autoFocus = false,
1818
Component,
@@ -22,16 +22,18 @@ const _Focusable = <T,>(
2222
}: Props<T>,
2323
ref: Ref<HTMLDivElement>,
2424
) => {
25-
const [isFocused, toggleFocused] = useState(autoFocus);
25+
const [isFocused, setIsFocused] = useState(false);
2626
const rest = props as unknown as T;
2727

2828
return (
2929
<>
3030
<Component
3131
{...rest}
3232
ref={ref}
33-
onFocus={() => toggleFocused(true)}
34-
onBlur={() => toggleFocused(false)}
33+
onFocus={() => setIsFocused(true)}
34+
onBlur={() => {
35+
setIsFocused(false);
36+
}}
3537
autoFocus={autoFocus}
3638
/>
3739
{!!withUnderline && isFocused && <Divider color={underlineColor} />}

β€Žpackages/web/src/components/Input/index.tsβ€Ž

Lines changed: 0 additions & 3 deletions
This file was deleted.

β€Žpackages/web/src/views/Calendar/components/Sidebar/MonthTab/MonthPicker/SidebarMonthPicker.tsxβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import dayjs from "dayjs";
22
import weekPlugin from "dayjs/plugin/weekOfYear";
33
import React, { FC, useEffect, useState } from "react";
44
import { ID_DATEPICKER_SIDEBAR } from "@web/common/constants/web.constants";
5-
import { DatePicker } from "@web/components/DatePicker";
5+
import { DatePicker } from "@web/components/DatePicker/DatePicker";
66
import { WeekProps } from "@web/views/Calendar/hooks/useWeek";
77
import { MonthPickerContainer } from "./../styled";
88

0 commit comments

Comments
Β (0)