Skip to content

Commit 685a9d5

Browse files
committed
🐛 fix(recurring-events): allow someday recurring instance creation
1 parent 1e022e3 commit 685a9d5

File tree

6 files changed

+97
-48
lines changed

6 files changed

+97
-48
lines changed

packages/web/src/ducks/events/event.types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Priorities } from "@core/constants/core.constants";
44
import {
55
RecurringEventUpdateScope,
66
Schema_Event,
7+
WithCompassId,
78
} from "@core/types/event.types";
89
import { SliceStateContext } from "@web/common/store/helpers";
910
import {
@@ -68,7 +69,7 @@ export interface Entities_Event {
6869
}
6970

7071
export interface Payload_ConvertEvent {
71-
event: Schema_WebEvent;
72+
event: WithCompassId<Omit<Schema_WebEvent, "_id">>;
7273
}
7374

7475
interface Payload_DeleteEvent {

packages/web/src/ducks/events/sagas/event.sagas.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { normalize } from "normalizr";
22
import { call, put, select } from "@redux-saga/core/effects";
33
import { ID_OPTIMISTIC_PREFIX } from "@core/constants/core.constants";
44
import { YEAR_MONTH_DAY_FORMAT } from "@core/constants/date.constants";
5-
import { Params_Events, Schema_Event } from "@core/types/event.types";
5+
import {
6+
Params_Events,
7+
RecurringEventUpdateScope,
8+
Schema_Event,
9+
} from "@core/types/event.types";
610
import dayjs from "@core/util/date/dayjs";
711
import { Response_HttpPaginatedSuccess } from "@web/common/types/api.types";
812
import { Payload_NormalizedAsyncAction } from "@web/common/types/entity.types";
@@ -49,11 +53,14 @@ export function* convertCalendarToSomedayEvent({
4953

5054
try {
5155
const gridEvent = yield* _assembleGridEvent(payload.event);
56+
const isInstance = typeof gridEvent.recurrence?.eventId === "string";
57+
const { ALL_EVENTS, THIS_EVENT } = RecurringEventUpdateScope;
58+
const applyTo = isInstance ? ALL_EVENTS : THIS_EVENT;
5259

5360
// optimistic event will have an entirely new ID that will not match that eventually saved
5461
optimisticEvent = yield* _createOptimisticGridEvent(gridEvent, true);
5562

56-
yield* _editEvent(gridEvent);
63+
yield* _editEvent(gridEvent, { applyTo });
5764
yield* replaceOptimisticId(optimisticEvent._id, false);
5865
yield put(editEventSlice.actions.success());
5966
} catch (error) {

packages/web/src/ducks/events/sagas/someday.sagas.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export function* convertSomedayToCalendarEvent({
3333
try {
3434
const gridEvent = yield* _assembleGridEvent(payload.event);
3535

36+
delete gridEvent.recurrence;
37+
3638
optimisticEvent = yield* _createOptimisticGridEvent(gridEvent);
3739

3840
yield* _editEvent(gridEvent);

packages/web/src/views/Calendar/components/Draft/hooks/actions/useDraftActions.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
Recurrence,
1313
RecurringEventUpdateScope,
1414
Schema_Event,
15+
WithCompassId,
1516
} from "@core/types/event.types";
1617
import { devAlert } from "@core/util/app.util";
1718
import dayjs, { Dayjs } from "@core/util/date/dayjs";
@@ -161,7 +162,7 @@ export const useDraftActions = (
161162
reset();
162163

163164
if (reduxDraft || reduxDraftType) {
164-
dispatch(draftSlice.actions.discard());
165+
dispatch(draftSlice.actions.discard(undefined));
165166
}
166167
}, [dispatch, reduxDraft, reduxDraftType, reset]);
167168

@@ -193,26 +194,37 @@ export const useDraftActions = (
193194
return;
194195
}
195196

196-
dispatch(
197-
getWeekEventsSlice.actions.convert({
198-
event: {
199-
...draft,
200-
_id: draft!._id!,
201-
user: draft!.user!,
202-
isAllDay: false,
203-
isSomeday: true,
204-
startDate: start,
205-
endDate: end,
206-
origin: draft!.origin!,
207-
priority: draft?.priority ?? Priorities.UNASSIGNED,
208-
order: somedayWeekCount,
209-
},
210-
}),
211-
);
197+
const event: WithCompassId<Omit<Schema_WebEvent, "_id">> = {
198+
...draft,
199+
_id: draft!._id!,
200+
user: draft!.user!,
201+
isAllDay: false,
202+
isSomeday: true,
203+
startDate: start,
204+
endDate: end,
205+
origin: draft!.origin!,
206+
priority: draft?.priority ?? Priorities.UNASSIGNED,
207+
order: somedayWeekCount,
208+
};
209+
210+
if (isRecurrence()) {
211+
event.recurrence = {
212+
...event.recurrence,
213+
rule: event.recurrence?.rule?.map((rule) => {
214+
const isRRule = rule.startsWith("RRULE:");
215+
216+
if (!isRRule) return rule;
217+
218+
return rule.replace(/FREQ=\w+;/, "FREQ=WEEKLY;");
219+
}) as string[],
220+
};
221+
}
222+
223+
dispatch(getWeekEventsSlice.actions.convert({ event }));
212224

213225
discard();
214226
},
215-
[discard, dispatch, draft, isAtWeeklyLimit, somedayWeekCount],
227+
[discard, dispatch, draft, isAtWeeklyLimit, somedayWeekCount, isRecurrence],
216228
);
217229

218230
const openForm = useCallback(() => {

packages/web/src/views/Forms/EventForm/DateControlsSection/RecurrenceSection/useRecurrence/useRecurrence.test.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
import { act } from "react";
22
import { Frequency } from "rrule";
33
import { renderHook } from "@testing-library/react";
4-
import { Priorities } from "@core/constants/core.constants";
5-
import { Schema_Event } from "@core/types/event.types";
4+
import { Origin, Priorities } from "@core/constants/core.constants";
5+
import { Schema_GridEvent } from "@web/common/types/web.event.types";
6+
import { assembleGridEvent } from "@web/common/utils/event/event.util";
67
import { useRecurrence } from "./useRecurrence";
78

89
describe("useRecurrence hook", () => {
9-
const baseEvent = (): Schema_Event => ({
10-
_id: "1",
11-
title: "Test Event",
12-
description: "desc",
13-
startDate: new Date().toISOString(),
14-
endDate: new Date(Date.now() + 3600000).toISOString(),
15-
priority: Priorities.UNASSIGNED,
16-
isSomeday: false,
17-
user: "user1",
18-
});
10+
const baseEvent = (): Schema_GridEvent =>
11+
assembleGridEvent({
12+
_id: "1",
13+
title: "Test Event",
14+
description: "desc",
15+
startDate: new Date().toISOString(),
16+
endDate: new Date(Date.now() + 3600000).toISOString(),
17+
priority: Priorities.UNASSIGNED,
18+
origin: Origin.COMPASS,
19+
isSomeday: false,
20+
user: "user1",
21+
});
1922

2023
it("initializes with no recurrence", () => {
2124
const event = baseEvent();

packages/web/src/views/Forms/EventForm/DateControlsSection/RecurrenceSection/useRecurrence/useRecurrence.ts

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@ import {
99
useState,
1010
} from "react";
1111
import { Frequency, Options, RRule, Weekday } from "rrule";
12-
import { Schema_Event } from "@core/types/event.types";
1312
import { CompassEventRRule } from "@core/util/event/compass.event.rrule";
1413
import { parseCompassEventDate } from "@core/util/event/event.util";
14+
import {
15+
Schema_GridEvent,
16+
Schema_WebEvent,
17+
} from "@web/common/types/web.event.types";
1518
import {
1619
FrequencyValues,
1720
WEEKDAYS,
1821
WEEKDAY_RRULE_MAP,
19-
} from "../constants/recurrence.constants";
20-
import { toWeekDays } from "../util/recurrence.util";
22+
} from "@web/views/Forms/EventForm/DateControlsSection/RecurrenceSection/constants/recurrence.constants";
23+
import { toWeekDays } from "@web/views/Forms/EventForm/DateControlsSection/RecurrenceSection/util/recurrence.util";
2124

2225
const WEEKDAY_LABELS_MAP: Record<keyof typeof WEEKDAY_RRULE_MAP, string> = {
2326
sunday: RRule.SU.toString(),
@@ -57,10 +60,21 @@ const WEEKDAY_MAP: Record<
5760
);
5861

5962
export const useRecurrence = (
60-
event: Pick<Schema_Event, "startDate" | "endDate" | "recurrence">,
61-
{ setEvent }: { setEvent: Dispatch<SetStateAction<Schema_Event | null>> },
63+
event: Partial<
64+
Pick<
65+
Schema_GridEvent | Schema_WebEvent,
66+
"startDate" | "endDate" | "recurrence" | "isSomeday"
67+
>
68+
> | null,
69+
{
70+
setEvent,
71+
}: {
72+
setEvent: Dispatch<
73+
SetStateAction<Schema_GridEvent | Schema_WebEvent | null>
74+
>;
75+
},
6276
) => {
63-
const { recurrence, endDate: _endDate } = event;
77+
const { recurrence, endDate: _endDate, isSomeday } = event ?? {};
6478
const startDate = event?.startDate ?? dayjs().toRFC3339OffsetString();
6579
const endDate = _endDate ?? dayjs().add(1, "hour").toRFC3339OffsetString();
6680
const _startDate = parseCompassEventDate(startDate);
@@ -81,13 +95,23 @@ export const useRecurrence = (
8195
};
8296
}
8397

84-
return new CompassEventRRule({
85-
_id: new ObjectId(), // we do not need the event's actual id here
86-
startDate: startDate!,
87-
endDate: endDate!,
88-
recurrence: { rule: recurrence?.rule as string[] },
89-
});
90-
}, [_startDate, startDate, endDate, hasRecurrence, recurrence?.rule]);
98+
return new CompassEventRRule(
99+
{
100+
_id: new ObjectId(), // we do not need the event's actual id here
101+
startDate: startDate!,
102+
endDate: endDate!,
103+
recurrence: { rule: recurrence?.rule as string[] },
104+
},
105+
isSomeday ? { count: 6 } : {}, // default to 6 occurrences for someday events
106+
);
107+
}, [
108+
_startDate,
109+
startDate,
110+
endDate,
111+
hasRecurrence,
112+
recurrence?.rule,
113+
isSomeday,
114+
]);
91115

92116
const defaultWeekDay: typeof WEEKDAYS = useMemo(
93117
() =>
@@ -141,7 +165,7 @@ export const useRecurrence = (
141165
const rule = useMemo(() => JSON.stringify(rrule.toRecurrence()), [rrule]);
142166

143167
const toggleRecurrence = useCallback(() => {
144-
setEvent((gridEvent): Schema_Event | null => {
168+
setEvent((gridEvent): Schema_GridEvent | Schema_WebEvent | null => {
145169
if (!gridEvent) return gridEvent;
146170

147171
const { recurrence, ...event } = gridEvent;
@@ -167,7 +191,7 @@ export const useRecurrence = (
167191
useEffect(() => {
168192
if (!hasRecurrence) return;
169193

170-
setEvent((gridEvent): Schema_Event | null => {
194+
setEvent((gridEvent): Schema_GridEvent | Schema_WebEvent | null => {
171195
if (!gridEvent) return gridEvent;
172196

173197
return {

0 commit comments

Comments
 (0)