diff --git a/HANDOFF.md b/HANDOFF.md index 508ebf6..6ae8351 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -4,7 +4,7 @@ - 프로젝트명: 10 Minute Planner 웹 UI - 기술 스택: Vue 3 + Vite + TailwindCSS + JavaScript -- 현재 기준 버전: `v0.0.2` +- 현재 기준 버전: `v0.0.3` ## 기준 디자인 @@ -25,7 +25,9 @@ - Tailwind 설정은 완료되어 있으며, 이 프로젝트의 스타일링 기준으로 유지한다. - 현재 선택 날짜는 시스템 날짜 기준으로 시작한다. - `COMMENT`, `TASKS`, `MEMO`는 화면에서 바로 편집할 수 있다. -- TASKS 체크박스는 토글 가능하지만, `TOTAL TIME`과 타임테이블 계산은 아직 미구현이다. +- TASKS 체크박스는 토글 가능하며, 체크 상태는 바로 시각적으로 반영된다. +- `TIME TABLE`은 드래그로 10분 블록을 연속 선택할 수 있다. +- `TOTAL TIME`은 타임테이블에서 선택된 블록 수를 기준으로 자동 계산된다. ## 확정된 결정사항 diff --git a/TODO.md b/TODO.md index 2838eb0..17c101e 100644 --- a/TODO.md +++ b/TODO.md @@ -15,9 +15,9 @@ - [x] `COMMENT`를 자유 입력 가능한 입력 필드로 바꾼다. - [x] `TASKS` 각 줄을 텍스트 입력 + 체크박스 토글 가능하게 만든다. - [x] `MEMO` 각 줄을 텍스트 입력 가능하게 만든다. -- [ ] `TIME TABLE`을 마우스 드래그로 칠할 수 있게 만든다. -- [ ] `TIME TABLE` 드래그가 여러 줄을 지나가더라도 시간 흐름 기준으로 연속 선택되도록 처리한다. -- [ ] 선택된 `TIME TABLE` 구간을 기준으로 `TOTAL TIME`을 자동 계산한다. +- [x] `TIME TABLE`을 마우스 드래그로 칠할 수 있게 만든다. +- [x] `TIME TABLE` 드래그가 여러 줄을 지나가더라도 시간 흐름 기준으로 연속 선택되도록 처리한다. +- [x] 선택된 `TIME TABLE` 구간을 기준으로 `TOTAL TIME`을 자동 계산한다. ## 2단계: 달력과 이동 기능 diff --git a/package-lock.json b/package-lock.json index bbe0df0..ab0a300 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ten-minute-planner", - "version": "0.0.2", + "version": "0.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ten-minute-planner", - "version": "0.0.2", + "version": "0.0.3", "dependencies": { "vue": "^3.5.13" }, diff --git a/package.json b/package.json index e43041d..2ebebf1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ten-minute-planner", "private": true, - "version": "0.0.2", + "version": "0.0.3", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.vue b/src/App.vue index 9591082..77d088d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -13,11 +13,28 @@ const hours = [ '1', '2', '3', '4', '5', ] +const timetableCellCount = hours.length * 6 + +function createEmptyTimetable() { + return Array.from({ length: timetableCellCount }, () => false) +} + +function createTimetableFromRanges(ranges) { + const timetable = createEmptyTimetable() + + ranges.forEach(([start, end]) => { + for (let index = start; index <= end; index += 1) { + timetable[index] = true + } + }) + + return timetable +} + const plannerSeed = { '2026-04-21': { dday: 'D-12 LAUNCH', comment: '집중 작업 3개만 남기고, 10분 단위로 흐름을 끊지 않기.', - totalTime: '09H 40M', tasks: [ { id: '01', title: '홈 화면 정보 구조 정리', checked: true }, { id: '02', title: '플래너 페이지 헤더 상태 연결', checked: true }, @@ -50,11 +67,15 @@ const plannerSeed = { '다음날 핵심 3개 자동 복사', '미완료 작업만 재정렬', ], + timetable: createTimetableFromRanges([ + [6, 13], + [24, 32], + [42, 47], + ]), }, '2026-04-22': { dday: 'D-11 LAUNCH', comment: '초반 90분은 구현, 후반은 문장과 사용성 정리에 사용.', - totalTime: '08H 20M', tasks: [ { id: '01', title: '통계 섹션 수치 실제 계산 연결', checked: true }, { id: '02', title: '날짜 선택 시 상태 동기화' }, @@ -87,11 +108,14 @@ const plannerSeed = { '오늘 미완료 작업 넘기기', '이번 주 누적 시간 요약', ], + timetable: createTimetableFromRanges([ + [3, 8], + [18, 23], + [54, 62], + ]), }, } -const plannerRecords = reactive(structuredClone(plannerSeed)) - const dateFormatter = new Intl.DateTimeFormat('ko-KR', { year: 'numeric', month: '2-digit', @@ -110,7 +134,6 @@ function buildFallbackRecord(date) { return { dday: 'D-00 FOCUS', comment: '', - totalTime: '00H 00M', tasks: Array.from({ length: 15 }, (_, index) => ({ id: `${index + 1}`.padStart(2, '0'), title: '', @@ -119,9 +142,25 @@ function buildFallbackRecord(date) { memo: ['', '', ''], prevSummary: ['이전 기록 없음', '새로운 흐름 시작', ''], nextFocus: ['다음 집중 블록 준비', '내일의 핵심 3개 정하기', ''], + timetable: createEmptyTimetable(), } } +function normalizeRecord(record) { + return { + ...record, + timetable: Array.isArray(record.timetable) && record.timetable.length === timetableCellCount + ? [...record.timetable] + : createEmptyTimetable(), + } +} + +const plannerRecords = reactive( + Object.fromEntries( + Object.entries(plannerSeed).map(([key, record]) => [key, normalizeRecord(record)]), + ), +) + function getPlannerRecord(date) { const key = toKey(date) @@ -167,6 +206,15 @@ const calendarDays = computed(() => { const completedTasks = computed(() => planner.value.tasks.filter((task) => task.checked).length) const completionRate = computed(() => Math.round((completedTasks.value / planner.value.tasks.length) * 100)) +function formatTotalTime(record) { + const activeCellCount = record.timetable.filter(Boolean).length + const totalMinutes = activeCellCount * 10 + const hoursPart = `${Math.floor(totalMinutes / 60)}`.padStart(2, '0') + const minutesPart = `${totalMinutes % 60}`.padStart(2, '0') + + return `${hoursPart}H ${minutesPart}M` +} + function shiftDate(amount) { const next = new Date(selectedDate.value) next.setDate(next.getDate() + amount) @@ -188,6 +236,10 @@ function toggleTask(record, index) { function updateMemo(record, { index, value }) { record.memo[index] = value } + +function updateTimetable(record, nextTimetable) { + record.timetable = nextTimetable +}