Skip to content

Commit ebb6a0d

Browse files
authored
♻️ Refactor: Draft event creation and shortcut handling (#277)
- Extract draft event creation logic into separate utility functions - Add `createTimedDraft` and `createSomedayDraft` for consistent draft event creation - Improve type safety and reduce code duplication in draft event shortcuts - Update event.util.ts to handle multiple case conditions more cleanly - Simplify draft creation in CmdPalette and useShortcuts hooks
1 parent ba8435a commit ebb6a0d

File tree

6 files changed

+140
-98
lines changed

6 files changed

+140
-98
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Categories_Event } from "@core/types/event.types";
2+
import { assembleDefaultEvent } from "../event.util";
3+
4+
jest.mock("@web/auth/auth.util", () => ({
5+
getUserId: jest.fn().mockResolvedValue("mock-user-id"),
6+
}));
7+
describe("assembleDefaultEvent", () => {
8+
it("should include dates for someday event when provided", async () => {
9+
const startDate = "2024-01-01";
10+
const endDate = "2024-01-07";
11+
const eventWithDates = await assembleDefaultEvent(
12+
Categories_Event.SOMEDAY_WEEK,
13+
startDate,
14+
endDate,
15+
);
16+
17+
expect(eventWithDates).toHaveProperty("startDate", startDate);
18+
expect(eventWithDates).toHaveProperty("endDate", endDate);
19+
});
20+
it("dates should be empty for someday event when not provided", async () => {
21+
const eventWithoutDates = await assembleDefaultEvent(
22+
Categories_Event.SOMEDAY_WEEK,
23+
);
24+
25+
expect(eventWithoutDates).toHaveProperty("startDate", "");
26+
expect(eventWithoutDates).toHaveProperty("endDate", "");
27+
});
28+
});

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import dayjs, { Dayjs } from "dayjs";
22
import { MouseEvent } from "react";
3+
import { Dispatch } from "redux";
34
import { Categories_Event } from "@core/types/event.types";
45
import {
56
ID_GRID_EVENTS_ALLDAY,
67
ID_GRID_EVENTS_TIMED,
7-
ID_SIDEBAR,
88
} from "@web/common/constants/web.constants";
99
import { Schema_GridEvent } from "@web/common/types/web.event.types";
1010
import { roundToNext } from "@web/common/utils";
1111
import { assembleDefaultEvent } from "@web/common/utils/event.util";
1212
import { getElemById, getX } from "@web/common/utils/grid.util";
13+
import { draftSlice } from "@web/ducks/events/slices/draft.slice";
14+
import { Activity_DraftEvent } from "@web/ducks/events/slices/draft.slice.types";
1315
import { DateCalcs } from "@web/views/Calendar/hooks/grid/useDateCalcs";
1416
import {
1517
DRAFT_DURATION_MIN,
@@ -54,7 +56,30 @@ export const assembleTimedDraft = async (
5456
return event;
5557
};
5658

57-
export const getDraftTimes = (isCurrentWeek: boolean, startOfWeek: Dayjs) => {
59+
export const createTimedDraft = async (
60+
isCurrentWeek: boolean,
61+
startOfView: Dayjs,
62+
activity: Activity_DraftEvent,
63+
dispatch: Dispatch,
64+
) => {
65+
const { startDate, endDate } = getDraftTimes(isCurrentWeek, startOfView);
66+
67+
const event = (await assembleDefaultEvent(
68+
Categories_Event.TIMED,
69+
startDate,
70+
endDate,
71+
)) as Schema_GridEvent;
72+
73+
dispatch(
74+
draftSlice.actions.start({
75+
activity,
76+
eventType: Categories_Event.TIMED,
77+
event,
78+
}),
79+
);
80+
};
81+
82+
const getDraftTimes = (isCurrentWeek: boolean, startOfWeek: Dayjs) => {
5883
const currentMinute = dayjs().minute();
5984
const nextMinuteInterval = roundToNext(currentMinute, GRID_TIME_STEP);
6085

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Dayjs } from "dayjs";
2+
import { Dispatch } from "redux";
3+
import { YEAR_MONTH_FORMAT } from "@core/constants/date.constants";
4+
import { Categories_Event } from "@core/types/event.types";
5+
import { draftSlice } from "@web/ducks/events/slices/draft.slice";
6+
import { Activity_DraftEvent } from "@web/ducks/events/slices/draft.slice.types";
7+
import { assembleDefaultEvent } from "../event.util";
8+
9+
export const createSomedayDraft = async (
10+
category: Categories_Event.SOMEDAY_WEEK | Categories_Event.SOMEDAY_MONTH,
11+
startOfView: Dayjs,
12+
endOfView: Dayjs,
13+
activity: Activity_DraftEvent,
14+
dispatch: Dispatch,
15+
) => {
16+
const startDate = startOfView.format(YEAR_MONTH_FORMAT);
17+
const endDate = endOfView.format(YEAR_MONTH_FORMAT);
18+
19+
const event = await assembleDefaultEvent(category, startDate, endDate);
20+
21+
dispatch(
22+
draftSlice.actions.start({
23+
activity,
24+
eventType: category,
25+
event,
26+
}),
27+
);
28+
};

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,14 @@ export const assembleDefaultEvent = async (
6767
};
6868
return defaultAllday;
6969
}
70-
case Categories_Event.SOMEDAY_WEEK || Categories_Event.SOMEDAY_MONTH: {
70+
case Categories_Event.SOMEDAY_WEEK:
71+
case Categories_Event.SOMEDAY_MONTH: {
7172
const defaultSomeday: Schema_Event = {
7273
...baseEvent,
7374
isAllDay: false,
7475
isSomeday: true,
7576
origin: Origin.COMPASS,
77+
...(startDate && endDate ? { startDate, endDate } : {}),
7678
};
7779
return defaultSomeday;
7880
}
@@ -168,6 +170,11 @@ export const getCategory = (event: Schema_Event) => {
168170
return Categories_Event.TIMED;
169171
};
170172

173+
export const getCalendarEventIdFromElement = (element: HTMLElement) => {
174+
const eventElement = element.closest(`[${DATA_EVENT_ELEMENT_ID}]`);
175+
return eventElement ? eventElement.getAttribute(DATA_EVENT_ELEMENT_ID) : null;
176+
};
177+
171178
export const getMonthListLabel = (start: Dayjs) => {
172179
return start.format("MMMM");
173180
};
@@ -284,8 +291,3 @@ const _assembleBaseEvent = (
284291

285292
return baseEvent;
286293
};
287-
288-
export const getCalendarEventIdFromElement = (element: HTMLElement) => {
289-
const eventElement = element.closest(`[${DATA_EVENT_ELEMENT_ID}]`);
290-
return eventElement ? eventElement.getAttribute(DATA_EVENT_ELEMENT_ID) : null;
291-
};

packages/web/src/views/Calendar/hooks/shortcuts/useShortcuts.ts

Lines changed: 24 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,14 @@ import { useHotkeys } from "react-hotkeys-hook";
44
import { useNavigate } from "react-router-dom";
55
import { Key } from "ts-keycode-enum";
66
import {
7-
Priorities,
87
SOMEDAY_MONTH_LIMIT_MSG,
98
SOMEDAY_WEEK_LIMIT_MSG,
109
} from "@core/constants/core.constants";
11-
import { YEAR_MONTH_FORMAT } from "@core/constants/date.constants";
1210
import { Categories_Event } from "@core/types/event.types";
1311
import { ROOT_ROUTES } from "@web/common/constants/routes";
14-
import { Schema_GridEvent } from "@web/common/types/web.event.types";
1512
import { isEventFormOpen } from "@web/common/utils";
16-
import { getDraftTimes } from "@web/common/utils/draft/draft.util";
17-
import { assembleDefaultEvent } from "@web/common/utils/event.util";
13+
import { createTimedDraft } from "@web/common/utils/draft/draft.util";
14+
import { createSomedayDraft } from "@web/common/utils/draft/someday.draft.util";
1815
import {
1916
selectIsAtMonthlyLimit,
2017
selectIsAtWeeklyLimit,
@@ -61,63 +58,29 @@ export const useShortcuts = ({
6158
});
6259

6360
useEffect(() => {
64-
const _createSomedayDraft = async (type: "week" | "month") => {
65-
if (type === "week" && isAtWeeklyLimit) {
61+
const _createSomedayDraft = async (
62+
category: Categories_Event.SOMEDAY_WEEK | Categories_Event.SOMEDAY_MONTH,
63+
) => {
64+
if (category === Categories_Event.SOMEDAY_WEEK && isAtWeeklyLimit) {
6665
alert(SOMEDAY_WEEK_LIMIT_MSG);
6766
return;
6867
}
69-
if (type === "month" && isAtMonthlyLimit) {
68+
if (category === Categories_Event.SOMEDAY_MONTH && isAtMonthlyLimit) {
7069
alert(SOMEDAY_MONTH_LIMIT_MSG);
7170
return;
7271
}
7372

73+
await createSomedayDraft(
74+
category,
75+
startOfView,
76+
endOfView,
77+
"createShortcut",
78+
dispatch,
79+
);
80+
7481
if (tab !== "tasks") {
7582
dispatch(viewSlice.actions.updateSidebarTab("tasks"));
7683
}
77-
78-
const eventType =
79-
type === "week"
80-
? Categories_Event.SOMEDAY_WEEK
81-
: Categories_Event.SOMEDAY_MONTH;
82-
83-
const somedayDefault = await assembleDefaultEvent(
84-
Categories_Event.SOMEDAY_WEEK,
85-
);
86-
dispatch(
87-
draftSlice.actions.start({
88-
activity: "createShortcut",
89-
eventType,
90-
event: {
91-
...somedayDefault,
92-
startDate: startOfView.format(YEAR_MONTH_FORMAT),
93-
endDate: endOfView.format(YEAR_MONTH_FORMAT),
94-
},
95-
}),
96-
);
97-
};
98-
99-
const _createTimedDraft = () => {
100-
const { startDate, endDate } = getDraftTimes(isCurrentWeek, startOfView);
101-
102-
const event: Schema_GridEvent = {
103-
startDate,
104-
endDate,
105-
priority: Priorities.UNASSIGNED,
106-
isAllDay: false,
107-
position: {
108-
isOverlapping: false,
109-
widthMultiplier: 1,
110-
horizontalOrder: 1,
111-
},
112-
};
113-
114-
dispatch(
115-
draftSlice.actions.start({
116-
activity: "createShortcut",
117-
eventType: Categories_Event.TIMED,
118-
event,
119-
}),
120-
);
12184
};
12285

12386
const _discardDraft = () => {
@@ -137,7 +100,13 @@ export const useShortcuts = ({
137100

138101
const handlersByKey = {
139102
[Key.OpenBracket]: () => dispatch(viewSlice.actions.toggleSidebar()),
140-
[Key.C]: () => _createTimedDraft(),
103+
[Key.C]: () =>
104+
createTimedDraft(
105+
isCurrentWeek,
106+
startOfView,
107+
"createShortcut",
108+
dispatch,
109+
),
141110
[Key.T]: () => {
142111
scrollUtil.scrollToNow();
143112
_discardDraft();
@@ -151,8 +120,8 @@ export const useShortcuts = ({
151120
_discardDraft();
152121
util.incrementWeek();
153122
},
154-
[Key.M]: () => _createSomedayDraft("month"),
155-
[Key.W]: () => _createSomedayDraft("week"),
123+
[Key.M]: () => _createSomedayDraft(Categories_Event.SOMEDAY_MONTH),
124+
[Key.W]: () => _createSomedayDraft(Categories_Event.SOMEDAY_WEEK),
156125
[Key.Z]: () => {
157126
navigate(ROOT_ROUTES.LOGOUT);
158127
},

packages/web/src/views/CmdPalette/CmdPalette.tsx

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@ import {
1212
} from "@core/constants/core.constants";
1313
import { Categories_Event } from "@core/types/event.types";
1414
import { ROOT_ROUTES } from "@web/common/constants/routes";
15-
import { Schema_GridEvent } from "@web/common/types/web.event.types";
1615
import { isEventFormOpen } from "@web/common/utils";
17-
import { getDraftTimes } from "@web/common/utils/draft/draft.util";
18-
import { assembleDefaultEvent } from "@web/common/utils/event.util";
16+
import { createTimedDraft } from "@web/common/utils/draft/draft.util";
17+
import { createSomedayDraft } from "@web/common/utils/draft/someday.draft.util";
1918
import {
2019
selectIsAtMonthlyLimit,
2120
selectIsAtWeeklyLimit,
@@ -30,6 +29,7 @@ const CmdPalette = ({
3029
today,
3130
isCurrentWeek,
3231
startOfView,
32+
endOfView,
3333
util,
3434
scrollUtil,
3535
}: ShortcutProps) => {
@@ -50,42 +50,24 @@ const CmdPalette = ({
5050

5151
useHandleOpenCommandPalette(setOpen);
5252

53-
const _createSomedayDraft = async (type: "week" | "month") => {
54-
if (type === "week" && isAtWeeklyLimit) {
53+
const handleCreateSomedayDraft = async (
54+
category: Categories_Event.SOMEDAY_WEEK | Categories_Event.SOMEDAY_MONTH,
55+
) => {
56+
if (category === Categories_Event.SOMEDAY_WEEK && isAtWeeklyLimit) {
5557
alert(SOMEDAY_WEEK_LIMIT_MSG);
5658
return;
5759
}
58-
if (type === "month" && isAtMonthlyLimit) {
60+
if (category === Categories_Event.SOMEDAY_MONTH && isAtMonthlyLimit) {
5961
alert(SOMEDAY_MONTH_LIMIT_MSG);
6062
return;
6163
}
6264

63-
const eventType =
64-
type === "week"
65-
? Categories_Event.SOMEDAY_WEEK
66-
: Categories_Event.SOMEDAY_MONTH;
67-
68-
dispatch(
69-
draftSlice.actions.start({
70-
activity: "createShortcut",
71-
eventType,
72-
}),
73-
);
74-
};
75-
76-
const _createTimedDraft = async () => {
77-
const { startDate, endDate } = getDraftTimes(isCurrentWeek, startOfView);
78-
const event = (await assembleDefaultEvent(
79-
Categories_Event.TIMED,
80-
startDate,
81-
endDate,
82-
)) as Schema_GridEvent;
83-
dispatch(
84-
draftSlice.actions.start({
85-
activity: "createShortcut",
86-
eventType: Categories_Event.TIMED,
87-
event,
88-
}),
65+
await createSomedayDraft(
66+
category,
67+
startOfView,
68+
endOfView,
69+
"createShortcut",
70+
dispatch,
8971
);
9072
};
9173

@@ -105,19 +87,27 @@ const CmdPalette = ({
10587
id: "create-event",
10688
children: "Create Event [c]",
10789
icon: "PlusIcon",
108-
onClick: () => _createTimedDraft(),
90+
onClick: () =>
91+
createTimedDraft(
92+
isCurrentWeek,
93+
startOfView,
94+
"createShortcut",
95+
dispatch,
96+
),
10997
},
11098
{
11199
id: "create-someday-week-event",
112100
children: "Create Week Event [w]",
113101
icon: "PlusIcon",
114-
onClick: () => _createSomedayDraft("week"),
102+
onClick: () =>
103+
handleCreateSomedayDraft(Categories_Event.SOMEDAY_WEEK),
115104
},
116105
{
117106
id: "create-someday-month-event",
118107
children: "Create Month Event [m]",
119108
icon: "PlusIcon",
120-
onClick: () => _createSomedayDraft("month"),
109+
onClick: () =>
110+
handleCreateSomedayDraft(Categories_Event.SOMEDAY_MONTH),
121111
},
122112
{
123113
id: "today",

0 commit comments

Comments
 (0)