Files
todos2/docs/FEATURE_SPEC.md
jungwoo choi 074b5133bf feat: 풀스택 할일관리 앱 구현 (통합 모달 + 간트차트)
- Backend: FastAPI + MongoDB + Redis (카테고리, 할일 CRUD, 파일 첨부, 검색, 대시보드)
- Frontend: Next.js 15 + Tailwind + React Query + Zustand
- 통합 TodoModal: 생성/수정 모달 통합, 탭 구조 (기본/태그와 첨부)
- 간트차트: 카테고리별 할일 타임라인 시각화
- TodoCard: 제목/카테고리/우선순위/태그/첨부 한줄 표시
- Docker Compose 배포 (Frontend:3010, Backend:8010, MongoDB:27021, Redis:6391)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 15:45:03 +09:00

790 lines
27 KiB
Markdown

# 기능 정의서 (Feature Specification)
> 프로젝트: todos2 | 버전: 1.0.0 | 작성일: 2026-02-10
---
## 1. 기능 목록 (Feature Inventory)
| # | 기능명 | 우선순위 | 카테고리 | 상태 |
|---|--------|---------|---------|------|
| F-001 | 할일 생성 | Must | 할일 CRUD | 미개발 |
| F-002 | 할일 목록 조회 | Must | 할일 CRUD | 미개발 |
| F-003 | 할일 상세 조회 | Must | 할일 CRUD | 미개발 |
| F-004 | 할일 수정 | Must | 할일 CRUD | 미개발 |
| F-005 | 할일 삭제 | Must | 할일 CRUD | 미개발 |
| F-006 | 할일 완료 토글 | Must | 할일 CRUD | 미개발 |
| F-007 | 카테고리 생성 | Must | 카테고리 | 미개발 |
| F-008 | 카테고리 목록 조회 | Must | 카테고리 | 미개발 |
| F-009 | 카테고리 수정 | Must | 카테고리 | 미개발 |
| F-010 | 카테고리 삭제 | Must | 카테고리 | 미개발 |
| F-011 | 태그 부여 | Must | 태그 | 미개발 |
| F-012 | 태그별 필터링 | Must | 태그 | 미개발 |
| F-013 | 태그 목록 조회 | Must | 태그 | 미개발 |
| F-014 | 우선순위 설정 | Must | 우선순위 | 미개발 |
| F-015 | 마감일 설정 | Must | 마감일 | 미개발 |
| F-016 | 마감일 알림 표시 | Must | 마감일 | 미개발 |
| F-017 | 검색 | Should | 검색 | 미개발 |
| F-018 | 대시보드 통계 | Should | 대시보드 | 미개발 |
| F-019 | 일괄 완료 처리 | Should | 일괄 작업 | 미개발 |
| F-020 | 일괄 삭제 | Should | 일괄 작업 | 미개발 |
| F-021 | 일괄 카테고리 변경 | Should | 일괄 작업 | 미개발 |
---
## 2. 기능 상세 정의
---
### F-001: 할일 생성
- **설명**: 사용자가 제목, 내용, 카테고리, 태그, 우선순위, 마감일을 입력하여 새로운 할일을 생성한다.
- **우선순위**: Must
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| title | string | Y | 1~200자, 공백만 불가 | 할일 제목 |
| content | string | N | 최대 2000자 | 할일 상세 내용 |
| category_id | string | N | 유효한 카테고리 ObjectId | 분류 카테고리 |
| tags | string[] | N | 각 태그 1~30자, 최대 10개 | 태그 목록 |
| priority | enum | N | high / medium / low (기본: medium) | 우선순위 |
| due_date | datetime | N | 현재 시각 이후 (생성 시점 기준) | 마감일 |
#### 처리 규칙 (Business Rules)
1. title 앞뒤 공백을 제거(trim)한다.
2. tags 배열 내 중복 태그를 제거하고, 각 태그를 소문자로 정규화한다.
3. category_id가 지정된 경우, 해당 카테고리가 존재하는지 검증한다.
4. created_at, updated_at을 현재 시각(UTC)으로 설정한다.
5. completed는 false로 초기화한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 201 Created | 생성된 Todo 객체 (id 포함) |
| 제목 누락/검증 실패 | 422 Unprocessable Entity | 필드별 에러 메시지 |
| 카테고리 미존재 | 404 Not Found | `{"detail": "카테고리를 찾을 수 없습니다"}` |
#### 수락 기준 (Acceptance Criteria)
- [ ] 제목만 입력하여 할일을 생성할 수 있다
- [ ] 모든 필드(제목, 내용, 카테고리, 태그, 우선순위, 마감일)를 입력하여 할일을 생성할 수 있다
- [ ] 제목 없이 생성 시 422 에러가 반환된다
- [ ] 201자 이상의 제목 입력 시 422 에러가 반환된다
- [ ] 존재하지 않는 카테고리 ID 입력 시 404 에러가 반환된다
- [ ] 생성 후 할일 목록에 즉시 반영된다
- [ ] 태그 중복이 자동으로 제거된다
---
### F-002: 할일 목록 조회
- **설명**: 필터링, 정렬, 페이지네이션을 적용하여 할일 목록을 조회한다.
- **우선순위**: Must
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| page | integer | N | >= 1, 기본: 1 | 페이지 번호 |
| limit | integer | N | 1~100, 기본: 20 | 페이지당 항목 수 |
| completed | boolean | N | true / false | 완료 상태 필터 |
| category_id | string | N | 유효한 ObjectId | 카테고리 필터 |
| priority | enum | N | high / medium / low | 우선순위 필터 |
| tag | string | N | - | 태그 필터 |
| sort | string | N | created_at / due_date / priority | 정렬 기준 (기본: created_at) |
| order | enum | N | asc / desc (기본: desc) | 정렬 방향 |
#### 처리 규칙 (Business Rules)
1. 필터 조건이 복수인 경우 AND 조건으로 적용한다.
2. 카테고리 필터 시 해당 category의 이름과 색상을 populate한다.
3. 페이지네이션 응답에 총 개수(total)와 총 페이지 수(total_pages)를 포함한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 200 OK | `{"items": [...], "total": N, "page": N, "limit": N, "total_pages": N}` |
| 잘못된 파라미터 | 422 Unprocessable Entity | 필드별 에러 메시지 |
#### 수락 기준 (Acceptance Criteria)
- [ ] 필터 없이 전체 할일 목록을 조회할 수 있다
- [ ] 완료/미완료 상태로 필터링할 수 있다
- [ ] 카테고리별로 필터링할 수 있다
- [ ] 우선순위별로 필터링할 수 있다
- [ ] 태그별로 필터링할 수 있다
- [ ] 페이지네이션이 정상 동작한다 (총 개수, 총 페이지 수 포함)
- [ ] 정렬 기준(생성일, 마감일, 우선순위)과 방향(오름차순/내림차순)을 지정할 수 있다
---
### F-003: 할일 상세 조회
- **설명**: 특정 할일의 전체 정보를 조회한다.
- **우선순위**: Must
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| id | string | Y | 유효한 ObjectId | 할일 ID |
#### 처리 규칙 (Business Rules)
1. 카테고리가 지정된 경우, 카테고리 이름과 색상을 함께 반환한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 200 OK | Todo 객체 (카테고리 정보 포함) |
| 미존재 | 404 Not Found | `{"detail": "할일을 찾을 수 없습니다"}` |
| 잘못된 ID 형식 | 422 Unprocessable Entity | `{"detail": "유효하지 않은 ID 형식입니다"}` |
#### 수락 기준 (Acceptance Criteria)
- [ ] 유효한 ID로 할일 상세 정보를 조회할 수 있다
- [ ] 카테고리 정보가 함께 반환된다
- [ ] 존재하지 않는 ID로 조회 시 404가 반환된다
---
### F-004: 할일 수정
- **설명**: 기존 할일의 제목, 내용, 카테고리, 태그, 우선순위, 마감일을 수정한다.
- **우선순위**: Must
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| id | string (path) | Y | 유효한 ObjectId | 할일 ID |
| title | string | N | 1~200자 | 할일 제목 |
| content | string | N | 최대 2000자 | 할일 상세 내용 |
| category_id | string \| null | N | 유효한 ObjectId 또는 null | 카테고리 (null이면 해제) |
| tags | string[] | N | 각 태그 1~30자, 최대 10개 | 태그 목록 |
| priority | enum | N | high / medium / low | 우선순위 |
| due_date | datetime \| null | N | null이면 해제 | 마감일 |
#### 처리 규칙 (Business Rules)
1. 요청에 포함된 필드만 업데이트한다 (Partial Update).
2. updated_at을 현재 시각(UTC)으로 갱신한다.
3. category_id가 지정된 경우 해당 카테고리의 존재를 검증한다.
4. tags 배열이 지정된 경우 중복 제거 및 소문자 정규화를 적용한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 200 OK | 수정된 Todo 객체 |
| 미존재 | 404 Not Found | `{"detail": "할일을 찾을 수 없습니다"}` |
| 검증 실패 | 422 Unprocessable Entity | 필드별 에러 메시지 |
#### 수락 기준 (Acceptance Criteria)
- [ ] 제목만 수정할 수 있다
- [ ] 카테고리를 변경할 수 있다
- [ ] 카테고리를 null로 설정하여 해제할 수 있다
- [ ] 태그를 추가/삭제할 수 있다
- [ ] 우선순위를 변경할 수 있다
- [ ] 마감일을 설정/해제할 수 있다
- [ ] 수정 후 updated_at이 갱신된다
- [ ] 존재하지 않는 할일 수정 시 404가 반환된다
---
### F-005: 할일 삭제
- **설명**: 특정 할일을 영구 삭제한다.
- **우선순위**: Must
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| id | string | Y | 유효한 ObjectId | 할일 ID |
#### 처리 규칙 (Business Rules)
1. 물리 삭제를 수행한다.
2. 삭제 전 확인 다이얼로그를 프론트엔드에서 표시한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 204 No Content | 빈 응답 |
| 미존재 | 404 Not Found | `{"detail": "할일을 찾을 수 없습니다"}` |
#### 수락 기준 (Acceptance Criteria)
- [ ] 할일을 삭제할 수 있다
- [ ] 삭제 전 확인 다이얼로그가 표시된다
- [ ] 삭제 후 목록에서 즉시 사라진다
- [ ] 존재하지 않는 할일 삭제 시 404가 반환된다
---
### F-006: 할일 완료 토글
- **설명**: 할일의 완료/미완료 상태를 전환한다.
- **우선순위**: Must
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| id | string (path) | Y | 유효한 ObjectId | 할일 ID |
#### 처리 규칙 (Business Rules)
1. 현재 completed 값의 반대값으로 토글한다.
2. updated_at을 갱신한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 200 OK | `{"id": "...", "completed": true/false}` |
| 미존재 | 404 Not Found | `{"detail": "할일을 찾을 수 없습니다"}` |
#### 수락 기준 (Acceptance Criteria)
- [ ] 체크박스 클릭으로 완료/미완료를 토글할 수 있다
- [ ] 완료된 할일은 시각적으로 구분된다 (취소선, 흐림 처리 등)
- [ ] 토글 후 대시보드 통계가 갱신된다
---
### F-007: 카테고리 생성
- **설명**: 할일 분류를 위한 새 카테고리를 생성한다.
- **우선순위**: Must
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| name | string | Y | 1~50자, 고유 | 카테고리 이름 |
| color | string | N | 유효한 hex color (기본: #6B7280) | 표시 색상 |
#### 처리 규칙 (Business Rules)
1. name 앞뒤 공백을 제거(trim)한다.
2. 동일한 이름의 카테고리가 이미 존재하면 409 Conflict를 반환한다.
3. order는 현재 최대값 + 1로 자동 설정한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 201 Created | 생성된 Category 객체 |
| 이름 중복 | 409 Conflict | `{"detail": "이미 존재하는 카테고리 이름입니다"}` |
| 검증 실패 | 422 Unprocessable Entity | 필드별 에러 메시지 |
#### 수락 기준 (Acceptance Criteria)
- [ ] 이름과 색상을 지정하여 카테고리를 생성할 수 있다
- [ ] 이름만으로 생성 시 기본 색상이 적용된다
- [ ] 중복 이름 생성 시 에러가 표시된다
- [ ] 생성 후 사이드바 카테고리 목록에 즉시 반영된다
---
### F-008: 카테고리 목록 조회
- **설명**: 전체 카테고리 목록을 조회한다.
- **우선순위**: Must
#### 입력 (Inputs)
없음 (파라미터 없이 전체 조회)
#### 처리 규칙 (Business Rules)
1. order 필드 기준 오름차순으로 정렬한다.
2. 각 카테고리에 속한 할일 수(todo_count)를 함께 반환한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 200 OK | `[{"_id": "...", "name": "...", "color": "...", "order": N, "todo_count": N}]` |
#### 수락 기준 (Acceptance Criteria)
- [ ] 전체 카테고리 목록을 조회할 수 있다
- [ ] 각 카테고리에 할일 수가 표시된다
- [ ] 정렬 순서대로 표시된다
---
### F-009: 카테고리 수정
- **설명**: 기존 카테고리의 이름, 색상, 순서를 수정한다.
- **우선순위**: Must
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| id | string (path) | Y | 유효한 ObjectId | 카테고리 ID |
| name | string | N | 1~50자, 고유 | 카테고리 이름 |
| color | string | N | 유효한 hex color | 표시 색상 |
| order | integer | N | >= 0 | 정렬 순서 |
#### 처리 규칙 (Business Rules)
1. 요청에 포함된 필드만 업데이트한다.
2. name 변경 시 중복 검사를 수행한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 200 OK | 수정된 Category 객체 |
| 미존재 | 404 Not Found | `{"detail": "카테고리를 찾을 수 없습니다"}` |
| 이름 중복 | 409 Conflict | `{"detail": "이미 존재하는 카테고리 이름입니다"}` |
#### 수락 기준 (Acceptance Criteria)
- [ ] 카테고리 이름을 변경할 수 있다
- [ ] 카테고리 색상을 변경할 수 있다
- [ ] 변경 사항이 해당 카테고리의 모든 할일 표시에 반영된다
- [ ] 중복 이름으로 변경 시 에러가 표시된다
---
### F-010: 카테고리 삭제
- **설명**: 카테고리를 삭제한다. 해당 카테고리에 속한 할일의 category_id는 null로 초기화된다.
- **우선순위**: Must
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| id | string (path) | Y | 유효한 ObjectId | 카테고리 ID |
#### 처리 규칙 (Business Rules)
1. 카테고리 삭제 시, 해당 카테고리에 속한 모든 할일의 category_id를 null로 변경한다.
2. 삭제 전 확인 다이얼로그에 영향받는 할일 수를 표시한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 204 No Content | 빈 응답 |
| 미존재 | 404 Not Found | `{"detail": "카테고리를 찾을 수 없습니다"}` |
#### 수락 기준 (Acceptance Criteria)
- [ ] 카테고리를 삭제할 수 있다
- [ ] 삭제 확인 다이얼로그에 영향받는 할일 수가 표시된다
- [ ] 삭제 후 해당 카테고리의 할일들이 "미분류"로 표시된다
- [ ] 사이드바에서 즉시 사라진다
---
### F-011: 태그 부여
- **설명**: 할일 생성 또는 수정 시 태그를 부여한다. 콤마로 구분하여 다중 태그를 입력한다.
- **우선순위**: Must
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| tags | string[] | N | 각 1~30자, 최대 10개, 특수문자 불가 | 태그 배열 |
#### 처리 규칙 (Business Rules)
1. F-001(할일 생성), F-004(할일 수정)에서 tags 필드로 처리된다.
2. 입력 태그를 소문자로 정규화하고, 중복을 제거한다.
3. 기존 태그 자동완성을 위해 사용 중인 태그 목록을 제공한다.
#### 출력 (Outputs)
F-001, F-004의 출력과 동일 (Todo 객체에 tags 포함)
#### 수락 기준 (Acceptance Criteria)
- [ ] 할일 생성/수정 폼에서 태그를 입력할 수 있다
- [ ] 기존 태그 자동완성이 동작한다
- [ ] 태그가 뱃지 형태로 표시된다
- [ ] 태그를 개별 삭제할 수 있다 (x 버튼)
---
### F-012: 태그별 필터링
- **설명**: 특정 태그가 부여된 할일만 필터링하여 조회한다.
- **우선순위**: Must
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| tag | string | Y | 존재하는 태그 | 필터링할 태그명 |
#### 처리 규칙 (Business Rules)
1. F-002(할일 목록 조회)의 tag 파라미터로 처리된다.
2. 태그 뱃지 클릭 시 해당 태그로 필터링된다.
#### 출력 (Outputs)
F-002의 출력과 동일
#### 수락 기준 (Acceptance Criteria)
- [ ] 태그 뱃지 클릭으로 해당 태그의 할일만 필터링된다
- [ ] 필터 적용 중 태그명이 표시된다
- [ ] 필터 해제 버튼으로 전체 목록으로 돌아갈 수 있다
---
### F-013: 태그 목록 조회
- **설명**: 현재 사용 중인 모든 태그의 목록과 사용 횟수를 조회한다.
- **우선순위**: Must
#### 입력 (Inputs)
없음
#### 처리 규칙 (Business Rules)
1. todos 컬렉션에서 tags 필드를 distinct로 추출한다.
2. 각 태그의 사용 횟수(count)를 집계한다.
3. 사용 횟수 내림차순으로 정렬한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 200 OK | `[{"name": "업무", "count": 5}, ...]` |
#### 수락 기준 (Acceptance Criteria)
- [ ] 사용 중인 모든 태그 목록을 조회할 수 있다
- [ ] 각 태그의 사용 횟수가 표시된다
- [ ] 할일이 없는 태그는 목록에 표시되지 않는다
---
### F-014: 우선순위 설정
- **설명**: 할일에 높음(high), 중간(medium), 낮음(low) 3단계 우선순위를 설정한다.
- **우선순위**: Must
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| priority | enum | N | high / medium / low | 우선순위 (기본: medium) |
#### 처리 규칙 (Business Rules)
1. F-001(할일 생성), F-004(할일 수정)의 priority 필드로 처리된다.
2. UI에서 색상으로 구분: high=빨강, medium=노랑, low=파랑.
#### 출력 (Outputs)
F-001, F-004의 출력과 동일
#### 수락 기준 (Acceptance Criteria)
- [ ] 할일 생성/수정 시 우선순위를 선택할 수 있다
- [ ] 우선순위별 색상 구분이 된다 (high=빨강, medium=노랑, low=파랑)
- [ ] 기본 우선순위는 medium이다
- [ ] 목록에서 우선순위별 필터링이 가능하다
---
### F-015: 마감일 설정
- **설명**: 할일에 마감일을 설정한다. 달력 UI로 날짜를 선택한다.
- **우선순위**: Must
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| due_date | datetime | N | ISO 8601 형식 | 마감일 |
#### 처리 규칙 (Business Rules)
1. F-001(할일 생성), F-004(할일 수정)의 due_date 필드로 처리된다.
2. null을 전송하면 마감일을 해제한다.
#### 출력 (Outputs)
F-001, F-004의 출력과 동일
#### 수락 기준 (Acceptance Criteria)
- [ ] 달력 UI로 마감일을 선택할 수 있다
- [ ] 마감일이 설정된 할일에 날짜가 표시된다
- [ ] 마감일을 해제할 수 있다
---
### F-016: 마감일 알림 표시
- **설명**: 마감일이 임박하거나 초과한 할일에 시각적 알림을 표시한다.
- **우선순위**: Must
#### 입력 (Inputs)
없음 (프론트엔드 렌더링 로직)
#### 처리 규칙 (Business Rules)
1. 마감일 1일 이내: 주황색 "임박" 뱃지 표시
2. 마감일 초과: 빨간색 "초과" 뱃지 표시
3. 마감일 3일 이내: 노란색 "곧 마감" 표시
4. 완료된 할일은 알림을 표시하지 않는다.
#### 출력 (Outputs)
UI 렌더링 (API 응답 없음)
#### 수락 기준 (Acceptance Criteria)
- [ ] 마감 3일 이내 할일에 "곧 마감" 표시가 된다
- [ ] 마감 1일 이내 할일에 "임박" 표시가 된다
- [ ] 마감 초과 할일에 "초과" 표시가 된다
- [ ] 완료된 할일에는 알림이 표시되지 않는다
- [ ] 뱃지 색상이 긴급도에 따라 구분된다 (노랑/주황/빨강)
---
### F-017: 검색
- **설명**: 제목, 내용, 태그를 기반으로 할일을 검색한다.
- **우선순위**: Should
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| q | string | Y | 1~200자 | 검색 키워드 |
| page | integer | N | >= 1, 기본: 1 | 페이지 번호 |
| limit | integer | N | 1~100, 기본: 20 | 페이지당 항목 수 |
#### 처리 규칙 (Business Rules)
1. MongoDB text index를 활용한 전문 검색을 수행한다.
2. 검색 대상: title, content, tags 필드.
3. 검색 결과는 관련도(text score) 기준으로 정렬한다.
4. 검색어가 태그와 정확히 일치하는 경우 해당 태그의 할일을 우선 표시한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 200 OK | `{"items": [...], "total": N, "query": "...", "page": N, "limit": N}` |
| 검색어 누락 | 422 Unprocessable Entity | `{"detail": "검색어를 입력해주세요"}` |
#### 수락 기준 (Acceptance Criteria)
- [ ] 헤더 검색바에 키워드를 입력하여 검색할 수 있다
- [ ] 제목에 포함된 키워드로 검색된다
- [ ] 내용에 포함된 키워드로 검색된다
- [ ] 태그명으로 검색된다
- [ ] 검색 결과가 관련도순으로 정렬된다
- [ ] 검색 결과가 없을 때 "결과 없음" 메시지가 표시된다
- [ ] 검색 결과에서 검색어가 하이라이트된다
---
### F-018: 대시보드 통계
- **설명**: 할일 현황을 한눈에 파악할 수 있는 대시보드 통계를 제공한다.
- **우선순위**: Should
#### 입력 (Inputs)
없음
#### 처리 규칙 (Business Rules)
1. 통계 항목:
- 전체 할일 수, 완료 수, 미완료 수
- 완료율 (%)
- 카테고리별 할일 분포 (도넛 차트)
- 우선순위별 현황 (가로 막대 차트)
- 마감 임박 할일 목록 (상위 5개)
2. Redis에 60초 TTL로 캐싱한다.
3. 캐시 무효화: 할일 CUD 작업 시.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 200 OK | 아래 JSON 구조 참고 |
```json
{
"overview": {
"total": 50,
"completed": 30,
"incomplete": 20,
"completion_rate": 60.0
},
"by_category": [
{"category_id": "...", "name": "업무", "color": "#EF4444", "count": 15}
],
"by_priority": {
"high": 10,
"medium": 25,
"low": 15
},
"upcoming_deadlines": [
{"id": "...", "title": "...", "due_date": "...", "priority": "high"}
]
}
```
#### 수락 기준 (Acceptance Criteria)
- [ ] 전체/완료/미완료 수가 카드 형태로 표시된다
- [ ] 완료율이 퍼센트로 표시된다
- [ ] 카테고리별 할일 분포가 도넛 차트로 표시된다
- [ ] 우선순위별 현황이 막대 차트로 표시된다
- [ ] 마감 임박 할일 상위 5개가 리스트로 표시된다
- [ ] 데이터가 없을 때 빈 상태 안내가 표시된다
---
### F-019: 일괄 완료 처리
- **설명**: 여러 할일을 선택하여 한번에 완료 처리한다.
- **우선순위**: Should
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| action | string | Y | "complete" | 작업 유형 |
| ids | string[] | Y | 1개 이상의 유효한 ObjectId | 대상 할일 ID 배열 |
#### 처리 규칙 (Business Rules)
1. 선택된 모든 할일의 completed를 true로 설정한다.
2. 각 항목의 updated_at을 갱신한다.
3. 존재하지 않는 ID는 무시하고 나머지를 처리한다.
4. 처리된 수와 실패 수를 반환한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 200 OK | `{"action": "complete", "processed": 5, "failed": 0}` |
| ID 누락 | 422 Unprocessable Entity | `{"detail": "대상 할일을 선택해주세요"}` |
#### 수락 기준 (Acceptance Criteria)
- [ ] 체크박스로 여러 할일을 선택할 수 있다
- [ ] "일괄 완료" 버튼으로 선택된 할일을 한번에 완료할 수 있다
- [ ] 처리 결과(성공 수)가 토스트로 표시된다
- [ ] 처리 후 목록이 갱신된다
---
### F-020: 일괄 삭제
- **설명**: 여러 할일을 선택하여 한번에 삭제한다.
- **우선순위**: Should
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| action | string | Y | "delete" | 작업 유형 |
| ids | string[] | Y | 1개 이상의 유효한 ObjectId | 대상 할일 ID 배열 |
#### 처리 규칙 (Business Rules)
1. 선택된 모든 할일을 물리 삭제한다.
2. 삭제 전 확인 다이얼로그를 표시한다.
3. 존재하지 않는 ID는 무시한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 200 OK | `{"action": "delete", "processed": 3, "failed": 0}` |
| ID 누락 | 422 Unprocessable Entity | `{"detail": "대상 할일을 선택해주세요"}` |
#### 수락 기준 (Acceptance Criteria)
- [ ] "일괄 삭제" 버튼으로 선택된 할일을 한번에 삭제할 수 있다
- [ ] 삭제 전 "N개의 할일을 삭제하시겠습니까?" 확인 다이얼로그가 표시된다
- [ ] 처리 결과가 토스트로 표시된다
- [ ] 삭제 후 목록이 갱신된다
---
### F-021: 일괄 카테고리 변경
- **설명**: 여러 할일을 선택하여 한번에 카테고리를 변경한다.
- **우선순위**: Should
#### 입력 (Inputs)
| 필드명 | 타입 | 필수 | 검증 규칙 | 설명 |
|--------|------|------|-----------|------|
| action | string | Y | "move_category" | 작업 유형 |
| ids | string[] | Y | 1개 이상의 유효한 ObjectId | 대상 할일 ID 배열 |
| category_id | string \| null | Y | 유효한 카테고리 ObjectId 또는 null | 변경할 카테고리 |
#### 처리 규칙 (Business Rules)
1. 선택된 모든 할일의 category_id를 지정된 값으로 변경한다.
2. category_id가 null이면 "미분류"로 설정한다.
3. 대상 카테고리의 존재를 검증한다.
#### 출력 (Outputs)
| 상황 | HTTP 상태 | 응답 |
|------|-----------|------|
| 성공 | 200 OK | `{"action": "move_category", "processed": 4, "failed": 0}` |
| 카테고리 미존재 | 404 Not Found | `{"detail": "카테고리를 찾을 수 없습니다"}` |
#### 수락 기준 (Acceptance Criteria)
- [ ] 카테고리 선택 드롭다운으로 일괄 변경할 수 있다
- [ ] "미분류"로 일괄 변경할 수 있다
- [ ] 처리 결과가 토스트로 표시된다
- [ ] 변경 후 목록이 갱신된다
---
## 3. API 엔드포인트 요약
| Method | Path | 기능 ID | 설명 |
|--------|------|---------|------|
| POST | `/api/todos` | F-001 | 할일 생성 |
| GET | `/api/todos` | F-002 | 할일 목록 조회 (필터/정렬/페이지네이션) |
| GET | `/api/todos/{id}` | F-003 | 할일 상세 조회 |
| PUT | `/api/todos/{id}` | F-004 | 할일 수정 |
| DELETE | `/api/todos/{id}` | F-005 | 할일 삭제 |
| PATCH | `/api/todos/{id}/toggle` | F-006 | 할일 완료 토글 |
| POST | `/api/categories` | F-007 | 카테고리 생성 |
| GET | `/api/categories` | F-008 | 카테고리 목록 조회 |
| PUT | `/api/categories/{id}` | F-009 | 카테고리 수정 |
| DELETE | `/api/categories/{id}` | F-010 | 카테고리 삭제 |
| GET | `/api/tags` | F-013 | 태그 목록 조회 |
| GET | `/api/search` | F-017 | 검색 |
| GET | `/api/dashboard/stats` | F-018 | 대시보드 통계 |
| POST | `/api/todos/batch` | F-019, F-020, F-021 | 일괄 작업 (완료/삭제/카테고리 변경) |