Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,28 @@

### 필수

- [ ] 반복 유형 선택
- [x] 반복 유형 선택
- 일정 생성 또는 수정 시 반복 유형을 선택할 수 있다.
- 반복 유형은 다음과 같다: 매일, 매주, 매월, 매년
- 31일에 매월을 선택한다면 -> 매월 마지막이 아닌, 31일에만 생성하세요.
- 윤년 29일에 매년을 선택한다면 -> 29일에만 생성하세요!
- [ ] 반복 일정 표시
- [x] 반복 일정 표시
- 캘린더 뷰에서 반복 일정을 시각적으로 구분하여 표시한다.
- 아이콘을 넣든 태그를 넣든 자유롭게 해보세요!
- [ ] 반복 종료
- Repeat 아이콘으로 반복 일정 구분 표시 ✓
- [x] 반복 종료
- 반복 종료 조건을 지정할 수 있다.
- 옵션: 특정 날짜까지, 특정 횟수만큼, 또는 종료 없음 (예제 특성상, 2025-06-30까지)
- [ ] 반복 일정 단일 수정
- 옵션: 특정 날짜까지 (2025-10-30까지 기본값 설정) ✓
- [x] 반복 일정 단일 수정
- 반복일정을 수정하면 단일 일정으로 변경됩니다.
- 반복일정 아이콘도 사라집니다.
- [ ] 반복 일정 단일 삭제
- 반복일정을 삭제하면 해당 일정만 삭제합니다.
- 반복일정 아이콘도 사라집니다.
- [x] 반복 일정 단일 삭제
- 반복일정을 삭제하면 해당 일정만 삭제합니다.

### 선택

- [ ] 반복 간격 설정
- [x] 반복 간격 설정
- 각 반복 유형에 대해 간격을 설정할 수 있다.
- 예: 2일마다, 3주마다, 2개월마다 등
- 예: 2일마다, 3주마다, 2개월마다 등
- [ ] 예외 날짜 처리:
- 반복 일정 중 특정 날짜를 제외할 수 있다.
- 반복 일정 중 특정 날짜의 일정을 수정할 수 있다.
Expand Down
70 changes: 68 additions & 2 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ app.post('/api/events', async (req, res) => {

app.put('/api/events/:id', async (req, res) => {
const events = await getEvents();
const { id } = req.params;
const id = req.params.id;
const eventIndex = events.events.findIndex((event) => event.id === id);
if (eventIndex > -1) {
const newEvents = [...events.events];
Expand All @@ -59,7 +59,7 @@ app.put('/api/events/:id', async (req, res) => {

app.delete('/api/events/:id', async (req, res) => {
const events = await getEvents();
const { id } = req.params;
const id = req.params.id;

fs.writeFileSync(
`${__dirname}/src/__mocks__/response/realEvents.json`,
Expand All @@ -71,6 +71,72 @@ app.delete('/api/events/:id', async (req, res) => {
res.status(204).send();
});

app.post('/api/events-list', async (req, res) => {
const events = await getEvents();
const repeatId = randomUUID();
const newEvents = req.body.events.map((event) => {
const isRepeatEvent = event.repeat.type !== 'none';
return {
id: randomUUID(),
...event,
repeat: {
...event.repeat,
id: isRepeatEvent ? repeatId : undefined,
},
};
});

fs.writeFileSync(
`${__dirname}/src/__mocks__/response/realEvents.json`,
JSON.stringify({
events: [...events.events, ...newEvents],
})
);

res.status(201).json(newEvents);
});

app.put('/api/events-list', async (req, res) => {
const events = await getEvents();
let isUpdated = false;

const newEvents = [...events.events];
req.body.events.forEach((event) => {
const eventIndex = events.events.findIndex((target) => target.id === event.id);
if (eventIndex > -1) {
isUpdated = true;
newEvents[eventIndex] = { ...events.events[eventIndex], ...event };
}
});

if (isUpdated) {
fs.writeFileSync(
`${__dirname}/src/__mocks__/response/realEvents.json`,
JSON.stringify({
events: newEvents,
})
);

res.json(events.events);
} else {
res.status(404).send('Event not found');
}
});

app.delete('/api/events-list', async (req, res) => {
const events = await getEvents();
const newEvents = events.events.filter((event) => !req.body.eventIds.includes(event.id)); // ? ids를 전달하면 해당 아이디를 기준으로 events에서 제거

fs.writeFileSync(
`${__dirname}/src/__mocks__/response/realEvents.json`,
JSON.stringify({
events: newEvents,
})
);

res.status(204).send();
});

app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
31 changes: 23 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { Notifications, ChevronLeft, ChevronRight, Delete, Edit, Close } from '@mui/icons-material';
import {
Notifications,
ChevronLeft,
ChevronRight,
Delete,
Edit,
Close,
Repeat,
} from '@mui/icons-material';
import {
Alert,
AlertTitle,
Expand Down Expand Up @@ -35,8 +43,7 @@ import { useEventForm } from './hooks/useEventForm.ts';
import { useEventOperations } from './hooks/useEventOperations.ts';
import { useNotifications } from './hooks/useNotifications.ts';
import { useSearch } from './hooks/useSearch.ts';
// import { Event, EventForm, RepeatType } from './types';
import { Event, EventForm } from './types';
import { Event, EventForm, RepeatType } from './types';
import {
formatDate,
formatMonth,
Expand Down Expand Up @@ -77,11 +84,11 @@ function App() {
isRepeating,
setIsRepeating,
repeatType,
// setRepeatType,
setRepeatType,
repeatInterval,
// setRepeatInterval,
setRepeatInterval,
repeatEndDate,
// setRepeatEndDate,
setRepeatEndDate,
notificationTime,
setNotificationTime,
startTimeError,
Expand Down Expand Up @@ -201,6 +208,9 @@ function App() {
>
<Stack direction="row" spacing={1} alignItems="center">
{isNotified && <Notifications fontSize="small" />}
{event.repeat.type !== 'none' && (
<Repeat fontSize="small" color="primary" />
)}
<Typography
variant="caption"
noWrap
Expand Down Expand Up @@ -288,6 +298,9 @@ function App() {
>
<Stack direction="row" spacing={1} alignItems="center">
{isNotified && <Notifications fontSize="small" />}
{event.repeat.type !== 'none' && (
<Repeat fontSize="small" color="primary" />
)}
<Typography
variant="caption"
noWrap
Expand Down Expand Up @@ -438,7 +451,7 @@ function App() {
</FormControl>

{/* ! 반복은 8주차 과제에 포함됩니다. 구현하고 싶어도 참아주세요~ */}
{/* {isRepeating && (
{isRepeating && (
<Stack spacing={2}>
<FormControl fullWidth>
<FormLabel>반복 유형</FormLabel>
Expand All @@ -447,6 +460,7 @@ function App() {
value={repeatType}
onChange={(e) => setRepeatType(e.target.value as RepeatType)}
>
<MenuItem value="none">반복 안함</MenuItem>
<MenuItem value="daily">매일</MenuItem>
<MenuItem value="weekly">매주</MenuItem>
<MenuItem value="monthly">매월</MenuItem>
Expand Down Expand Up @@ -475,7 +489,7 @@ function App() {
</FormControl>
</Stack>
</Stack>
)} */}
)}

<Button
data-testid="event-submit-button"
Expand Down Expand Up @@ -541,6 +555,7 @@ function App() {
<Stack>
<Stack direction="row" spacing={1} alignItems="center">
{notifiedEvents.includes(event.id) && <Notifications color="error" />}
{event.repeat.type !== 'none' && <Repeat color="primary" />}
<Typography
fontWeight={notifiedEvents.includes(event.id) ? 'bold' : 'normal'}
color={notifiedEvents.includes(event.id) ? 'error' : 'inherit'}
Expand Down
50 changes: 50 additions & 0 deletions src/__mocks__/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,54 @@ export const handlers = [

return new HttpResponse(null, { status: 404 });
}),

http.post('/api/events-list', async ({ request }) => {
const { events: requestEvents } = (await request.json()) as { events: Event[] };
const repeatId = String(events.length + 1);
const newEvents = requestEvents.map((event, index) => {
const isRepeatEvent = event.repeat.type !== 'none';
return {
...event,
id: String(events.length + index + 1),
repeat: {
...event.repeat,
id: isRepeatEvent ? repeatId : undefined,
},
};
});

events.push(...newEvents);

return HttpResponse.json(newEvents, { status: 201 });
}),

http.put('/api/events-list', async ({ request }) => {
const { events: requestEvents } = (await request.json()) as { events: Event[] };
let isUpdated = false;

const newEvents = [...events];
requestEvents.forEach((event) => {
const index = events.findIndex((target) => target.id === event.id);
if (index !== -1) {
isUpdated = true;
newEvents[index] = { ...events[index], ...event };
}
});

if (isUpdated) {
events.splice(0, events.length, ...newEvents);
return HttpResponse.json(newEvents, { status: 200 });
}

return new HttpResponse(null, { status: 404 });
}),

http.delete('/api/events-list', async ({ request }) => {
const { eventIds } = (await request.json()) as { eventIds: string[] };
const remainingEvents = events.filter((event) => !eventIds.includes(event.id));

events.splice(0, events.length, ...remainingEvents);

return new HttpResponse(null, { status: 204 });
}),
];
71 changes: 71 additions & 0 deletions src/__mocks__/handlersUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,74 @@ export const setupMockHandlerDeletion = () => {
})
);
};

export const setupMockHandlerRepeatCreation = (initEvents = [] as Event[]) => {
const mockEvents: Event[] = [...initEvents];

server.use(
http.get('/api/events', () => {
return HttpResponse.json({ events: mockEvents });
}),
http.post('/api/events-list', async ({ request }) => {
const { events: requestEvents } = (await request.json()) as { events: Event[] };
const repeatId = String(mockEvents.length + 1);
const newEvents = requestEvents.map((event, index) => {
const isRepeatEvent = event.repeat.type !== 'none';
return {
...event,
id: String(mockEvents.length + index + 1),
repeat: {
...event.repeat,
id: isRepeatEvent ? repeatId : undefined,
},
};
});

mockEvents.push(...newEvents);

return HttpResponse.json(newEvents, { status: 201 });
})
);
};

export const setupMockHandlerRepeatUpdating = (initEvents = [] as Event[]) => {
const mockEvents: Event[] = [...initEvents];

server.use(
http.get('/api/events', () => {
return HttpResponse.json({ events: mockEvents });
}),
http.put('/api/events/:id', async ({ params, request }) => {
const { id } = params;
const updatedEvent = (await request.json()) as Event;
const index = mockEvents.findIndex((event) => event.id === id);

if (index !== -1) {
mockEvents[index] = { ...mockEvents[index], ...updatedEvent };
return HttpResponse.json(mockEvents[index]);
}

return new HttpResponse(null, { status: 404 });
})
);
};

export const setupMockHandlerRepeatDeletion = (initEvents = [] as Event[]) => {
const mockEvents: Event[] = [...initEvents];

server.use(
http.get('/api/events', () => {
return HttpResponse.json({ events: mockEvents });
}),
http.delete('/api/events/:id', ({ params }) => {
const { id } = params;
const index = mockEvents.findIndex((event) => event.id === id);

if (index !== -1) {
mockEvents.splice(index, 1);
}

return new HttpResponse(null, { status: 204 });
})
);
};
Loading
Loading