Skip to content

Commit 899e6cd

Browse files
✨ feat(edit-recurrence): create recurring event update scope selection component (#825)
* ✨ feat(edit-recurrence): add recurring event update scope dialog * ✨ feat(edit-recurrence): enable basic recurring events edit
1 parent efee38a commit 899e6cd

File tree

24 files changed

+661
-314
lines changed

24 files changed

+661
-314
lines changed

packages/web/src/__tests__/utils/test.util.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ export const mockBSON = () => {
4141
toString() {
4242
return crypto.randomUUID();
4343
}
44+
45+
static isValid(value?: string) {
46+
return value?.length === 36;
47+
}
4448
},
4549
}));
4650
};

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/components/Button/styled.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,26 @@ export const PriorityButton = styled(Btn)<PalletteProps>`
2727
2828
&:hover {
2929
background: ${({ theme }) => theme.color.bg.primary};
30-
color: ${({ color }) => brighten(color)};
30+
color: ${({ color }) => brighten(color!)};
3131
transition: background-color 0.5s;
3232
transition: color 0.55s;
3333
}
3434
`;
3535
interface CustomProps {
3636
priority: Priority;
3737
minWidth: number;
38+
disabled?: boolean;
3839
}
3940

4041
export const StyledSaveBtn = styled(PriorityButton)<CustomProps>`
4142
background: ${({ priority }) => darken(colorByPriority[priority])};
4243
color: ${({ theme }) => theme.color.text.dark}
43-
44+
4445
min-width: ${({ minWidth }) => minWidth}px;
4546
47+
opacity: ${({ disabled }) => (disabled ? 0.5 : 1)};
48+
pointer-events: ${({ disabled }) => (disabled ? "none" : "auto")};
49+
4650
&:focus {
4751
border: 2px solid ${({ theme }) => theme.color.border.primaryDark};
4852
}

packages/web/src/components/ContextMenu/ContextMenuItems.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ import { colorByPriority } from "@web/common/styles/theme.util";
66
import { Schema_GridEvent } from "@web/common/types/web.event.types";
77
import { assembleGridEvent } from "@web/common/utils/event.util";
88
import { getSomedayEventCategory } from "@web/common/utils/someday.util";
9-
import IconButton from "@web/components/IconButton/IconButton";
10-
import { useDraftContext } from "@web/views/Calendar/components/Draft/context/useDraftContext";
11-
import { useSidebarContext } from "@web/views/Calendar/components/Draft/sidebar/context/useSidebarContext";
129
import {
1310
MenuItem,
1411
MenuItemLabel,
1512
PriorityCircle,
1613
PriorityContainer,
1714
TooltipText,
1815
TooltipWrapper,
19-
} from "./styled";
16+
} from "@web/components/ContextMenu/styled";
17+
import IconButton from "@web/components/IconButton/IconButton";
18+
import { useDraftContext } from "@web/views/Calendar/components/Draft/context/useDraftContext";
19+
import { useSidebarContext } from "@web/views/Calendar/components/Draft/sidebar/context/useSidebarContext";
2020

2121
export interface ContextMenuAction {
2222
id: string;
@@ -31,8 +31,8 @@ interface ContextMenuItemsProps {
3131
}
3232

3333
export function ContextMenuItems({ event, close }: ContextMenuItemsProps) {
34-
const { actions, setters } = useDraftContext();
35-
const { openForm, deleteEvent, duplicateEvent, submit } = actions;
34+
const { actions, setters, confirmation } = useDraftContext();
35+
const { openForm, duplicateEvent, submit } = actions;
3636
const { setDraft } = setters;
3737

3838
const sidebarContext = useSidebarContext(true);
@@ -80,6 +80,8 @@ export function ContextMenuItems({ event, close }: ContextMenuItemsProps) {
8080
}
8181
};
8282

83+
const { onDelete } = confirmation;
84+
8385
const menuActions: ContextMenuAction[] = [
8486
{
8587
id: "edit",
@@ -104,7 +106,7 @@ export function ContextMenuItems({ event, close }: ContextMenuItemsProps) {
104106
{
105107
id: "delete",
106108
label: "Delete",
107-
onClick: deleteEvent,
109+
onClick: onDelete,
108110
icon: (
109111
<IconButton>
110112
<Trash />

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ const EventApi = {
1111
create: (event: Schema_Event | Schema_Event[]) => {
1212
return CompassApi.post(`/event`, event);
1313
},
14-
delete: (_id: string) => {
15-
return CompassApi.delete(`/event/${_id}`);
14+
delete: (_id: string, applyTo?: RecurringEventUpdateScope) => {
15+
return CompassApi.delete(`/event/${_id}?applyTo=${applyTo}`);
1616
},
1717
edit: (
1818
_id: string,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ interface Payload_ConvertTimedEvent {
7979

8080
interface Payload_DeleteEvent {
8181
_id: string;
82+
applyTo?: RecurringEventUpdateScope;
8283
}
8384

8485
export interface Payload_EditEvent {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export function* deleteEvent({ payload }: Action_DeleteEvent) {
109109

110110
const isInDb = !event._id.startsWith(ID_OPTIMISTIC_PREFIX);
111111
if (isInDb) {
112-
yield call(EventApi.delete, payload._id);
112+
yield call(EventApi.delete, payload._id, payload.applyTo);
113113
}
114114

115115
yield put(deleteEventSlice.actions.success());

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export function* deleteSomedayEvent({ payload }: Action_DeleteEvent) {
5555
try {
5656
yield put(eventsEntitiesSlice.actions.delete(payload));
5757

58-
yield call(EventApi.delete, payload._id);
58+
yield call(EventApi.delete, payload._id, payload.applyTo);
5959
} catch (error) {
6060
yield put(getSomedayEventsSlice.actions.error());
6161
handleError(error as Error);

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/Calendar.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ContextMenuWrapper } from "@web/components/ContextMenu/GridContextMenuW
44
import { FlexDirections } from "@web/components/Flex/styled";
55
import { selectIsSidebarOpen } from "@web/ducks/events/selectors/view.selectors";
66
import { useAppSelector } from "@web/store/store.hooks";
7+
import { RecurringEventUpdateScopeDialog } from "@web/views/Forms/EventForm/RecurringEventUpdateScopeDialog";
78
import { CmdPalette } from "../CmdPalette";
89
import { RootProps } from "./calendarView.types";
910
import { Dedication } from "./components/Dedication";
@@ -104,6 +105,8 @@ export const CalendarView = () => {
104105
</StyledCalendar>
105106
</Shortcuts>
106107
</SidebarDraftProvider>
108+
109+
<RecurringEventUpdateScopeDialog />
107110
</DraftProvider>
108111
</Styled>
109112
);

0 commit comments

Comments
 (0)