Skip to content

Commit 5a2cc7e

Browse files
committed
✨ feat(edit-recurrence): add recurring event update scope dialog
1 parent efee38a commit 5a2cc7e

File tree

10 files changed

+505
-245
lines changed

10 files changed

+505
-245
lines changed

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

Lines changed: 11 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,22 @@
1-
import dayjs, { Dayjs } from "dayjs";
2-
import isBetween from "dayjs/plugin/isBetween";
3-
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
4-
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
51
import { v4 as uuidv4 } from "uuid";
62
import { DropResult } from "@hello-pangea/dnd";
73
import { Origin, Priorities } from "@core/constants/core.constants";
84
import { YEAR_MONTH_DAY_COMPACT_FORMAT } from "@core/constants/date.constants";
95
import { Status } from "@core/errors/status.codes";
106
import { Categories_Event, Schema_Event } from "@core/types/event.types";
7+
import dayjs, { Dayjs } from "@core/util/date/dayjs";
118
import { validateEvent } from "@core/validators/event.validator";
129
import { getUserId } from "@web/auth/auth.util";
13-
import { PartialMouseEvent } from "@web/common/types/util.types";
14-
import { validateSomedayEvent } from "@web/common/validators/someday.event.validator";
1510
import {
1611
DATA_EVENT_ELEMENT_ID,
1712
ID_OPTIMISTIC_PREFIX,
18-
} from "../constants/web.constants";
13+
} from "@web/common/constants/web.constants";
14+
import { PartialMouseEvent } from "@web/common/types/util.types";
1915
import {
2016
Schema_GridEvent,
2117
Schema_OptimisticEvent,
22-
} from "../types/web.event.types";
23-
24-
dayjs.extend(isSameOrAfter);
25-
dayjs.extend(isSameOrBefore);
26-
dayjs.extend(isBetween);
27-
28-
export const assembleBaseEvent = (
29-
userId: string,
30-
event: Partial<Schema_Event>,
31-
): Schema_Event => {
32-
const baseEvent = {
33-
_id: event._id,
34-
title: event.title || "",
35-
description: event.description || "",
36-
startDate: event.startDate,
37-
endDate: event.endDate,
38-
user: userId,
39-
isAllDay: event.isAllDay || false,
40-
isSomeday: event.isSomeday || false,
41-
origin: event.origin || Origin.COMPASS,
42-
priority: event.priority || Priorities.UNASSIGNED,
43-
};
44-
45-
return baseEvent;
46-
};
18+
} from "@web/common/types/web.event.types";
19+
import { validateSomedayEvent } from "@web/common/validators/someday.event.validator";
4720

4821
const gridEventDefaultPosition = {
4922
isOverlapping: false,
@@ -55,7 +28,7 @@ const gridEventDefaultPosition = {
5528
};
5629

5730
export const assembleDefaultEvent = async (
58-
draftType: Categories_Event,
31+
draftType?: Categories_Event | null,
5932
startDate?: string,
6033
endDate?: string,
6134
): Promise<Schema_Event | Schema_GridEvent> => {
@@ -192,7 +165,7 @@ export const isOptimisticEvent = (event: Schema_Event) => {
192165

193166
export const prepEvtAfterDraftDrop = (
194167
category: Categories_Event,
195-
dropItem: DropResult,
168+
dropItem: DropResult & Schema_Event,
196169
dates: { startDate: string; endDate: string },
197170
) => {
198171
const baseEvent = assembleDefaultEvent(category);
@@ -260,10 +233,10 @@ const _assembleBaseEvent = (
260233
startDate: event.startDate,
261234
endDate: event.endDate,
262235
user: userId,
263-
isAllDay: event.isAllDay || false,
264-
isSomeday: event.isSomeday || false,
265-
origin: event.origin || Origin.COMPASS,
266-
priority: event.priority || Priorities.UNASSIGNED,
236+
isAllDay: event.isAllDay ?? false,
237+
isSomeday: event.isSomeday ?? false,
238+
origin: event.origin ?? Origin.COMPASS,
239+
priority: event.priority ?? Priorities.UNASSIGNED,
267240
};
268241

269242
return baseEvent;

packages/web/src/ducks/events/slices/event.slice.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const deleteEventSlice = createAsyncSlice<{ _id: string }>({
3333

3434
export const editEventSlice = createAsyncSlice<Payload_EditEvent>({
3535
name: "editEvent",
36-
initialState: {},
36+
initialState: {} as unknown as undefined,
3737
reducers: {
3838
migrate: () => {},
3939
},

packages/web/src/views/Calendar/components/Draft/grid/GridDraft.tsx

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
import React, { FC, MouseEvent } from "react";
1+
import React, { FC, MouseEvent, useCallback, useMemo, useState } from "react";
22
import { FloatingFocusManager } from "@floating-ui/react";
33
import { YEAR_MONTH_DAY_FORMAT } from "@core/constants/date.constants";
4-
import { Categories_Event } from "@core/types/event.types";
4+
import {
5+
Categories_Event,
6+
RecurringEventUpdateScope,
7+
} from "@core/types/event.types";
58
import { PartialMouseEvent } from "@web/common/types/util.types";
69
import { Schema_GridEvent } from "@web/common/types/web.event.types";
710
import { getEventDragOffset } from "@web/common/utils/event.util";
11+
import { useDraftContext } from "@web/views/Calendar/components/Draft/context/useDraftContext";
12+
import { GridEvent } from "@web/views/Calendar/components/Event/Grid";
813
import { useGridEventMouseDown } from "@web/views/Calendar/hooks/grid/useGridEventMouseDown";
914
import { Measurements_Grid } from "@web/views/Calendar/hooks/grid/useGridLayout";
1015
import { WeekProps } from "@web/views/Calendar/hooks/useWeek";
1116
import { EventForm } from "@web/views/Forms/EventForm/EventForm";
17+
import { RecurringEventUpdateScopeDialog } from "@web/views/Forms/EventForm/RecurringEventUpdateScopeDialog";
1218
import { StyledFloatContainer } from "@web/views/Forms/SomedayEventForm/styled";
13-
import { GridEvent } from "../../Event/Grid";
14-
import { useDraftContext } from "../context/useDraftContext";
1519

1620
interface Props {
1721
draft: Schema_GridEvent;
@@ -23,8 +27,9 @@ interface Props {
2327

2428
export const GridDraft: FC<Props> = ({ measurements, weekProps }) => {
2529
const { actions, setters, state } = useDraftContext();
26-
const { discard, deleteEvent, duplicateEvent, submit, startDragging } =
27-
actions;
30+
const { discard, deleteEvent, duplicateEvent, submit } = actions;
31+
const { startDragging, isRecurrenceChanged } = actions;
32+
const { isInstance, isRecurrence, isEventDirty } = actions;
2833
const { setDraft, setDateBeingChanged, setIsResizing } = setters;
2934
const { draft, isDragging, formProps, isFormOpen, isResizing } = state;
3035
const { context, getReferenceProps, getFloatingProps, x, y, refs, strategy } =
@@ -55,6 +60,46 @@ export const GridDraft: FC<Props> = ({ measurements, weekProps }) => {
5560
startDragging();
5661
};
5762

63+
const [
64+
isRecurrenceUpdateScopeDialogOpen,
65+
setRecurrenceUpdateScopeDialogOpen,
66+
] = useState<boolean>(false);
67+
68+
const [finalDraft, setFinalDraft] = useState<Schema_GridEvent | null>(null);
69+
70+
const onUpdateScopeChange = useCallback(
71+
(applyTo: RecurringEventUpdateScope) => {
72+
if (finalDraft) {
73+
submit(finalDraft, applyTo);
74+
setFinalDraft(null);
75+
discard();
76+
}
77+
},
78+
[finalDraft, submit, setFinalDraft, discard],
79+
);
80+
81+
const onSubmit = useCallback(
82+
async (_draft: Schema_GridEvent) => {
83+
if (isEventDirty(_draft) && isRecurrence(_draft)) {
84+
setFinalDraft(_draft);
85+
86+
return setRecurrenceUpdateScopeDialogOpen(true);
87+
}
88+
89+
submit(_draft);
90+
discard();
91+
},
92+
[
93+
submit,
94+
setRecurrenceUpdateScopeDialogOpen,
95+
isRecurrenceChanged,
96+
setFinalDraft,
97+
isEventDirty,
98+
isRecurrence,
99+
discard,
100+
],
101+
);
102+
58103
const { onMouseDown } = useGridEventMouseDown(
59104
draft?.isAllDay ? Categories_Event.ALLDAY : Categories_Event.TIMED,
60105
handleClick,
@@ -78,7 +123,7 @@ export const GridDraft: FC<Props> = ({ measurements, weekProps }) => {
78123
onMouseDown(e, event);
79124
}}
80125
onScalerMouseDown={(
81-
event: Schema_GridEvent,
126+
_event: Schema_GridEvent,
82127
e: MouseEvent,
83128
dateToChange: "startDate" | "endDate",
84129
) => {
@@ -108,13 +153,25 @@ export const GridDraft: FC<Props> = ({ measurements, weekProps }) => {
108153
onConvert={onConvert}
109154
onDelete={deleteEvent}
110155
onDuplicate={duplicateEvent}
111-
onSubmit={(_draft: Schema_GridEvent) => submit(_draft)}
156+
onSubmit={onSubmit}
112157
setEvent={setDraft}
113158
/>
114159
</StyledFloatContainer>
115160
</FloatingFocusManager>
116161
)}
117162
</div>
163+
164+
{useMemo(
165+
() => (
166+
<RecurringEventUpdateScopeDialog
167+
open={isRecurrenceUpdateScopeDialogOpen}
168+
priority={draft.priority}
169+
onOpenChange={setRecurrenceUpdateScopeDialogOpen}
170+
onSubmit={onUpdateScopeChange}
171+
/>
172+
),
173+
[isRecurrenceUpdateScopeDialogOpen, setRecurrenceUpdateScopeDialogOpen],
174+
)}
118175
</>
119176
);
120177
};

0 commit comments

Comments
 (0)