diff --git a/src/App.vue b/src/App.vue index 1de60f0..8d02976 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,9 +1,36 @@ diff --git a/src/components/CalendarGrid.vue b/src/components/CalendarGrid.vue index 77a57af..d9adbd8 100644 --- a/src/components/CalendarGrid.vue +++ b/src/components/CalendarGrid.vue @@ -8,7 +8,12 @@
- +
@@ -16,7 +21,15 @@ diff --git a/src/components/CalendarHeader.vue b/src/components/CalendarHeader.vue index 3c869c6..61d8c8d 100644 --- a/src/components/CalendarHeader.vue +++ b/src/components/CalendarHeader.vue @@ -1,12 +1,12 @@ @@ -32,7 +37,14 @@ const weekdayNames = computed(() => { @@ -70,6 +82,6 @@ const weekdayNames = computed(() => { font-weight: 500; } .overlay-header-spacer { - /* Empty spacer for the month label column */ + grid-area: auto; } diff --git a/src/components/CalendarView.vue b/src/components/CalendarView.vue index c3c484d..ff470a1 100644 --- a/src/components/CalendarView.vue +++ b/src/components/CalendarView.vue @@ -4,22 +4,39 @@ import { useCalendarStore } from '@/stores/CalendarStore' import CalendarHeader from '@/components/CalendarHeader.vue' import CalendarWeek from '@/components/CalendarWeek.vue' import Jogwheel from '@/components/Jogwheel.vue' -import EventDialog from '@/components/EventDialog.vue' -import { isoWeekInfo, getLocalizedMonthName, monthAbbr, lunarPhaseSymbol, pad, mondayIndex, daysInclusive, addDaysStr, formatDateRange } from '@/utils/date' +import { + isoWeekInfo, + getLocalizedMonthName, + monthAbbr, + lunarPhaseSymbol, + pad, + daysInclusive, + addDaysStr, + formatDateRange, +} from '@/utils/date' import { toLocalString, fromLocalString } from '@/utils/date' const calendarStore = useCalendarStore() const viewport = ref(null) -const eventDialog = ref(null) -// UI state moved from store +const emit = defineEmits(['create-event', 'edit-event']) + +function createEventFromSelection() { + if (!selection.value.startDate || selection.value.dayCount === 0) return null + + return { + startDate: selection.value.startDate, + dayCount: selection.value.dayCount, + endDate: addDaysStr(selection.value.startDate, selection.value.dayCount - 1), + } +} + const scrollTop = ref(0) const viewportHeight = ref(600) const rowHeight = ref(64) -const baseDate = new Date(2024, 0, 1) // Monday +const baseDate = new Date(1970, 0, 4 + calendarStore.config.first_day) -// Selection state moved from store -const selection = ref({ start: null, end: null }) +const selection = ref({ startDate: null, dayCount: 0 }) const isDragging = ref(false) const dragAnchor = ref(null) @@ -27,16 +44,18 @@ const WEEK_MS = 7 * 24 * 60 * 60 * 1000 const minVirtualWeek = computed(() => { const date = new Date(calendarStore.minYear, 0, 1) - const monday = new Date(date) - monday.setDate(date.getDate() - ((date.getDay() + 6) % 7)) - return Math.floor((monday - baseDate) / WEEK_MS) + const firstDayOfWeek = new Date(date) + const dayOffset = (date.getDay() - calendarStore.config.first_day + 7) % 7 + firstDayOfWeek.setDate(date.getDate() - dayOffset) + return Math.floor((firstDayOfWeek - baseDate) / WEEK_MS) }) const maxVirtualWeek = computed(() => { const date = new Date(calendarStore.maxYear, 11, 31) - const monday = new Date(date) - monday.setDate(date.getDate() - ((date.getDay() + 6) % 7)) - return Math.floor((monday - baseDate) / WEEK_MS) + const firstDayOfWeek = new Date(date) + const dayOffset = (date.getDay() - calendarStore.config.first_day + 7) % 7 + firstDayOfWeek.setDate(date.getDate() - dayOffset) + return Math.floor((firstDayOfWeek - baseDate) / WEEK_MS) }) const totalVirtualWeeks = computed(() => { @@ -50,18 +69,25 @@ const initialScrollTop = computed(() => { const selectedDateRange = computed(() => { if (!selection.value.start || !selection.value.end) return '' - return formatDateRange(fromLocalString(selection.value.start), fromLocalString(selection.value.end)) + return formatDateRange( + fromLocalString(selection.value.start), + fromLocalString(selection.value.end), + ) }) const todayString = computed(() => { - const t = calendarStore.now.toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric'}).replace(/,? /, "\n") + const t = calendarStore.now + .toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric' }) + .replace(/,? /, '\n') return t.charAt(0).toUpperCase() + t.slice(1) }) const visibleWeeks = computed(() => { const buffer = 10 const startIdx = Math.floor((scrollTop.value - buffer * rowHeight.value) / rowHeight.value) - const endIdx = Math.ceil((scrollTop.value + viewportHeight.value + buffer * rowHeight.value) / rowHeight.value) + const endIdx = Math.ceil( + (scrollTop.value + viewportHeight.value + buffer * rowHeight.value) / rowHeight.value, + ) const startVW = Math.max(minVirtualWeek.value, startIdx + minVirtualWeek.value) const endVW = Math.min(maxVirtualWeek.value, endIdx + minVirtualWeek.value) @@ -77,7 +103,6 @@ const contentHeight = computed(() => { return totalVirtualWeeks.value * rowHeight.value }) -// Functions moved from store function computeRowHeight() { const el = document.createElement('div') el.style.position = 'absolute' @@ -91,22 +116,23 @@ function computeRowHeight() { } function getWeekIndex(date) { - const monday = new Date(date) - monday.setDate(date.getDate() - mondayIndex(date)) - return Math.floor((monday - baseDate) / WEEK_MS) + const firstDayOfWeek = new Date(date) + const dayOffset = (date.getDay() - calendarStore.config.first_day + 7) % 7 + firstDayOfWeek.setDate(date.getDate() - dayOffset) + return Math.floor((firstDayOfWeek - baseDate) / WEEK_MS) } -function getMondayForVirtualWeek(virtualWeek) { - const monday = new Date(baseDate) - monday.setDate(monday.getDate() + virtualWeek * 7) - return monday +function getFirstDayForVirtualWeek(virtualWeek) { + const firstDay = new Date(baseDate) + firstDay.setDate(firstDay.getDate() + virtualWeek * 7) + return firstDay } function createWeek(virtualWeek) { - const monday = getMondayForVirtualWeek(virtualWeek) - const weekNumber = isoWeekInfo(monday).week + const firstDay = getFirstDayForVirtualWeek(virtualWeek) + const weekNumber = isoWeekInfo(firstDay).week const days = [] - const cur = new Date(monday) + const cur = new Date(firstDay) let hasFirst = false let monthToLabel = null let labelYear = null @@ -116,7 +142,7 @@ function createWeek(virtualWeek) { const eventsForDay = calendarStore.events.get(dateStr) || [] const dow = cur.getDay() const isFirst = cur.getDate() === 1 - + if (isFirst) { hasFirst = true monthToLabel = cur.getMonth() @@ -128,7 +154,7 @@ function createWeek(virtualWeek) { if (cur.getMonth() === 0) { displayText = cur.getFullYear() } else { - displayText = monthAbbr[cur.getMonth()].slice(0,3).toUpperCase() + displayText = monthAbbr[cur.getMonth()].slice(0, 3).toUpperCase() } } @@ -141,8 +167,12 @@ function createWeek(virtualWeek) { isWeekend: calendarStore.weekend[dow], isFirstDay: isFirst, lunarPhase: lunarPhaseSymbol(cur), - isSelected: selection.value.start && selection.value.end && dateStr >= selection.value.start && dateStr <= selection.value.end, - events: eventsForDay + isSelected: + selection.value.startDate && + selection.value.dayCount > 0 && + dateStr >= selection.value.startDate && + dateStr <= addDaysStr(selection.value.startDate, selection.value.dayCount - 1), + events: eventsForDay, }) cur.setDate(cur.getDate() + 1) } @@ -150,11 +180,10 @@ function createWeek(virtualWeek) { let monthLabel = null if (hasFirst && monthToLabel !== null) { if (labelYear && labelYear <= calendarStore.config.max_year) { - // Calculate how many weeks this month spans let weeksSpan = 0 const d = new Date(cur) - d.setDate(cur.getDate() - 1) // Go back to last day of the week we just processed - + d.setDate(cur.getDate() - 1) + for (let i = 0; i < 6; i++) { d.setDate(cur.getDate() - 1 + i * 7) if (d.getMonth() === monthToLabel) weeksSpan++ @@ -168,7 +197,7 @@ function createWeek(virtualWeek) { text: `${getLocalizedMonthName(monthToLabel)} '${year}`, month: monthToLabel, weeksSpan: weeksSpan, - height: weeksSpan * rowHeight.value + height: weeksSpan * rowHeight.value, } } } @@ -178,7 +207,7 @@ function createWeek(virtualWeek) { weekNumber: pad(weekNumber), days, monthLabel, - top: (virtualWeek - minVirtualWeek.value) * rowHeight.value + top: (virtualWeek - minVirtualWeek.value) * rowHeight.value, } } @@ -193,39 +222,47 @@ function goToToday() { } function clearSelection() { - selection.value = { start: null, end: null } + selection.value = { startDate: null, dayCount: 0 } } function startDrag(dateStr) { if (calendarStore.config.select_days === 0) return isDragging.value = true dragAnchor.value = dateStr - selection.value = { start: dateStr, end: dateStr } + selection.value = { startDate: dateStr, dayCount: 1 } } function updateDrag(dateStr) { if (!isDragging.value) return - const [start, end] = clampRange(dragAnchor.value, dateStr) - selection.value = { start, end } + const { startDate, dayCount } = calculateSelection(dragAnchor.value, dateStr) + selection.value = { startDate, dayCount } } function endDrag(dateStr) { if (!isDragging.value) return isDragging.value = false - const [start, end] = clampRange(dragAnchor.value, dateStr) - selection.value = { start, end } + const { startDate, dayCount } = calculateSelection(dragAnchor.value, dateStr) + selection.value = { startDate, dayCount } } -function clampRange(anchorStr, otherStr) { +function calculateSelection(anchorStr, otherStr) { const limit = calendarStore.config.select_days - const forward = fromLocalString(otherStr) >= fromLocalString(anchorStr) + const anchorDate = fromLocalString(anchorStr) + const otherDate = fromLocalString(otherStr) + const forward = otherDate >= anchorDate const span = daysInclusive(anchorStr, otherStr) + if (span <= limit) { - const a = [anchorStr, otherStr].sort() - return [a[0], a[1]] + const startDate = forward ? anchorStr : otherStr + return { startDate, dayCount: span } + } + + if (forward) { + return { startDate: anchorStr, dayCount: limit } + } else { + const startDate = addDaysStr(anchorStr, -(limit - 1)) + return { startDate, dayCount: limit } } - if (forward) return [anchorStr, addDaysStr(anchorStr, limit - 1)] - return [addDaysStr(anchorStr, -(limit - 1)), anchorStr] } const onScroll = () => { @@ -241,20 +278,18 @@ const handleJogwheelScrollTo = (newScrollTop) => { } onMounted(() => { - // Compute row height and initialize computeRowHeight() calendarStore.updateCurrentDate() - + if (viewport.value) { viewportHeight.value = viewport.value.clientHeight viewport.value.scrollTop = initialScrollTop.value viewport.value.addEventListener('scroll', onScroll) } - - // Update time periodically + const timer = setInterval(() => { calendarStore.updateCurrentDate() - }, 60000) // Update every minute + }, 60000) onBeforeUnmount(() => { clearInterval(timer) @@ -280,14 +315,14 @@ const handleDayMouseEnter = (dateStr) => { const handleDayMouseUp = (dateStr) => { if (isDragging.value) { endDrag(dateStr) - // Show event dialog if we have a selection - if (selection.value.start && selection.value.end && eventDialog.value) { - setTimeout(() => eventDialog.value.openCreateDialog(), 50) + const eventData = createEventFromSelection() + if (eventData) { + clearSelection() + emit('create-event', eventData) } } } -// Touch event handlers const handleDayTouchStart = (dateStr) => { startDrag(dateStr) } @@ -301,17 +336,16 @@ const handleDayTouchMove = (dateStr) => { const handleDayTouchEnd = (dateStr) => { if (isDragging.value) { endDrag(dateStr) - // Show event dialog if we have a selection - if (selection.value.start && selection.value.end && eventDialog.value) { - setTimeout(() => eventDialog.value.openCreateDialog(), 50) + const eventData = createEventFromSelection() + if (eventData) { + clearSelection() + emit('create-event', eventData) } } } const handleEventClick = (eventInstanceId) => { - if (eventDialog.value) { - eventDialog.value.openEditDialog(eventInstanceId) - } + emit('edit-event', eventInstanceId) } @@ -323,14 +357,14 @@ const handleEventClick = (eventInstanceId) => {
{{ todayString }}
-
-
+
{ :key="`month-${week.virtualWeek}`" v-show="week.monthLabel" class="month-name-label" - :style="{ + :style="{ top: week.top + 'px', - height: week.monthLabel?.height + 'px' + height: week.monthLabel?.height + 'px', }" > {{ week.monthLabel?.text }} @@ -360,19 +394,14 @@ const handleEventClick = (eventInstanceId) => {
-
-
diff --git a/src/components/EventDialog.vue b/src/components/EventDialog.vue index 0d67819..21ca90a 100644 --- a/src/components/EventDialog.vue +++ b/src/components/EventDialog.vue @@ -3,9 +3,10 @@ import { useCalendarStore } from '@/stores/CalendarStore' import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue' import WeekdaySelector from './WeekdaySelector.vue' import Numeric from './Numeric.vue' +import { addDaysStr } from '@/utils/date' const props = defineProps({ - selection: { type: Object, default: () => ({ start: null, end: null }) }, + selection: { type: Object, default: () => ({ startDate: null, dayCount: 0 }) }, }) const emit = defineEmits(['clear-selection']) @@ -27,9 +28,10 @@ const eventSaved = ref(false) const titleInput = ref(null) // Helper to get starting weekday (Sunday-first index) -function getStartingWeekday() { - if (!props.selection.start) return 0 // Default to Sunday - const date = new Date(props.selection.start + 'T00:00:00') +function getStartingWeekday(selectionData = null) { + const currentSelection = selectionData || props.selection + if (!currentSelection.start) return 0 // Default to Sunday + const date = new Date(currentSelection.start + 'T00:00:00') const dayOfWeek = date.getDay() // 0=Sunday, 1=Monday, ... return dayOfWeek // Keep Sunday-first (0=Sunday, 6=Saturday) } @@ -91,7 +93,23 @@ const selectedColor = computed({ }, }) -function openCreateDialog() { +function openCreateDialog(selectionData = null) { + const currentSelection = selectionData || props.selection + + // Convert new format to start/end for compatibility with existing logic + let start, end + if (currentSelection.startDate && currentSelection.dayCount) { + start = currentSelection.startDate + end = addDaysStr(currentSelection.startDate, currentSelection.dayCount - 1) + } else if (currentSelection.start && currentSelection.end) { + // Fallback for old format + start = currentSelection.start + end = currentSelection.end + } else { + start = null + end = null + } + occurrenceContext.value = null dialogMode.value = 'create' title.value = '' @@ -100,18 +118,16 @@ function openCreateDialog() { recurrenceFrequency.value = 'weeks' recurrenceWeekdays.value = [false, false, false, false, false, false, false] recurrenceOccurrences.value = 0 - colorId.value = calendarStore.selectEventColorId(props.selection.start, props.selection.end) + colorId.value = calendarStore.selectEventColorId(start, end) eventSaved.value = false - // Auto-select starting day for weekly recurrence - const startingDay = getStartingWeekday() + const startingDay = getStartingWeekday({ start, end }) recurrenceWeekdays.value[startingDay] = true - // Create the event immediately in the store editingEventId.value = calendarStore.createEvent({ title: '', - startDate: props.selection.start, - endDate: props.selection.end, + startDate: start, + endDate: end, colorId: colorId.value, repeat: repeat.value, repeatInterval: recurrenceInterval.value, diff --git a/src/components/WeekdaySelector.vue b/src/components/WeekdaySelector.vue index 6df4a42..601e7ae 100644 --- a/src/components/WeekdaySelector.vue +++ b/src/components/WeekdaySelector.vue @@ -34,7 +34,12 @@