diff --git a/frontend/src/views/CalendarView.vue b/frontend/src/views/CalendarView.vue index b003ba3..a6ac12e 100644 --- a/frontend/src/views/CalendarView.vue +++ b/frontend/src/views/CalendarView.vue @@ -67,6 +67,27 @@ async function loadUpcoming() { onMounted(loadUpcoming); +// ── Month/year picker ────────────────────────────────────────────────────── +const currentViewYear = ref(new Date().getFullYear()); +const currentViewMonth = ref(new Date().getMonth()); +const pickerOpen = ref(false); +const pickerYear = ref(new Date().getFullYear()); +const pickerStyle = ref>({}); +const pickerEl = ref(null); + +const MONTH_NAMES = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"] as const; + +function handleDatesSet(arg: { view: { currentStart: Date } }) { + const d = arg.view.currentStart; + currentViewYear.value = d.getFullYear(); + currentViewMonth.value = d.getMonth(); +} + +function jumpTo(year: number, month: number) { + calendarRef.value?.getApi().gotoDate(new Date(year, month, 1)); + pickerOpen.value = false; +} + // ── Event popover ────────────────────────────────────────────────────────── const popover = ref(null); const popoverStyle = ref>({}); @@ -108,9 +129,32 @@ function onPopoverEdit() { } } -// Close popover on outside click +// Close popover / open picker on outside or title click function onDocClick(e: MouseEvent) { - if (popover.value && popoverEl.value && !popoverEl.value.contains(e.target as Node)) { + const target = e.target as HTMLElement; + // Title click → toggle month/year picker + const titleEl = target.closest(".fc-toolbar-title"); + if (titleEl) { + if (!pickerOpen.value) { + pickerYear.value = currentViewYear.value; + const rect = titleEl.getBoundingClientRect(); + const left = Math.max(8, Math.min(rect.left + rect.width / 2 - 140, window.innerWidth - 296)); + pickerStyle.value = { + position: "fixed", + top: `${rect.bottom + 6}px`, + left: `${left}px`, + zIndex: "9999", + }; + } + pickerOpen.value = !pickerOpen.value; + return; + } + // Close picker on outside click + if (pickerOpen.value && pickerEl.value && !pickerEl.value.contains(target)) { + pickerOpen.value = false; + } + // Close event popover on outside click + if (popover.value && popoverEl.value && !popoverEl.value.contains(target)) { closePopover(); } } @@ -225,6 +269,7 @@ const calendarOptions: CalendarOptions = { right: "dayGridMonth,timeGridWeek,timeGridDay", }, events: loadEvents, + datesSet: handleDatesSet, dateClick: handleDateClick, eventClick: handleEventClick, eventDrop: handleEventDrop, @@ -334,6 +379,26 @@ const upcomingGrouped = computed(() => { + + +
+
+ + {{ pickerYear }} + +
+
+ +
+
+
+ { :deep(.fc-toolbar-title) { font-size: 1.1rem; font-weight: 600; + cursor: pointer; + border-radius: 6px; + padding: 2px 8px; + transition: background 0.15s; + user-select: none; +} +:deep(.fc-toolbar-title:hover) { + background: rgba(255,255,255,0.07); } :deep(.fc-button) { background: var(--color-input-bg, var(--color-bg)); @@ -439,6 +512,71 @@ const upcomingGrouped = computed(() => { :deep(.fc-daygrid-day) { cursor: pointer; } :deep(.fc-daygrid-day:hover) { background: rgba(255,255,255,0.03); } +/* ── Month/year picker ──────────────────────────────────────────────────── */ +.month-picker { + background: var(--color-bg-card); + border: 1px solid var(--color-border, #2a2b30); + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0,0,0,0.5); + width: 280px; + padding: 12px 14px; +} + +.picker-year-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.picker-year-label { + font-size: 0.95rem; + font-weight: 700; + color: var(--color-text, #e8e9f0); +} + +.picker-year-btn { + background: none; + border: 1px solid var(--color-border, #2a2b30); + border-radius: 6px; + color: var(--color-text-muted, #888); + cursor: pointer; + padding: 2px 10px; + font-size: 1rem; + line-height: 1.4; + transition: color 0.15s, border-color 0.15s; +} +.picker-year-btn:hover { + color: var(--color-text, #e8e9f0); + border-color: rgba(255,255,255,0.25); +} + +.picker-months { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 4px; +} + +.picker-month { + padding: 7px 4px; + border: none; + border-radius: 7px; + background: transparent; + color: var(--color-text, #e8e9f0); + cursor: pointer; + font-size: 0.84rem; + text-align: center; + transition: background 0.12s, color 0.12s; +} +.picker-month:hover { + background: rgba(255,255,255,0.08); +} +.picker-month.active { + background: rgba(99,102,241,0.22); + color: var(--color-primary, #818cf8); + font-weight: 700; +} + /* ── Upcoming strip ─────────────────────────────────────────────────────── */ .upcoming-section { margin-top: 2rem;