Skip to content

Commit 188e10e

Browse files
committed
feat: 삭제시 단일 이벤트 삭제 기능 구현
1 parent b2d6dff commit 188e10e

File tree

5 files changed

+51
-9
lines changed

5 files changed

+51
-9
lines changed

src/__tests__/medium.integration.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ describe('일정 CRUD 및 기본 기능', () => {
106106
expect(await eventList.findByText('삭제할 이벤트')).toBeInTheDocument();
107107

108108
// 삭제 버튼 클릭
109-
const allDeleteButton = await screen.findAllByLabelText('Delete event');
109+
const allDeleteButton = await screen.findAllByTestId('DeleteIcon');
110110
await user.click(allDeleteButton[0]);
111111

112112
expect(eventList.queryByText('삭제할 이벤트')).not.toBeInTheDocument();

src/__tests__/repeat/integration/integration.spec.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@ import { http, HttpResponse } from 'msw';
66
import { SnackbarProvider } from 'notistack';
77
import { ReactElement } from 'react';
88

9-
import { handlers } from '../../../__mocks__/handlers';
109
import {
1110
setupMockHandlerCreation,
1211
setupMockHandlerUpdating,
1312
} from '../../../__mocks__/handlersUtils';
14-
import { setupMockHandlerDeletion } from '../../../__mocks__/handlersUtils';
1513
import App from '../../../App';
1614
import { server } from '../../../setupTests';
15+
import { Event } from '../../../types';
1716
import { makeEvent } from '../../utils';
1817

1918
const theme = createTheme();
@@ -106,10 +105,8 @@ describe('반복 UI 및 표시', () => {
106105
});
107106

108107
it('반복일정을 단일 삭제하면 해당 날짜 셀에서만 사라진다', async () => {
109-
// 현재 주간 뷰 범위에 10/16, 10/17이 포함되도록 고정
110108
vi.setSystemTime(new Date('2025-10-16'));
111109

112-
// 반복: daily 10/15 시작 ~ 10/22 종료, 서버에는 베이스 이벤트 1건만 존재
113110
const series = makeEvent({
114111
id: 'r1',
115112
title: '반복 삭제 테스트',
@@ -140,6 +137,16 @@ describe('반복 UI 및 표시', () => {
140137
http.get('/api/events', () => {
141138
return HttpResponse.json({ events: mockEvents });
142139
}),
140+
http.put('/api/events/:id', async ({ params, request }) => {
141+
const { id } = params;
142+
const updatedEvent = (await request.json()) as Event;
143+
144+
const index = mockEvents.findIndex((event) => event.id === id);
145+
if (index !== -1) {
146+
mockEvents[index] = { ...mockEvents[index], ...updatedEvent };
147+
}
148+
return HttpResponse.json(mockEvents[index]);
149+
}),
143150
http.delete('/api/events/:id', ({ params }) => {
144151
const { id } = params;
145152
mockEvents = mockEvents.filter((event) => event.id !== id);

src/hooks/useEventOperations.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,37 @@ export const useEventOperations = (editing: boolean, onSave?: () => void) => {
5555

5656
const deleteEvent = async (id: string) => {
5757
try {
58-
const response = await fetch(`/api/events/${id}`, { method: 'DELETE' });
58+
const isInstanceId = id.includes('_') && id.match(/_\d{4}-\d{2}-\d{2}$/);
5959

60-
if (!response.ok) {
61-
throw new Error('Failed to delete event');
60+
if (isInstanceId) {
61+
const [baseId, dateToExclude] = id.split('_');
62+
const baseEvent = events.find((event) => event.id === baseId);
63+
64+
if (baseEvent && baseEvent.repeat.type !== 'none') {
65+
const updatedEvent = {
66+
...baseEvent,
67+
repeat: {
68+
...baseEvent.repeat,
69+
excludedDates: [...(baseEvent.repeat.excludedDates || []), dateToExclude],
70+
},
71+
};
72+
73+
const response = await fetch(`/api/events/${baseId}`, {
74+
method: 'PUT',
75+
headers: { 'Content-Type': 'application/json' },
76+
body: JSON.stringify(updatedEvent),
77+
});
78+
79+
if (!response.ok) {
80+
throw new Error('Failed to exclude event instance');
81+
}
82+
}
83+
} else {
84+
const response = await fetch(`/api/events/${id}`, { method: 'DELETE' });
85+
86+
if (!response.ok) {
87+
throw new Error('Failed to delete event');
88+
}
6289
}
6390

6491
await fetchEvents();

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export interface RepeatInfo {
44
type: RepeatType;
55
interval: number;
66
endDate?: string;
7+
excludedDates?: string[]; // 제외할 날짜들 (YYYY-MM-DD 형식)
78
}
89

910
export interface EventForm {

src/utils/repeat/actions.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,15 @@ function appendIfInRange(
2424
rangeEnd: Date,
2525
acc: Event[]
2626
): Event[] {
27+
const dateString = toDateStringUTC(cursor);
28+
29+
if (event.repeat.excludedDates?.includes(dateString)) {
30+
return acc;
31+
}
32+
2733
if (cursor >= rangeStart && cursor <= rangeEnd) {
28-
return [...acc, { ...event, date: toDateStringUTC(cursor) }];
34+
const instanceId = dateString === event.date ? event.id : `${event.id}_${dateString}`;
35+
return [...acc, { ...event, date: dateString, id: instanceId }];
2936
}
3037
return acc;
3138
}

0 commit comments

Comments
 (0)