Skip to content

claude-code-expert/todo-app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

56 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

TODO App - Ticket-based Kanban Board

ν‹°μΌ“ 기반 칸반 λ³΄λ“œ ν•  일 관리 μ• ν”Œλ¦¬μΌ€μ΄μ…˜ (MVP) https://ticket-kanban.vercel.app

μŠ€ν¬λ¦°μƒ·

ν΄λ‘œλ“œ μ½”λ“œ λ§ˆμŠ€ν„° μ±… 예제둜 6μž₯에 ν•΄λ‹Ή ν•˜λŠ” 예제 μ‚¬μ΄νŠΈ 이닀.

단일 μ‚¬μš©μž ν™˜κ²½μ—μ„œ 티켓을 μƒμ„±ν•˜κ³ , 4개 칼럼(Backlog, TODO, In Progress, Done) κ°„ λ“œλž˜κ·Έμ•€λ“œλ‘­μœΌλ‘œ μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜λŠ” 칸반 λ³΄λ“œ 앱이닀.


μ£Όμš” κΈ°λŠ₯

ID κΈ°λŠ₯ μ„€λͺ…
FR-001 ν‹°μΌ“ 생성 제λͺ©, μ„€λͺ…, μš°μ„ μˆœμœ„, μ‹œμž‘μ˜ˆμ •μΌ, μ’…λ£Œμ˜ˆμ •μΌ μž…λ ₯. Backlog 맨 μœ„μ— 배치
FR-002 λ³΄λ“œ 쑰회 4개 μΉΌλŸΌλ³„ κ·Έλ£Ήν™”, position μ˜€λ¦„μ°¨μˆœ μ •λ ¬
FR-003 ν‹°μΌ“ 상세 μΉ΄λ“œ 클릭 μ‹œ λͺ¨λ‹¬λ‘œ 전체 정보 ν‘œμ‹œ
FR-004 ν‹°μΌ“ μˆ˜μ • 제λͺ©, μ„€λͺ…, μš°μ„ μˆœμœ„, μ‹œμž‘μ˜ˆμ •μΌ, μ’…λ£Œμ˜ˆμ •μΌ μˆ˜μ •
FR-005 ν‹°μΌ“ μ™„λ£Œ Done 이동 μ‹œ completedAt μžλ™ μ„€μ •, 24μ‹œκ°„ κ²½κ³Ό μ‹œ λ³΄λ“œμ—μ„œ μ œμ™Έ
FR-006 ν‹°μΌ“ μ‚­μ œ 확인 λ‹€μ΄μ–Όλ‘œκ·Έ ν›„ 영ꡬ μ‚­μ œ
FR-007 λ“œλž˜κ·Έμ•€λ“œλ‘­ 칼럼 κ°„/λ‚΄ 이동, startedAt/completedAt μžλ™ 관리
FR-008 μ˜€λ²„λ“€ νŒμ • μ’…λ£Œμ˜ˆμ •μΌ 초과 + λ―Έμ™„λ£Œ 티켓에 μ‹œκ°μ  κ²½κ³ 

기술 μŠ€νƒ

ν”„λŸ°νŠΈμ—”λ“œ

기술 버전 μš©λ„
Next.js 15.x ν’€μŠ€νƒ ν”„λ ˆμž„μ›Œν¬ (App Router)
React 19.x UI λ Œλ”λ§
TypeScript 5.x νƒ€μž… μ•ˆμ „μ„± (strict mode)
Tailwind CSS 4.x μœ ν‹Έλ¦¬ν‹° 기반 μŠ€νƒ€μΌλ§
@dnd-kit core 6.x / sortable 8.x λ“œλž˜κ·Έμ•€λ“œλ‘­

λ°±μ—”λ“œ

기술 버전 μš©λ„
Drizzle ORM 0.38.x DB 쿼리, μŠ€ν‚€λ§ˆ 관리
@vercel/postgres latest Vercel Postgres μ—°κ²° λ“œλΌμ΄λ²„
Zod 3.x μš”μ²­/폼 데이터 검증 (ν”„λ‘ νŠΈ/λ°±μ—”λ“œ 곡유)

개발 도ꡬ

기술 μš©λ„
Jest + React Testing Library TDD ν…ŒμŠ€νŠΈ
ESLint 린트
Prettier μ½”λ“œ ν¬λ§·νŒ…
Drizzle Kit DB λ§ˆμ΄κ·Έλ ˆμ΄μ…˜

κ΅¬ν˜„ ν˜„ν™©

λ°±μ—”λ“œ (7/7 API μ™„λ£Œ)

μ—”λ“œν¬μΈνŠΈ ν…ŒμŠ€νŠΈ μƒνƒœ
POST /api/tickets 11 passed βœ…
GET /api/tickets 8 passed βœ…
GET /api/tickets/:id 3 passed βœ…
PATCH /api/tickets/:id 8 passed βœ…
PATCH /api/tickets/:id/complete 5 passed βœ…
DELETE /api/tickets/:id 2 passed βœ…
PATCH /api/tickets/reorder 10 passed βœ…

ν”„λŸ°νŠΈμ—”λ“œ (16/16 μ»΄ν¬λ„ŒνŠΈ μ™„λ£Œ)

Phase μ»΄ν¬λ„ŒνŠΈ ν…ŒμŠ€νŠΈ μƒνƒœ
1 Button, Badge, Modal, ConfirmDialog 22 passed βœ…
2 TicketCard, ColumnHeader, Column, Board 25 passed βœ…
3 TicketDetailView, TicketForm, TicketModal 17 passed βœ…
4 ticketApi, useTickets 21 passed βœ…
5 BoardHeader, FilterBar, BoardContainer, page.tsx 16 passed βœ…

전체: 26 test suites, 169 tests passed


ν”„λ‘œμ νŠΈ ꡬ쑰

tika/
β”œβ”€β”€ app/                              # Next.js App Router
β”‚   β”œβ”€β”€ api/                          # λ°±μ—”λ“œ: API Route Handlers
β”‚   β”‚   └── tickets/
β”‚   β”‚       β”œβ”€β”€ route.ts              # GET /api/tickets, POST /api/tickets
β”‚   β”‚       β”œβ”€β”€ [id]/
β”‚   β”‚       β”‚   β”œβ”€β”€ route.ts          # GET, PATCH, DELETE /api/tickets/:id
β”‚   β”‚       β”‚   └── complete/
β”‚   β”‚       β”‚       └── route.ts      # PATCH /api/tickets/:id/complete
β”‚   β”‚       └── reorder/
β”‚   β”‚           └── route.ts          # PATCH /api/tickets/reorder
β”‚   β”‚
β”‚   β”œβ”€β”€ page.tsx                      # 메인 νŽ˜μ΄μ§€ (μ„œλ²„ μ»΄ν¬λ„ŒνŠΈ)
β”‚   β”œβ”€β”€ layout.tsx                    # 루트 λ ˆμ΄μ•„μ›ƒ
β”‚   └── globals.css                   # λ””μžμΈ μ‹œμŠ€ν…œ + CSS λ³€μˆ˜
β”‚
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ server/                       # λ°±μ—”λ“œ 둜직 (μ„œλ²„μ—μ„œλ§Œ μ‹€ν–‰)
β”‚   β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”‚   β”œβ”€β”€ ticketService.ts      # λΉ„μ¦ˆλ‹ˆμŠ€ 둜직
β”‚   β”‚   β”‚   └── index.ts              # μ„œλΉ„μŠ€ export
β”‚   β”‚   └── db/
β”‚   β”‚       β”œβ”€β”€ index.ts              # Drizzle ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™”
β”‚   β”‚       β”œβ”€β”€ schema.ts             # DB μŠ€ν‚€λ§ˆ μ •μ˜
β”‚   β”‚       └── seed.ts               # μ‹œλ“œ 데이터
β”‚   β”‚
β”‚   β”œβ”€β”€ client/                       # ν”„λŸ°νŠΈμ—”λ“œ 둜직 (λΈŒλΌμš°μ €μ—μ„œ μ‹€ν–‰)
β”‚   β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”‚   β”œβ”€β”€ board/                # BoardContainer, Board, Column, ColumnHeader
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ BoardContainer.tsx  # 메인 μ»¨ν…Œμ΄λ„ˆ (ν•„ν„°, λͺ¨λ‹¬, CRUD)
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ BoardHeader.tsx     # 상단 헀더 (타이틀, μƒˆ 업무 λ²„νŠΌ)
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ FilterBar.tsx       # ν•„ν„° λ°” (이번주 업무, 일정 초과)
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Board.tsx           # 4칼럼 λ³΄λ“œ λ ˆμ΄μ•„μ›ƒ
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ Column.tsx          # 단일 칼럼 (DnD λ“œλ‘­ μ˜μ—­)
β”‚   β”‚   β”‚   β”‚   └── ColumnHeader.tsx    # 칼럼 헀더 (이름 + μΉ΄λ“œ 수)
β”‚   β”‚   β”‚   β”œβ”€β”€ ticket/               # TicketCard, TicketModal, TicketForm
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ TicketCard.tsx      # κ°œλ³„ μΉ΄λ“œ (DnD λ“œλž˜κ·Έ μ†ŒμŠ€)
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ TicketModal.tsx     # 상세/μˆ˜μ • λͺ¨λ‹¬
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ TicketForm.tsx      # 생성/μˆ˜μ • 폼
β”‚   β”‚   β”‚   β”‚   └── TicketDetailView.tsx # 읽기 μ „μš© ν•„λ“œ
β”‚   β”‚   β”‚   └── ui/                   # 곡톡 UI μ»΄ν¬λ„ŒνŠΈ
β”‚   β”‚   β”‚       β”œβ”€β”€ Button.tsx          # variant, size, isLoading
β”‚   β”‚   β”‚       β”œβ”€β”€ Modal.tsx           # ESC λ‹«κΈ°, λ°”κΉ₯ 클릭, 슀크둀 잠금
β”‚   β”‚   β”‚       β”œβ”€β”€ Badge.tsx           # PriorityBadge, DueDateBadge
β”‚   β”‚   β”‚       └── ConfirmDialog.tsx   # μ‚­μ œ 확인 λ‹€μ΄μ–Όλ‘œκ·Έ
β”‚   β”‚   β”œβ”€β”€ hooks/
β”‚   β”‚   β”‚   └── useTickets.ts         # ν‹°μΌ“ CRUD + λ³΄λ“œ μƒνƒœ 관리
β”‚   β”‚   └── api/
β”‚   β”‚       └── ticketApi.ts          # API 호좜 ν•¨μˆ˜ (fetch 래퍼)
β”‚   β”‚
β”‚   └── shared/                       # ν”„λ‘ νŠΈ/λ°±μ—”λ“œ 곡유 μ½”λ“œ
β”‚       β”œβ”€β”€ types/
β”‚       β”‚   └── index.ts              # Ticket, BoardData, API νƒ€μž…
β”‚       β”œβ”€β”€ validations/
β”‚       β”‚   └── ticket.ts             # Zod μŠ€ν‚€λ§ˆ (폼 + API 검증)
β”‚       └── errors/
β”‚           └── index.ts              # TicketNotFoundError λ“±
β”‚
β”œβ”€β”€ __tests__/                        # ν…ŒμŠ€νŠΈ μ½”λ“œ (169 tests)
β”‚   β”œβ”€β”€ api/                          # API Route + ticketApi ν…ŒμŠ€νŠΈ
β”‚   β”œβ”€β”€ services/                     # μ„œλΉ„μŠ€ 톡합 ν…ŒμŠ€νŠΈ (@jest-environment node)
β”‚   β”œβ”€β”€ components/                   # μ»΄ν¬λ„ŒνŠΈ ν…ŒμŠ€νŠΈ (jsdom)
β”‚   └── hooks/                        # Hook ν…ŒμŠ€νŠΈ
β”‚
β”œβ”€β”€ docs/                             # ν”„λ‘œμ νŠΈ λͺ…μ„Έ λ¬Έμ„œ
β”œβ”€β”€ drizzle/                          # Drizzle λ§ˆμ΄κ·Έλ ˆμ΄μ…˜
β”œβ”€β”€ CLAUDE.md                         # Claude Code ν”„λ‘œμ νŠΈ μ„€μ •
β”œβ”€β”€ CHANGELOG.md                      # 개발 νžˆμŠ€ν† λ¦¬
β”œβ”€β”€ jest.config.ts                    # Jest μ„€μ • (jsdom + runInBand)
└── package.json

계측 κ°„ 경계 κ·œμΉ™

κ·œμΉ™ μ„€λͺ…
src/server/ β†’ src/client/ import κΈˆμ§€ λ°±μ—”λ“œμ—μ„œ ν”„λŸ°νŠΈμ—”λ“œ μ½”λ“œ μ°Έμ‘° λΆˆκ°€
src/client/ β†’ src/server/ import κΈˆμ§€ ν”„λŸ°νŠΈμ—”λ“œμ—μ„œ λ°±μ—”λ“œ μ½”λ“œ 직접 μ°Έμ‘° λΆˆκ°€
src/shared/만 μ–‘μͺ½μ—μ„œ μ°Έμ‘° κ°€λŠ₯ νƒ€μž…, Zod μŠ€ν‚€λ§ˆ, μƒμˆ˜λ§Œ 곡유
app/page.tsx만 src/server/ 직접 μ°Έμ‘° μ„œλ²„ μ»΄ν¬λ„ŒνŠΈμ—μ„œ initialData 전달

API μ—”λ“œν¬μΈνŠΈ

# λ©”μ„œλ“œ 경둜 μ„€λͺ…
1 POST /api/tickets ν‹°μΌ“ 생성
2 GET /api/tickets 전체 λ³΄λ“œ 쑰회
3 GET /api/tickets/:id ν‹°μΌ“ 상세 쑰회
4 PATCH /api/tickets/:id ν‹°μΌ“ μˆ˜μ •
5 PATCH /api/tickets/:id/complete ν‹°μΌ“ μ™„λ£Œ
6 DELETE /api/tickets/:id ν‹°μΌ“ μ‚­μ œ
7 PATCH /api/tickets/reorder μƒνƒœ/μˆœμ„œ λ³€κ²½ (DnD)

Done으둜의 이동은 /complete, κ·Έ μ™Έ λͺ¨λ“  이동은 /reorderλ₯Ό μ‚¬μš©ν•œλ‹€.


μ‹œμž‘ν•˜κΈ°

1. μ˜μ‘΄μ„± μ„€μΉ˜

npm install

2. ν™˜κ²½ λ³€μˆ˜ μ„€μ •

cp .env.example .env.local

.env.local νŒŒμΌμ— DATABASE_URL 값을 μ„€μ •ν•œλ‹€.

둜컬 PostgreSQL μ‚¬μš© μ‹œ:

DATABASE_URL=postgresql://username:password@localhost:5432/tika

Vercel Postgres μ‚¬μš© μ‹œ:

vercel env pull .env.local

3. DB λ§ˆμ΄κ·Έλ ˆμ΄μ…˜

npm run db:generate   # λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 파일 생성
npm run db:migrate    # λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 적용

4. (선택) μ‹œλ“œ 데이터

npm run db:seed

5. 개발 μ„œλ²„ μ‹€ν–‰

npm run dev

http://localhost:3000 μ—μ„œ 확인


μ£Όμš” λͺ…λ Ήμ–΄

λͺ…λ Ήμ–΄ μ„€λͺ…
npm run dev 개발 μ„œλ²„ μ‹€ν–‰
npm run build ν”„λ‘œλ•μ…˜ λΉŒλ“œ
npm run test 전체 ν…ŒμŠ€νŠΈ μ‹€ν–‰ (169 tests, --runInBand)
npm run test:components μ»΄ν¬λ„ŒνŠΈ ν…ŒμŠ€νŠΈλ§Œ (80 tests)
npm run test:watch ν…ŒμŠ€νŠΈ κ°μ‹œ λͺ¨λ“œ
npx tsc --noEmit νƒ€μž… 체크
npm run db:generate DB λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 생성
npm run db:migrate DB λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 적용
npm run db:studio Drizzle Studio (DB GUI)
npm run db:seed μ‹œλ“œ 데이터 μ‚½μž…
npm run lint ESLint 검사

ν”„λ‘œμ νŠΈ λ¬Έμ„œ

λ¬Έμ„œ μ„€λͺ…
PRD.md μ œν’ˆ μš”κ΅¬μ‚¬ν•­ (μ‹œλ‚˜λ¦¬μ˜€, μ™€μ΄μ–΄ν”„λ ˆμž„, FR/NFR)
TRD.md 기술 μš”κ΅¬μ‚¬ν•­ (μ•„ν‚€ν…μ²˜, 기술 μŠ€νƒ, 디렉토리 ꡬ쑰)
REQUIREMENTS.md 상세 μš”κ΅¬μ‚¬ν•­ (FR, NFR, US, 좔적 맀트릭슀)
API_SPEC.md 7개 REST API μ—”λ“œν¬μΈνŠΈ λͺ…μ„Έ
DATA_MODEL.md DB μŠ€ν‚€λ§ˆ, Drizzle μ •μ˜, λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™
COMPONENT_SPEC.md μ»΄ν¬λ„ŒνŠΈ 계측, Props, 이벀트 흐름, Hook λͺ…μ„Έ
TEST_CASES.md TDD용 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€ (API + μ»΄ν¬λ„ŒνŠΈ + 톡합)
FRONTEND_TASKS.md ν”„λŸ°νŠΈμ—”λ“œ κ΅¬ν˜„ μˆœμ„œ + TDD 체크리슀트

ν…ŒμŠ€νŠΈ ꡬ쑰

__tests__/
β”œβ”€β”€ api/                    # API ν…ŒμŠ€νŠΈ
β”‚   β”œβ”€β”€ tickets.test.ts       # POST /api/tickets Route (@jest-environment node)
β”‚   β”œβ”€β”€ tickets-detail.test.ts  # GET/PATCH/DELETE Route (@jest-environment node)
β”‚   β”œβ”€β”€ tickets-reorder.test.ts # PATCH /api/tickets/reorder (@jest-environment node)
β”‚   └── ticketApi.test.ts     # fetch 래퍼 ν…ŒμŠ€νŠΈ (jsdom)
β”œβ”€β”€ services/               # μ„œλΉ„μŠ€ 톡합 ν…ŒμŠ€νŠΈ (@jest-environment node)
β”‚   β”œβ”€β”€ ticketService.test.ts   # 생성 (TC-API-001)
β”‚   β”œβ”€β”€ ticketService.board.test.ts  # λ³΄λ“œ 쑰회 (TC-API-002)
β”‚   └── ...                 # getById, update, complete, delete, reorder, overdue
β”œβ”€β”€ components/             # μ»΄ν¬λ„ŒνŠΈ ν…ŒμŠ€νŠΈ (jsdom)
β”‚   β”œβ”€β”€ Button.test.tsx       # Phase 1
β”‚   β”œβ”€β”€ TicketCard.test.tsx   # Phase 2
β”‚   β”œβ”€β”€ TicketForm.test.tsx   # Phase 3
β”‚   β”œβ”€β”€ BoardContainer.test.tsx # Phase 5
β”‚   └── ...
└── hooks/
    └── useTickets.test.ts    # Phase 4
  • μ„œλΉ„μŠ€/API Route ν…ŒμŠ€νŠΈ: μ‹€μ œ DB μ—°κ²° ν•„μš” β†’ @jest-environment node + --runInBand
  • μ»΄ν¬λ„ŒνŠΈ/Hook ν…ŒμŠ€νŠΈ: mock 기반 β†’ jsdom ν™˜κ²½ (κΈ°λ³Έκ°’)

개발 방식

이 ν”„λ‘œμ νŠΈλŠ” Claude Code와 ν•¨κ»˜ SDD(Specification-Driven Development) + TDD λ°©μ‹μœΌλ‘œ κ°œλ°œλ˜μ—ˆλ‹€.

# Claude Code μ‹€ν–‰
claude

# CLAUDE.mdλ₯Ό μ°Έκ³ ν•˜λ„λ‘ μš”μ²­
> CLAUDE.mdλ₯Ό 읽고 ν”„λ‘œμ νŠΈ μ»¨ν…μŠ€νŠΈλ₯Ό νŒŒμ•…ν•΄μ€˜

개발 μˆœμ„œ:

  1. λͺ…μ„Έ μž‘μ„±: PRD β†’ TRD β†’ REQUIREMENTS β†’ API_SPEC β†’ DATA_MODEL β†’ COMPONENT_SPEC β†’ TEST_CASES
  2. λ°±μ—”λ“œ TDD: TEST_CASES.md의 TC-API 기반으둜 Red β†’ Green β†’ Refactor
  3. ν”„λŸ°νŠΈμ—”λ“œ TDD: FRONTEND_TASKS.md μˆœμ„œλŒ€λ‘œ Bottom-up κ΅¬ν˜„ (UI β†’ Board β†’ Ticket β†’ Data β†’ Container)

λΌμ΄μ„ μŠ€

MIT

Releases

Packages

Contributors

Languages