feat: 일정 주간/월간 뷰 전환 추가

상단 토글(주간/월간)로 보기 전환. 월간은 전통 달력 그리드(주×요일).

- scheduleDate: firstOfMonth/addMonths/monthLabel/monthGridWeeks(+MonthDay) 추가
- schedule.store: viewMode + anchor 기반으로 주간/월간 범위·라벨·네비 일원화
  (goPrev/goNext 가 모드별로 주/월 이동, setViewMode 시 범위 재조회)
- ScheduleMonthGrid: 주 단위 다중일 막대(레인 패킹) + 초과 '+N',
  카테고리 색·유형 필터·오늘/주말/이전·다음달 표기, 막대 클릭 시 상세 패널
- SchedulePage: SegmentedTabs 토글 + 주간/월간 조건부 렌더

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-23 12:45:52 +09:00
parent 3f134ccca3
commit 4ff746638c
4 changed files with 447 additions and 26 deletions
+29 -6
View File
@@ -1,13 +1,15 @@
<script setup lang="ts">
// 일정 — 전사 공유 주간 타임라인(간트형) 캘린더
// 일정 — 전사 공유 캘린더(주간 간트 / 월간 달력 그리드 전환)
import { computed, onMounted, ref } from 'vue'
import AppShell from '@/layouts/AppShell.vue'
import ScheduleTimeline from '@/components/schedule/ScheduleTimeline.vue'
import ScheduleMonthGrid from '@/components/schedule/ScheduleMonthGrid.vue'
import ScheduleDetailPanel from '@/components/schedule/ScheduleDetailPanel.vue'
import ScheduleEventModal from '@/components/schedule/ScheduleEventModal.vue'
import ScheduleCategoryModal from '@/components/schedule/ScheduleCategoryModal.vue'
import ScheduleTypeFilter from '@/components/schedule/ScheduleTypeFilter.vue'
import { useScheduleStore } from '@/stores/schedule.store'
import SegmentedTabs from '@/components/common/SegmentedTabs.vue'
import { useScheduleStore, type ScheduleViewMode } from '@/stores/schedule.store'
import { useDialogStore } from '@/stores/dialog.store'
import type {
ApiScheduleEvent,
@@ -18,6 +20,12 @@ import type {
const store = useScheduleStore()
const dialog = useDialogStore()
// 보기 모드 토글 옵션
const VIEW_OPTIONS: { value: ScheduleViewMode; label: string }[] = [
{ value: 'week', label: '주간' },
{ value: 'month', label: '월간' },
]
const selId = ref<string | null>(null)
const eventModal = ref<{ init: ApiScheduleEvent | null } | null>(null)
const catModal = ref<{ init: import('@/types/schedule').ApiScheduleCategory | null } | null>(null)
@@ -107,8 +115,8 @@ async function onDeleteCategory(id: string): Promise<void> {
<div class="navgroup">
<button
class="navbtn"
title="이전 주"
@click="store.goPrevWeek()"
:title="store.viewMode === 'week' ? '이전 주' : '이전 달'"
@click="store.goPrev()"
>
<svg
viewBox="0 0 24 24"
@@ -121,8 +129,8 @@ async function onDeleteCategory(id: string): Promise<void> {
</button>
<button
class="navbtn"
title="다음 주"
@click="store.goNextWeek()"
:title="store.viewMode === 'week' ? '다음 주' : '다음 달'"
@click="store.goNext()"
>
<svg
viewBox="0 0 24 24"
@@ -136,6 +144,11 @@ async function onDeleteCategory(id: string): Promise<void> {
</div>
<span class="range">{{ store.rangeLabel }}</span>
</div>
<SegmentedTabs
:model-value="store.viewMode"
:options="VIEW_OPTIONS"
@update:model-value="store.setViewMode"
/>
<div class="spacer" />
<ScheduleTypeFilter
:categories="store.categories"
@@ -166,6 +179,7 @@ async function onDeleteCategory(id: string): Promise<void> {
<div class="content-row">
<div class="viewport">
<ScheduleTimeline
v-if="store.viewMode === 'week'"
:categories="store.categories"
:events="store.events"
:days="store.days"
@@ -174,6 +188,15 @@ async function onDeleteCategory(id: string): Promise<void> {
@select="selId = $event"
@add-category="openNewCategory"
/>
<ScheduleMonthGrid
v-else
:weeks="store.monthWeeks"
:events="store.events"
:categories="store.categories"
:active-type-ids="store.activeTypeIds"
:sel-id="selId"
@select="selId = $event"
/>
</div>
<div
v-if="selEvent"