Skip to content

Commit 5c87aba

Browse files
committed
🐛 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
1 parent 3e1d693 commit 5c87aba

File tree

7 files changed

+87
-52
lines changed

7 files changed

+87
-52
lines changed

packages/web/src/common/utils/web.date.util.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,11 +237,9 @@ export const getCalendarHeadingLabel = (
237237

238238
export const mapToBackend = (s: Schema_SelectedDates) => {
239239
if (s.isAllDay) {
240-
const adjustedEnd = dayjs(s.endDate).add(1, "day");
241-
242240
return {
243241
startDate: dayjs(s.startDate).format(YEAR_MONTH_DAY_FORMAT),
244-
endDate: adjustedEnd.format(YEAR_MONTH_DAY_FORMAT),
242+
endDate: dayjs(s.endDate).format(YEAR_MONTH_DAY_FORMAT),
245243
};
246244
}
247245

packages/web/src/components/DatePicker/DatePicker.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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;

packages/web/src/views/Forms/EventForm/DateTimeSection/DatePickers/DatePickers.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { DatePicker } from "@web/components/DatePicker/DatePicker";
1111
import { AlignItems } from "@web/components/Flex/styled";
1212
import { SetEventFormField } from "../../types";
13+
import { adjustEndDate } from "../form.datetime.util";
1314
import { StyledDateFlex } from "./styled";
1415

1516
const stopPropagation = (e: React.MouseEvent<HTMLDivElement>) => {
@@ -18,12 +19,14 @@ const stopPropagation = (e: React.MouseEvent<HTMLDivElement>) => {
1819

1920
interface Props {
2021
bgColor: string;
22+
displayEndDate: Date;
2123
inputColor?: string;
2224
isEndDatePickerOpen: boolean;
2325
isStartDatePickerOpen: boolean;
2426
selectedEndDate: Date;
2527
selectedStartDate: Date;
2628
onSetEventField: SetEventFormField;
29+
setDisplayEndDate: (value: Date) => void;
2730
setSelectedEndDate: (value: Date) => void;
2831
setSelectedStartDate: (value: Date) => void;
2932
setIsStartDatePickerOpen: (arg0: boolean) => void;
@@ -32,12 +35,14 @@ interface Props {
3235

3336
export const DatePickers: FC<Props> = ({
3437
bgColor,
38+
displayEndDate,
3539
inputColor,
3640
isEndDatePickerOpen,
3741
isStartDatePickerOpen,
3842
selectedEndDate,
3943
selectedStartDate,
4044
onSetEventField,
45+
setDisplayEndDate,
4146
setIsEndDatePickerOpen,
4247
setIsStartDatePickerOpen,
4348
setSelectedEndDate,
@@ -142,17 +147,23 @@ export const DatePickers: FC<Props> = ({
142147
};
143148

144149
const onSelectStartDate = (start: Date) => {
145-
setSelectedStartDate(start);
146150
setIsStartDatePickerOpen(false);
151+
setSelectedStartDate(start);
147152
adjustComplimentDateIfNeeded("start", start);
148-
onSetEventField("startDate", dayjs(start).format(MONTH_DAY_YEAR));
153+
154+
const newStartDate = dayjs(start).format(MONTH_DAY_YEAR);
155+
onSetEventField("startDate", newStartDate);
149156
};
150157

151158
const onSelectEndDate = (end: Date) => {
152-
setSelectedEndDate(end);
153159
setIsEndDatePickerOpen(false);
154160
adjustComplimentDateIfNeeded("end", end);
155-
onSetEventField("endDate", dayjs(end).format(MONTH_DAY_YEAR));
161+
162+
setDisplayEndDate(end);
163+
164+
const { datePickerDate, formattedEndDate } = adjustEndDate(end);
165+
setSelectedEndDate(datePickerDate);
166+
onSetEventField("endDate", formattedEndDate);
156167
};
157168

158169
return (
@@ -202,7 +213,7 @@ export const DatePickers: FC<Props> = ({
202213
}}
203214
onKeyDown={(e) => onPickerKeyDown("end", e)}
204215
onSelect={onSelectEndDate}
205-
selected={selectedEndDate}
216+
selected={displayEndDate}
206217
title="Pick End Date"
207218
view="grid"
208219
/>

packages/web/src/views/Forms/EventForm/DateTimeSection/DateTimeSection.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dayjs.extend(customParseFormat);
1414
export interface Props {
1515
bgColor: string;
1616
category: Categories_Event;
17+
displayEndDate: Date;
1718
event: Schema_Event;
1819
endTime: SelectOption<string>;
1920
inputColor?: string;
@@ -22,6 +23,7 @@ export interface Props {
2223
onSetEventField: SetEventFormField;
2324
selectedEndDate: Date;
2425
selectedStartDate: Date;
26+
setDisplayEndDate: (value: Date) => void;
2527
setEndTime: (value: SelectOption<string>) => void;
2628
setIsEndDatePickerOpen: (arg0: boolean) => void;
2729
setIsStartDatePickerOpen: (arg0: boolean) => void;
@@ -35,13 +37,15 @@ export interface Props {
3537
export const DateTimeSection: FC<Props> = ({
3638
bgColor,
3739
category,
40+
displayEndDate,
3841
event,
3942
inputColor,
4043
isEndDatePickerOpen,
4144
isStartDatePickerOpen,
4245
selectedEndDate,
4346
selectedStartDate,
4447
onSetEventField,
48+
setDisplayEndDate,
4549
setIsStartDatePickerOpen,
4650
setIsEndDatePickerOpen,
4751
setStartTime,
@@ -57,12 +61,14 @@ export const DateTimeSection: FC<Props> = ({
5761
{category === Categories_Event.ALLDAY && (
5862
<DatePickers
5963
bgColor={bgColor}
64+
displayEndDate={displayEndDate}
6065
inputColor={inputColor}
6166
isEndDatePickerOpen={isEndDatePickerOpen}
6267
isStartDatePickerOpen={isStartDatePickerOpen}
6368
selectedEndDate={selectedEndDate}
6469
selectedStartDate={selectedStartDate}
6570
onSetEventField={onSetEventField}
71+
setDisplayEndDate={setDisplayEndDate}
6672
setSelectedEndDate={setSelectedEndDate}
6773
setSelectedStartDate={setSelectedStartDate}
6874
setIsEndDatePickerOpen={setIsEndDatePickerOpen}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { adjustEndDate } from "./form.datetime.util";
2+
3+
describe("adjustEndDate", () => {
4+
it("should return the correct end date variations", () => {
5+
const end = new Date("2025-12-25");
6+
const { datePickerDate, formattedEndDate } = adjustEndDate(end);
7+
8+
expect(formattedEndDate).toEqual("2025-12-25");
9+
expect(datePickerDate).toEqual(new Date("2025-12-26"));
10+
});
11+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import dayjs from "dayjs";
2+
import { YEAR_MONTH_DAY_FORMAT } from "@core/constants/date.constants";
3+
import { getTimeOptionByValue } from "@web/common/utils/web.date.util";
4+
5+
export const adjustEndDate = (end: Date) => {
6+
// Given an all-day event that starts and ends on December 25,
7+
// the event form should show a start of "2025-12-25" and an end of "2025-12-25",
8+
// and the backend should store the start as "2025-12-25" and the end as"2025-12-26".
9+
// Adding one day to the end here helps us achieve that requirement.
10+
const endPlusOne = dayjs(end).add(1, "day");
11+
12+
const formattedEndDate = endPlusOne.format(YEAR_MONTH_DAY_FORMAT);
13+
const datePickerDate = endPlusOne.toDate();
14+
return { datePickerDate, formattedEndDate };
15+
};
16+
17+
export const getFormDates = (startDate: string, endDate: string) => {
18+
const start = dayjs(startDate);
19+
const startTime = getTimeOptionByValue(start);
20+
const _startDate = start.toDate();
21+
22+
const end = dayjs(endDate);
23+
const isOneDay = start.format(YEAR_MONTH_DAY_FORMAT) === endDate;
24+
const displayEndDate = isOneDay
25+
? start.format(YEAR_MONTH_DAY_FORMAT)
26+
: end.subtract(1, "day").format(YEAR_MONTH_DAY_FORMAT);
27+
const _endDate = isOneDay ? start.toDate() : end.toDate();
28+
const endTime = getTimeOptionByValue(end);
29+
30+
return {
31+
startDate: _startDate,
32+
startTime,
33+
endDate: _endDate,
34+
displayEndDate,
35+
endTime,
36+
};
37+
};

packages/web/src/views/Forms/EventForm/EventForm.tsx

Lines changed: 15 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,18 @@ import React, {
1010
import { Key } from "ts-key-enum";
1111
import { Trash } from "@phosphor-icons/react";
1212
import { Priorities } from "@core/constants/core.constants";
13-
import { Schema_Event } from "@core/types/event.types";
1413
import { ID_EVENT_FORM } from "@web/common/constants/web.constants";
1514
import {
1615
colorByPriority,
1716
hoverColorByPriority,
1817
} from "@web/common/styles/theme.util";
1918
import { SelectOption } from "@web/common/types/component.types";
2019
import { getCategory } from "@web/common/utils/event.util";
21-
import {
22-
getTimeOptionByValue,
23-
mapToBackend,
24-
} from "@web/common/utils/web.date.util";
20+
import { mapToBackend } from "@web/common/utils/web.date.util";
2521
import IconButton from "@web/components/IconButton/IconButton";
2622
import { StyledMigrateArrowInForm } from "@web/views/Calendar/components/Sidebar/SomedayTab/SomedayEvents/SomedayEventContainer/styled";
2723
import { DateTimeSection } from "./DateTimeSection/DateTimeSection";
24+
import { getFormDates } from "./DateTimeSection/form.datetime.util";
2825
import { PrioritySection } from "./PrioritySection";
2926
import { SaveSection } from "./SaveSection";
3027
import {
@@ -64,13 +61,14 @@ export const EventForm: React.FC<FormProps> = ({
6461
label: "12 AM",
6562
value: "12:00 AM",
6663
});
67-
const [selectedEndDate, setSelectedEndDate] = useState(new Date());
6864
const [selectedStartDate, setSelectedStartDate] = useState(new Date());
65+
const [selectedEndDate, setSelectedEndDate] = useState(new Date());
66+
const [displayEndDate, setDisplayEndDate] = useState(selectedStartDate);
6967

7068
const descriptionRef = useRef<HTMLTextAreaElement>(null);
7169

72-
/********
73-
* Effect
70+
/*********
71+
* Effects
7472
*********/
7573

7674
const keyDownHandler = useCallback(
@@ -100,46 +98,18 @@ export const EventForm: React.FC<FormProps> = ({
10098
}, []);
10199

102100
useEffect(() => {
103-
const getDefaultDateTimes = (event: Schema_Event) => {
104-
const start = event?.startDate ? dayjs(event.startDate) : dayjs();
105-
const startTime = getTimeOptionByValue(start);
106-
const startDate = start.toDate();
107-
108-
const { endDate, endTime } = getDefaultEndDateTimes(event);
109-
110-
return { startDate, startTime, endDate, endTime };
111-
};
112-
113-
const dt = getDefaultDateTimes(event);
114-
115101
setEvent(event || {});
102+
103+
const dt = getFormDates(event.startDate as string, event.endDate as string);
116104
setStartTime(dt.startTime);
117-
setEndTime(dt.endTime);
118105
setSelectedStartDate(dt.startDate);
106+
setDisplayEndDate(dayjs(dt.displayEndDate).toDate());
107+
setEndTime(dt.endTime);
119108
setSelectedEndDate(dt.endDate);
109+
120110
setIsFormOpen(true);
121111
}, [event, setEvent]);
122112

123-
/***********
124-
* Helpers
125-
**********/
126-
127-
const getDefaultEndDateTimes = (event: Schema_Event) => {
128-
const end = event?.endDate ? dayjs(event.endDate) : dayjs();
129-
const endTime = getTimeOptionByValue(end);
130-
131-
if (event.isAllDay) {
132-
const isMultiDay = !dayjs(event.startDate).isSame(end);
133-
if (isMultiDay) {
134-
const userFriendlyEnd = end.add(-1, "day").toDate();
135-
136-
return { endDate: userFriendlyEnd, endTime };
137-
}
138-
}
139-
140-
return { endDate: end.toDate(), endTime };
141-
};
142-
143113
/***********
144114
* Handlers
145115
**********/
@@ -192,17 +162,16 @@ export const EventForm: React.FC<FormProps> = ({
192162
};
193163

194164
onSubmit(finalEvent);
195-
196165
onClose();
197166
};
198167

199168
const onSetEventField: SetEventFormField = (field, value) => {
200-
const newEvent = { ...event, [field]: value };
169+
const oldEvent = { ...event };
170+
const newEvent = { ...oldEvent, [field]: value };
201171
setEvent(newEvent);
202172
};
203173

204174
const onFormKeyDown: KeyboardEventHandler<HTMLFormElement> = (e) => {
205-
console.log("onFormKeyDown", e.key);
206175
if (e.key === Key.Backspace || e.key == Key.Delete) {
207176
if (isDraft) {
208177
onClose();
@@ -289,6 +258,7 @@ export const EventForm: React.FC<FormProps> = ({
289258

290259
<DateTimeSection
291260
bgColor={priorityColor}
261+
displayEndDate={displayEndDate}
292262
event={event}
293263
category={category}
294264
endTime={endTime}
@@ -303,6 +273,7 @@ export const EventForm: React.FC<FormProps> = ({
303273
setSelectedStartDate={setSelectedStartDate}
304274
setStartTime={setStartTime}
305275
startTime={startTime}
276+
setDisplayEndDate={setDisplayEndDate}
306277
setIsEndDatePickerOpen={setIsEndDatePickerOpen}
307278
setIsStartDatePickerOpen={setIsStartDatePickerOpen}
308279
setEvent={setEvent}

0 commit comments

Comments
 (0)