평가용 과제로 TodoList 서비스를 만들었다. 평가는 프론트엔드 위주로 진행되지만, 실제 동작하는 서비스를 보여주기 위해 백엔드까지 직접 구성했다.
Spring Initializr로 백엔드(Gradle, Web/JPA/H2/Lombok 의존성)를 받고, npm create vite로 React 프론트를 세팅했다. Tailwind CSS는 @tailwindcss/vite 플러그인으로 붙였다.
처음엔 단순히 title 하나만 가진 Todo로 시작했다. 등록 / 조회 / 완료 토글 / 삭제 API를 만들고, 프론트에서 입력폼 → 목록 → 완료 체크 → 삭제까지 한 사이클을 먼저 완성했다.
과제 요구사항에 맞춰 Todo를 제목 / 내용 / 마감일 / 완료여부 / 카테고리 구조로 확장했다.
Todo 엔티티에 content(내용), dueDate(LocalDate), category 필드 추가TodoForm에 내용 textarea, 마감일 date picker, 카테고리 select 추가TodoItem에 카테고리 배지, 내용 미리보기, 마감일 표시 추가전체 / 미완료 / 완료 상태별 필터 버튼을 추가하고, "완료 항목 삭제" / "전체 삭제" 버튼도 만들었다. 대상이 없을 땐 버튼이 자동으로 비활성화되도록 처리했다.
요구사항에 있는 "마감 임박(24시간 이내) 항목 구분 표시"를 위해, 마감일 기준으로 상태를 계산하는 함수를 만들었다.
function getDueStatus(dueDate, completed) {
if (!dueDate || completed) return null;
const due = new Date(`${dueDate}T23:59:59`);
const diffHours = (due.getTime() - Date.now()) / (1000 * 60 * 60);
if (diffHours < 0) return "overdue"; // 기한 초과
if (diffHours <= 24) return "imminent"; // 마감 임박
return null;
}
기한이 지난 항목은 빨간 배지("기한 초과"), 24시간 이내로 남은 항목은 주황 배지("마감 임박")로 표시했다.
목록이 길어질 걸 대비해 백엔드에 페이지네이션을 붙였다. Spring Data의 Page<Todo>를 활용해서 filter, page, size 쿼리 파라미터를 받도록 구성하고, 프론트에는 이전/다음 버튼을 달았다. 항목을 삭제해서 현재 페이지가 비어버리면 자동으로 이전 페이지로 돌아가도록 보정 로직도 넣었다.
git init 후 커밋 이력을 쌓고 GitHub에 push했다. README에는 기술 스택, 실행 방법, ERD, API 명세를 정리해서 누구든 클론받아 바로 실행해볼 수 있도록 했다.
상태 관리와 API 호출은 App.jsx에 몰아넣고, 나머지는 props로 데이터/콜백만 받는 표현 컴포넌트로 나눴다.
App.jsx (컨테이너 — 상태 + API 호출)
├─ TodoForm.jsx (입력 폼)
├─ TodoList.jsx (목록 래퍼)
│ └─ TodoItem.jsx (개별 항목 렌더링 + 마감상태 계산)
└─ 페이지네이션 버튼
Base URL: /api/todos
| Method | Endpoint | 설명 |
|---|---|---|
| GET | /api/todos?filter=&page=&size= | 목록 조회 (필터 + 페이지네이션) |
| GET | /api/todos/active-count | 미완료 개수 |
| POST | /api/todos | 할 일 등록 |
| PATCH | /api/todos/{id}/toggle | 완료 여부 토글 |
| DELETE | /api/todos/{id} | 단건 삭제 |
| DELETE | /api/todos/completed | 완료 항목 일괄 삭제 |
| DELETE | /api/todos | 전체 삭제 |
title ↔ text)이 바뀔 때마다 프론트의 API 함수, 컴포넌트 props까지 같이 맞춰줘야 해서, 처음부터 필드 이름을 확정하고 가는 게 훨씬 편하다는 걸 체감했다.