diff --git a/HANDOFF.md b/HANDOFF.md index 568d2b2..4673a55 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -4,7 +4,7 @@ - 프로젝트명: 10 Minute Planner 웹 UI - 기술 스택: Vue 3 + Vite + TailwindCSS + JavaScript -- 현재 기준 버전: `v0.1.53` 준비 중 +- 현재 기준 버전: `v0.1.54` 준비 중 - Git 원격 저장소: `https://git.sori.studio/zenn/planner.sori.studio.git` ## 기준 디자인 @@ -49,6 +49,8 @@ - `TIME TABLE`은 우클릭 드래그 시 선택된 블록을 지우는 방식으로도 편집할 수 있다. - `TIME TABLE` 숫자 영역은 선택/드래그로 텍스트가 잡히지 않도록 막아두었다. - `TOTAL TIME`은 타임테이블에서 선택된 블록 수를 기준으로 자동 계산된다. +- 오른쪽 패널에는 특정 날짜의 `TIME TABLE`을 다른 날짜로 그대로 복사하는 카드가 추가되었다. +- 모바일과 태블릿처럼 `TIME TABLE`이 아래로 내려가는 구간에서는 6칸 그리드가 남는 폭을 더 넓게 채우도록 조정했다. - 달력은 연/월 이동이 가능하며, 현재 보이는 월과 선택된 날짜 상태를 분리해서 관리한다. - 달력 상단은 월 좌우 화살표, 클릭형 연도 선택, `TODAY` 버튼 구조로 동작한다. - 입력 내용이 있는 날짜는 달력 하단에 빨간 점으로 표시된다. diff --git a/TODO.md b/TODO.md index 9eef43f..c5aa0bd 100644 --- a/TODO.md +++ b/TODO.md @@ -18,6 +18,7 @@ - [x] `TIME TABLE`을 마우스 드래그로 칠할 수 있게 만든다. - [x] `TIME TABLE` 드래그가 여러 줄을 지나가더라도 시간 흐름 기준으로 연속 선택되도록 처리한다. - [x] 선택된 `TIME TABLE` 구간을 기준으로 `TOTAL TIME`을 자동 계산한다. +- [x] 원하는 날짜의 `TIME TABLE`을 다른 날짜로 복사할 수 있게 한다. ## 2단계: 달력과 이동 기능 diff --git a/package-lock.json b/package-lock.json index 0fa67e3..7700f8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ten-minute-planner", - "version": "0.1.53", + "version": "0.1.54", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ten-minute-planner", - "version": "0.1.53", + "version": "0.1.54", "dependencies": { "vue": "^3.5.13" }, diff --git a/package.json b/package.json index b538445..2b54083 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ten-minute-planner", "private": true, - "version": "0.1.53", + "version": "0.1.54", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.vue b/src/App.vue index 23807c3..022a4ce 100644 --- a/src/App.vue +++ b/src/App.vue @@ -108,6 +108,7 @@ const passwordMessage = ref('') const accountDeleteBusy = ref(false) const accountDeleteMessage = ref('') const carryoverMessage = ref('') +const timetableCopyMessage = ref('') const carryoverCheckPolicy = ref(readCarryoverCheckPolicy()) const carryoverCheckPrompt = ref(null) const guideTooltipResetMessage = ref('') @@ -131,6 +132,10 @@ const adminUsers = ref([]) const adminRecentLogins = ref([]) const adminSelectedUserId = ref(null) const adminUserDetail = ref(null) +const timetableCopyForm = reactive({ + sourceDate: toKey(new Date()), + targetDate: toKey(new Date()), +}) const hours = [ '6', '7', '8', '9', '10', '11', '12', @@ -765,6 +770,7 @@ function shiftDate(amount) { selectedDate.value = next calendarViewDate.value = new Date(next) carryoverMessage.value = '' + timetableCopyMessage.value = '' } function shiftCalendarMonth(amount) { @@ -783,6 +789,7 @@ function selectDate(date) { selectedDate.value = new Date(date) calendarViewDate.value = new Date(date) carryoverMessage.value = '' + timetableCopyMessage.value = '' } function updateComment(record, value) { @@ -969,6 +976,40 @@ function carryIncompleteTasksToNextDay() { : `${nextDateLabel} 빈칸 ${copyCount}개까지만 이월했습니다.` } +function useSelectedDateAsTimetableSource() { + timetableCopyForm.sourceDate = selectedDateKey.value + timetableCopyMessage.value = '' +} + +function useSelectedDateAsTimetableTarget() { + timetableCopyForm.targetDate = selectedDateKey.value + timetableCopyMessage.value = '' +} + +function copyTimetableBetweenDates() { + const sourceKey = timetableCopyForm.sourceDate + const targetKey = timetableCopyForm.targetDate + + if (!sourceKey || !targetKey) { + timetableCopyMessage.value = '복사할 날짜와 붙여넣을 날짜를 모두 선택해 주세요.' + return + } + + const sourceRecord = getPlannerRecord(toDateValue(sourceKey)) + const targetRecord = getPlannerRecord(toDateValue(targetKey)) + const nextTimetable = [...sourceRecord.timetable] + + targetRecord.timetable = nextTimetable + schedulePlannerSyncForRecord(targetRecord) + + const sourceDateLabel = createDateLabel(sourceKey) + const targetDateLabel = createDateLabel(targetKey) + + timetableCopyMessage.value = nextTimetable.some(Boolean) + ? `${sourceDateLabel} 타임테이블을 ${targetDateLabel}에 복사했습니다.` + : `${sourceDateLabel} 타임테이블이 비어 있어 ${targetDateLabel}도 비웠습니다.` +} + function updateMemo(record, { index, value }) { record.memo[index].text = value schedulePlannerSyncForRecord(record) @@ -1325,6 +1366,19 @@ watch( }, ) +watch( + selectedDateKey, + (nextKey, previousKey) => { + if (!timetableCopyForm.targetDate || timetableCopyForm.targetDate === previousKey) { + timetableCopyForm.targetDate = nextKey + } + + if (!timetableCopyForm.sourceDate) { + timetableCopyForm.sourceDate = nextKey + } + }, +) + function fillTaskLabelsWithNumbers(record) { record.tasks.forEach((task, index) => { task.label = createTaskLabel(index) @@ -2957,6 +3011,71 @@ onBeforeUnmount(() => { +
+
+

TIME TABLE 복사

+ +
+ +
+ + + +
+ + + +

+ {{ timetableCopyMessage }} +

+
+
@@ -3101,6 +3220,71 @@ onBeforeUnmount(() => {
+
+
+

TIME TABLE 복사

+ +
+ +
+ + + +
+ + + +

+ {{ timetableCopyMessage }} +

+
+
diff --git a/src/components/PlannerPage.vue b/src/components/PlannerPage.vue index 6ed8c8c..d67fc04 100644 --- a/src/components/PlannerPage.vue +++ b/src/components/PlannerPage.vue @@ -413,11 +413,11 @@ onBeforeUnmount(() => {
-
+
{ v-for="quarter in 6" :key="quarter" :class="props.timetable[index * 6 + quarter - 1] ? 'bg-stone-800/90' : 'bg-transparent'" - class="h-full w-[30px] cursor-crosshair border-r border-dashed border-line transition-colors last:border-r-0 touch-none select-none" + class="h-full cursor-crosshair border-r border-dashed border-line transition-colors last:border-r-0 touch-none select-none" @contextmenu.prevent @pointerdown.prevent="startTimetableDrag(index * 6 + quarter - 1, $event)" @pointerenter="moveTimetableDrag(index * 6 + quarter - 1)"