Simplified CalendarStore
This commit is contained in:
		| @@ -20,6 +20,7 @@ import { | |||||||
| } from '@/utils/date' | } from '@/utils/date' | ||||||
| import { toLocalString, fromLocalString, DEFAULT_TZ } from '@/utils/date' | import { toLocalString, fromLocalString, DEFAULT_TZ } from '@/utils/date' | ||||||
| import { addDays, differenceInCalendarDays } from 'date-fns' | import { addDays, differenceInCalendarDays } from 'date-fns' | ||||||
|  | import { getHolidayForDate } from '@/utils/holidays' | ||||||
|  |  | ||||||
| const calendarStore = useCalendarStore() | const calendarStore = useCalendarStore() | ||||||
| const viewport = ref(null) | const viewport = ref(null) | ||||||
| @@ -232,7 +233,12 @@ function createWeek(virtualWeek) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Get holiday info once per day |     // Get holiday info once per day | ||||||
|     const holiday = calendarStore.getHolidayForDate(dateStr) |     // Ensure holidays initialized lazily | ||||||
|  |     let holiday = null | ||||||
|  |     if (calendarStore.config.holidays.enabled) { | ||||||
|  |       calendarStore._ensureHolidaysInitialized?.() | ||||||
|  |       holiday = getHolidayForDate(dateStr) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     days.push({ |     days.push({ | ||||||
|       date: dateStr, |       date: dateStr, | ||||||
|   | |||||||
| @@ -4,18 +4,11 @@ import { | |||||||
|   fromLocalString, |   fromLocalString, | ||||||
|   getLocaleWeekendDays, |   getLocaleWeekendDays, | ||||||
|   getMondayOfISOWeek, |   getMondayOfISOWeek, | ||||||
|   getOccurrenceIndex, |  | ||||||
|   getOccurrenceDate, |   getOccurrenceDate, | ||||||
|   DEFAULT_TZ, |   DEFAULT_TZ, | ||||||
| } from '@/utils/date' | } from '@/utils/date' | ||||||
| import { differenceInCalendarDays, addDays } from 'date-fns' | import { differenceInCalendarDays, addDays } from 'date-fns' | ||||||
| import { | import { initializeHolidays, getAvailableCountries, getAvailableStates } from '@/utils/holidays' | ||||||
|   initializeHolidays, |  | ||||||
|   getHolidayForDate, |  | ||||||
|   isHoliday, |  | ||||||
|   getAvailableCountries, |  | ||||||
|   getAvailableStates, |  | ||||||
| } from '@/utils/holidays' |  | ||||||
|  |  | ||||||
| const MIN_YEAR = 1900 | const MIN_YEAR = 1900 | ||||||
| const MAX_YEAR = 2100 | const MAX_YEAR = 2100 | ||||||
| @@ -43,29 +36,23 @@ export const useCalendarStore = defineStore('calendar', { | |||||||
|   }), |   }), | ||||||
|  |  | ||||||
|   getters: { |   getters: { | ||||||
|     // Basic configuration getters |  | ||||||
|     minYear: () => MIN_YEAR, |     minYear: () => MIN_YEAR, | ||||||
|     maxYear: () => MAX_YEAR, |     maxYear: () => MAX_YEAR, | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   actions: { |   actions: { | ||||||
|     // Initialize holidays based on current config |     _resolveCountry(code) { | ||||||
|     initializeHolidaysFromConfig() { |       if (!code || code !== 'auto') return code | ||||||
|       if (!this.config.holidays.enabled) { |  | ||||||
|         return false |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       let country = this.config.holidays.country |  | ||||||
|       if (country === 'auto') { |  | ||||||
|       const locale = navigator.language || navigator.languages?.[0] |       const locale = navigator.language || navigator.languages?.[0] | ||||||
|         if (!locale) return false |       if (!locale) return null | ||||||
|  |  | ||||||
|       const parts = locale.split('-') |       const parts = locale.split('-') | ||||||
|         if (parts.length < 2) return false |       if (parts.length < 2) return null | ||||||
|  |       return parts[parts.length - 1].toUpperCase() | ||||||
|         country = parts[parts.length - 1].toUpperCase() |     }, | ||||||
|       } |  | ||||||
|  |  | ||||||
|  |     initializeHolidaysFromConfig() { | ||||||
|  |       if (!this.config.holidays.enabled) return false | ||||||
|  |       const country = this._resolveCountry(this.config.holidays.country) | ||||||
|       if (country) { |       if (country) { | ||||||
|         return this.initializeHolidays( |         return this.initializeHolidays( | ||||||
|           country, |           country, | ||||||
| @@ -73,107 +60,55 @@ export const useCalendarStore = defineStore('calendar', { | |||||||
|           this.config.holidays.region, |           this.config.holidays.region, | ||||||
|         ) |         ) | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       return false |       return false | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     occursOnDate(event, dateStr) { |  | ||||||
|       return getOccurrenceIndex(event, dateStr) !== null |  | ||||||
|     }, |  | ||||||
|     updateCurrentDate() { |     updateCurrentDate() { | ||||||
|       const d = new Date() |       const d = new Date() | ||||||
|       this.now = d.toISOString() |       this.now = d.toISOString() | ||||||
|       const today = toLocalString(d, DEFAULT_TZ) |       const today = toLocalString(d, DEFAULT_TZ) | ||||||
|       if (this.today !== today) { |       if (this.today !== today) this.today = today | ||||||
|         this.today = today |  | ||||||
|       } |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // Holiday management |  | ||||||
|     initializeHolidays(country, state = null, region = null) { |     initializeHolidays(country, state = null, region = null) { | ||||||
|       let actualCountry = country |       const actualCountry = this._resolveCountry(country) | ||||||
|       if (country === 'auto') { |       if (this.config.holidays.country !== 'auto') this.config.holidays.country = country | ||||||
|         const locale = navigator.language || navigator.languages?.[0] |  | ||||||
|         if (!locale) return false |  | ||||||
|  |  | ||||||
|         const parts = locale.split('-') |  | ||||||
|         if (parts.length < 2) return false |  | ||||||
|  |  | ||||||
|         actualCountry = parts[parts.length - 1].toUpperCase() |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (this.config.holidays.country !== 'auto') { |  | ||||||
|         this.config.holidays.country = country |  | ||||||
|       } |  | ||||||
|       this.config.holidays.state = state |       this.config.holidays.state = state | ||||||
|       this.config.holidays.region = region |       this.config.holidays.region = region | ||||||
|  |  | ||||||
|       this._holidayConfigSignature = null |       this._holidayConfigSignature = null | ||||||
|       this._holidaysInitialized = false |       this._holidaysInitialized = false | ||||||
|  |  | ||||||
|       return initializeHolidays(actualCountry, state, region) |       return initializeHolidays(actualCountry, state, region) | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _ensureHolidaysInitialized() { |     _ensureHolidaysInitialized() { | ||||||
|       if (!this.config.holidays.enabled) { |       if (!this.config.holidays.enabled) return false | ||||||
|         return false |       const actualCountry = this._resolveCountry(this.config.holidays.country) | ||||||
|       } |       const sig = `${actualCountry}-${this.config.holidays.state}-${this.config.holidays.region}-${this.config.holidays.enabled}` | ||||||
|  |       if (this._holidayConfigSignature !== sig || !this._holidaysInitialized) { | ||||||
|       let actualCountry = this.config.holidays.country |         const ok = initializeHolidays( | ||||||
|       if (this.config.holidays.country === 'auto') { |  | ||||||
|         const locale = navigator.language || navigator.languages?.[0] |  | ||||||
|         if (!locale) return false |  | ||||||
|  |  | ||||||
|         const parts = locale.split('-') |  | ||||||
|         if (parts.length < 2) return false |  | ||||||
|  |  | ||||||
|         actualCountry = parts[parts.length - 1].toUpperCase() |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       const configSignature = `${actualCountry}-${this.config.holidays.state}-${this.config.holidays.region}-${this.config.holidays.enabled}` |  | ||||||
|  |  | ||||||
|       if (this._holidayConfigSignature !== configSignature || !this._holidaysInitialized) { |  | ||||||
|         const success = initializeHolidays( |  | ||||||
|           actualCountry, |           actualCountry, | ||||||
|           this.config.holidays.state, |           this.config.holidays.state, | ||||||
|           this.config.holidays.region, |           this.config.holidays.region, | ||||||
|         ) |         ) | ||||||
|         if (success) { |         if (ok) { | ||||||
|           this._holidayConfigSignature = configSignature |           this._holidayConfigSignature = sig | ||||||
|           this._holidaysInitialized = true |           this._holidaysInitialized = true | ||||||
|         } |         } | ||||||
|         return success |         return ok | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       return this._holidaysInitialized |       return this._holidaysInitialized | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     getHolidayForDate(dateStr) { |  | ||||||
|       if (!this._ensureHolidaysInitialized()) { |  | ||||||
|         return null |  | ||||||
|       } |  | ||||||
|       return getHolidayForDate(dateStr) |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     isHoliday(dateStr) { |  | ||||||
|       if (!this._ensureHolidaysInitialized()) { |  | ||||||
|         return false |  | ||||||
|       } |  | ||||||
|       return isHoliday(dateStr) |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     getAvailableCountries() { |     getAvailableCountries() { | ||||||
|       return getAvailableCountries() || [] |       return getAvailableCountries() || [] | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     getAvailableStates(country) { |     getAvailableStates(country) { | ||||||
|       return getAvailableStates(country) || [] |       return getAvailableStates(country) || [] | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     toggleHolidays() { |     toggleHolidays() { | ||||||
|       this.config.holidays.enabled = !this.config.holidays.enabled |       this.config.holidays.enabled = !this.config.holidays.enabled | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // Event management |  | ||||||
|     generateId() { |     generateId() { | ||||||
|       try { |       try { | ||||||
|         if (window.crypto && typeof window.crypto.randomUUID === 'function') { |         if (window.crypto && typeof window.crypto.randomUUID === 'function') { | ||||||
| @@ -194,14 +129,12 @@ export const useCalendarStore = defineStore('calendar', { | |||||||
|           eventData.colorId ?? this.selectEventColorId(eventData.startDate, eventData.endDate), |           eventData.colorId ?? this.selectEventColorId(eventData.startDate, eventData.endDate), | ||||||
|         startTime: singleDay ? eventData.startTime || '09:00' : null, |         startTime: singleDay ? eventData.startTime || '09:00' : null, | ||||||
|         durationMinutes: singleDay ? eventData.durationMinutes || 60 : null, |         durationMinutes: singleDay ? eventData.durationMinutes || 60 : null, | ||||||
|         // Normalized repeat value: only 'weeks', 'months', or 'none' |  | ||||||
|         repeat: ['weeks', 'months'].includes(eventData.repeat) ? eventData.repeat : 'none', |         repeat: ['weeks', 'months'].includes(eventData.repeat) ? eventData.repeat : 'none', | ||||||
|         repeatInterval: eventData.repeatInterval || 1, |         repeatInterval: eventData.repeatInterval || 1, | ||||||
|         repeatCount: eventData.repeatCount || 'unlimited', |         repeatCount: eventData.repeatCount || 'unlimited', | ||||||
|         repeatWeekdays: eventData.repeatWeekdays, |         repeatWeekdays: eventData.repeatWeekdays, | ||||||
|         isRepeating: eventData.repeat && eventData.repeat !== 'none', |         isRepeating: eventData.repeat && eventData.repeat !== 'none', | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       this.events.set(event.id, { ...event, isSpanning: event.startDate < event.endDate }) |       this.events.set(event.id, { ...event, isSpanning: event.startDate < event.endDate }) | ||||||
|       return event.id |       return event.id | ||||||
|     }, |     }, | ||||||
| @@ -214,53 +147,42 @@ export const useCalendarStore = defineStore('calendar', { | |||||||
|       const colorCounts = [0, 0, 0, 0, 0, 0, 0, 0] |       const colorCounts = [0, 0, 0, 0, 0, 0, 0, 0] | ||||||
|       const startDate = fromLocalString(startDateStr, DEFAULT_TZ) |       const startDate = fromLocalString(startDateStr, DEFAULT_TZ) | ||||||
|       const endDate = fromLocalString(endDateStr, DEFAULT_TZ) |       const endDate = fromLocalString(endDateStr, DEFAULT_TZ) | ||||||
|       // Count events whose ranges overlap at least one day in selected span |  | ||||||
|       for (const ev of this.events.values()) { |       for (const ev of this.events.values()) { | ||||||
|         const evStart = fromLocalString(ev.startDate) |         const evStart = fromLocalString(ev.startDate) | ||||||
|         const evEnd = fromLocalString(ev.endDate) |         const evEnd = fromLocalString(ev.endDate) | ||||||
|         if (evEnd < startDate || evStart > endDate) continue |         if (evEnd < startDate || evStart > endDate) continue | ||||||
|         if (ev.colorId >= 0 && ev.colorId < 8) colorCounts[ev.colorId]++ |         if (ev.colorId >= 0 && ev.colorId < 8) colorCounts[ev.colorId]++ | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       let minCount = colorCounts[0] |       let minCount = colorCounts[0] | ||||||
|       let selectedColor = 0 |       let selectedColor = 0 | ||||||
|  |       for (let c = 1; c < 8; c++) { | ||||||
|       for (let colorId = 1; colorId < 8; colorId++) { |         if (colorCounts[c] < minCount) { | ||||||
|         if (colorCounts[colorId] < minCount) { |           minCount = colorCounts[c] | ||||||
|           minCount = colorCounts[colorId] |           selectedColor = c | ||||||
|           selectedColor = colorId |  | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       return selectedColor |       return selectedColor | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     deleteEvent(eventId) { |     deleteEvent(eventId) { | ||||||
|       console.log('Deleting event', eventId) |  | ||||||
|       this.events.delete(eventId) |       this.events.delete(eventId) | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // Remove the first (base) occurrence of a repeating event by shifting anchor forward |  | ||||||
|     deleteFirstOccurrence(baseId) { |     deleteFirstOccurrence(baseId) { | ||||||
|       console.log('Deleting first occurrence', baseId) |  | ||||||
|       const base = this.getEventById(baseId) |       const base = this.getEventById(baseId) | ||||||
|       if (!base) return |       if (!base) return | ||||||
|       if (!base.isRepeating) { |       if (!base.isRepeating) { | ||||||
|         // Simple (non-repeating) event: delete entirely |  | ||||||
|         this.deleteEvent(baseId) |         this.deleteEvent(baseId) | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
|       const numericCount = |       const numericCount = | ||||||
|         base.repeatCount === 'unlimited' ? Infinity : parseInt(base.repeatCount, 10) |         base.repeatCount === 'unlimited' ? Infinity : parseInt(base.repeatCount, 10) | ||||||
|       if (numericCount <= 1) { |       if (numericCount <= 1) { | ||||||
|         // Only one occurrence (or invalid count) -> delete event |  | ||||||
|         this.deleteEvent(baseId) |         this.deleteEvent(baseId) | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
|       // Get the next occurrence start date (index 1) |  | ||||||
|       const nextStartStr = getOccurrenceDate(base, 1, DEFAULT_TZ) |       const nextStartStr = getOccurrenceDate(base, 1, DEFAULT_TZ) | ||||||
|       if (!nextStartStr) { |       if (!nextStartStr) { | ||||||
|         // No next occurrence; remove event |  | ||||||
|         this.deleteEvent(baseId) |         this.deleteEvent(baseId) | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
| @@ -271,39 +193,29 @@ export const useCalendarStore = defineStore('calendar', { | |||||||
|         addDays(fromLocalString(nextStartStr, DEFAULT_TZ), durationDays), |         addDays(fromLocalString(nextStartStr, DEFAULT_TZ), durationDays), | ||||||
|         DEFAULT_TZ, |         DEFAULT_TZ, | ||||||
|       ) |       ) | ||||||
|       // Mutate existing event instead of delete+recreate so references remain stable |  | ||||||
|       base.startDate = nextStartStr |       base.startDate = nextStartStr | ||||||
|       base.endDate = newEndStr |       base.endDate = newEndStr | ||||||
|       if (numericCount !== Infinity) { |       if (numericCount !== Infinity) base.repeatCount = String(Math.max(1, numericCount - 1)) | ||||||
|         base.repeatCount = String(Math.max(1, numericCount - 1)) |  | ||||||
|       } |  | ||||||
|       this.events.set(baseId, { ...base, isSpanning: base.startDate < base.endDate }) |       this.events.set(baseId, { ...base, isSpanning: base.startDate < base.endDate }) | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // Delete a specific occurrence (not the first) from a repeating series, splitting into two |  | ||||||
|     deleteSingleOccurrence(ctx) { |     deleteSingleOccurrence(ctx) { | ||||||
|       console.log('DeletesingleOccurrence') |  | ||||||
|       const { baseId, occurrenceIndex } = ctx || {} |       const { baseId, occurrenceIndex } = ctx || {} | ||||||
|       if (occurrenceIndex === undefined || occurrenceIndex === null) return |       if (occurrenceIndex == null) return | ||||||
|       const base = this.getEventById(baseId) |       const base = this.getEventById(baseId) | ||||||
|       if (!base) return |       if (!base) return | ||||||
|       if (!base.isRepeating) { |       if (!base.isRepeating) { | ||||||
|         // Single non-repeating event deletion |  | ||||||
|         if (occurrenceIndex === 0) this.deleteEvent(baseId) |         if (occurrenceIndex === 0) this.deleteEvent(baseId) | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
|       if (occurrenceIndex === 0) { |       if (occurrenceIndex === 0) { | ||||||
|         // Delegate to specialized first-occurrence deletion |  | ||||||
|         this.deleteFirstOccurrence(baseId) |         this.deleteFirstOccurrence(baseId) | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
|       // Save copy before truncation for computing next occurrence date |  | ||||||
|       const snapshot = { ...base } |       const snapshot = { ...base } | ||||||
|       // Cap original series to occurrences before the deleted one |  | ||||||
|       base.repeatCount = occurrenceIndex |       base.repeatCount = occurrenceIndex | ||||||
|       const nextStartStr = getOccurrenceDate(snapshot, occurrenceIndex + 1, DEFAULT_TZ) |       const nextStartStr = getOccurrenceDate(snapshot, occurrenceIndex + 1, DEFAULT_TZ) | ||||||
|       console.log('Deleting single', occurrenceIndex, nextStartStr) |       if (!nextStartStr) return | ||||||
|       if (!nextStartStr) return // no continuation |  | ||||||
|       const durationDays = Math.max( |       const durationDays = Math.max( | ||||||
|         0, |         0, | ||||||
|         differenceInCalendarDays( |         differenceInCalendarDays( | ||||||
| @@ -336,40 +248,30 @@ export const useCalendarStore = defineStore('calendar', { | |||||||
|       const { baseId, occurrenceIndex } = ctx |       const { baseId, occurrenceIndex } = ctx | ||||||
|       const base = this.getEventById(baseId) |       const base = this.getEventById(baseId) | ||||||
|       if (!base || !base.isRepeating) return |       if (!base || !base.isRepeating) return | ||||||
|  |  | ||||||
|       // Special case: if deleting from the base occurrence (index 0), delete the entire series |  | ||||||
|       if (occurrenceIndex === 0) { |       if (occurrenceIndex === 0) { | ||||||
|         this.deleteEvent(baseId) |         this.deleteEvent(baseId) | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
|       const keptTotal = occurrenceIndex |       this._terminateRepeatSeriesAtIndex(baseId, occurrenceIndex) | ||||||
|       this._terminateRepeatSeriesAtIndex(baseId, keptTotal) |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // Adjust start/end range of a base event (non-generated) and reindex occurrences |  | ||||||
|     setEventRange(eventId, newStartStr, newEndStr, { mode = 'auto' } = {}) { |     setEventRange(eventId, newStartStr, newEndStr, { mode = 'auto' } = {}) { | ||||||
|       const snapshot = this.events.get(eventId) |       const snapshot = this.events.get(eventId) | ||||||
|       if (!snapshot) return |       if (!snapshot) return | ||||||
|       // Calculate current duration in days (inclusive) |  | ||||||
|       const prevStart = fromLocalString(snapshot.startDate, DEFAULT_TZ) |       const prevStart = fromLocalString(snapshot.startDate, DEFAULT_TZ) | ||||||
|       const prevEnd = fromLocalString(snapshot.endDate, DEFAULT_TZ) |       const prevEnd = fromLocalString(snapshot.endDate, DEFAULT_TZ) | ||||||
|       const prevDurationDays = Math.max(0, differenceInCalendarDays(prevEnd, prevStart)) |       const prevDurationDays = Math.max(0, differenceInCalendarDays(prevEnd, prevStart)) | ||||||
|  |  | ||||||
|       const newStart = fromLocalString(newStartStr, DEFAULT_TZ) |       const newStart = fromLocalString(newStartStr, DEFAULT_TZ) | ||||||
|       const newEnd = fromLocalString(newEndStr, DEFAULT_TZ) |       const newEnd = fromLocalString(newEndStr, DEFAULT_TZ) | ||||||
|       const proposedDurationDays = Math.max(0, differenceInCalendarDays(newEnd, newStart)) |       const proposedDurationDays = Math.max(0, differenceInCalendarDays(newEnd, newStart)) | ||||||
|  |  | ||||||
|       let finalDurationDays = prevDurationDays |       let finalDurationDays = prevDurationDays | ||||||
|       if (mode === 'resize-left' || mode === 'resize-right') { |       if (mode === 'resize-left' || mode === 'resize-right') | ||||||
|         finalDurationDays = proposedDurationDays |         finalDurationDays = proposedDurationDays | ||||||
|       } |  | ||||||
|  |  | ||||||
|       snapshot.startDate = newStartStr |       snapshot.startDate = newStartStr | ||||||
|       snapshot.endDate = toLocalString( |       snapshot.endDate = toLocalString( | ||||||
|         addDays(fromLocalString(newStartStr, DEFAULT_TZ), finalDurationDays), |         addDays(fromLocalString(newStartStr, DEFAULT_TZ), finalDurationDays), | ||||||
|         DEFAULT_TZ, |         DEFAULT_TZ, | ||||||
|       ) |       ) | ||||||
|       // Rotate weekly recurrence pattern when moving (not resizing) so selected weekdays track shift |  | ||||||
|       if ( |       if ( | ||||||
|         mode === 'move' && |         mode === 'move' && | ||||||
|         snapshot.isRepeating && |         snapshot.isRepeating && | ||||||
| @@ -391,29 +293,20 @@ export const useCalendarStore = defineStore('calendar', { | |||||||
|           snapshot.repeatWeekdays = rotated |           snapshot.repeatWeekdays = rotated | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       // Update the event directly |       this.events.set(eventId, { ...snapshot, isSpanning: snapshot.startDate < snapshot.endDate }) | ||||||
|       this.events.set(eventId, { |  | ||||||
|         ...snapshot, |  | ||||||
|         startDate: snapshot.startDate, |  | ||||||
|         endDate: snapshot.endDate, |  | ||||||
|         isSpanning: snapshot.startDate < snapshot.endDate, |  | ||||||
|       }) |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // Split a repeating series at a specific occurrence date and move that occurrence (and future) to new range |  | ||||||
|     splitMoveVirtualOccurrence(baseId, occurrenceDateStr, newStartStr, newEndStr) { |     splitMoveVirtualOccurrence(baseId, occurrenceDateStr, newStartStr, newEndStr) { | ||||||
|       const base = this.events.get(baseId) |       const base = this.events.get(baseId) | ||||||
|       if (!base || !base.isRepeating) return |       if (!base || !base.isRepeating) return | ||||||
|       const originalCountRaw = base.repeatCount |       const originalCountRaw = base.repeatCount | ||||||
|       // spanDays not needed for splitting logic here post-refactor |  | ||||||
|       const occurrenceDate = fromLocalString(occurrenceDateStr, DEFAULT_TZ) |       const occurrenceDate = fromLocalString(occurrenceDateStr, DEFAULT_TZ) | ||||||
|       const baseStart = fromLocalString(base.startDate, DEFAULT_TZ) |       const baseStart = fromLocalString(base.startDate, DEFAULT_TZ) | ||||||
|       if (occurrenceDate <= baseStart) { |       if (occurrenceDate <= baseStart) { | ||||||
|         // Moving the base itself: just move entire series |  | ||||||
|         this.setEventRange(baseId, newStartStr, newEndStr, { mode: 'move' }) |         this.setEventRange(baseId, newStartStr, newEndStr, { mode: 'move' }) | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
|       let keptOccurrences = 0 // number of occurrences BEFORE the moved one |       let keptOccurrences = 0 | ||||||
|       if (base.repeat === 'weeks') { |       if (base.repeat === 'weeks') { | ||||||
|         const interval = base.repeatInterval || 1 |         const interval = base.repeatInterval || 1 | ||||||
|         const pattern = base.repeatWeekdays || [] |         const pattern = base.repeatWeekdays || [] | ||||||
| @@ -435,15 +328,12 @@ export const useCalendarStore = defineStore('calendar', { | |||||||
|           (occurrenceDate.getFullYear() - baseStart.getFullYear()) * 12 + |           (occurrenceDate.getFullYear() - baseStart.getFullYear()) * 12 + | ||||||
|           (occurrenceDate.getMonth() - baseStart.getMonth()) |           (occurrenceDate.getMonth() - baseStart.getMonth()) | ||||||
|         const interval = base.repeatInterval || 1 |         const interval = base.repeatInterval || 1 | ||||||
|         if (diffMonths <= 0 || diffMonths % interval !== 0) return // invalid occurrence |         if (diffMonths <= 0 || diffMonths % interval !== 0) return | ||||||
|         keptOccurrences = diffMonths // base is occurrence 0; we keep all before diffMonths |         keptOccurrences = diffMonths | ||||||
|       } else { |       } else { | ||||||
|         // Unsupported repeat type |  | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
|       // Truncate original series to keptOccurrences |  | ||||||
|       this._terminateRepeatSeriesAtIndex(baseId, keptOccurrences) |       this._terminateRepeatSeriesAtIndex(baseId, keptOccurrences) | ||||||
|       // Compute remaining occurrences count |  | ||||||
|       let remainingCount = 'unlimited' |       let remainingCount = 'unlimited' | ||||||
|       if (originalCountRaw !== 'unlimited') { |       if (originalCountRaw !== 'unlimited') { | ||||||
|         const total = parseInt(originalCountRaw, 10) |         const total = parseInt(originalCountRaw, 10) | ||||||
| @@ -453,10 +343,8 @@ export const useCalendarStore = defineStore('calendar', { | |||||||
|           remainingCount = String(rem) |           remainingCount = String(rem) | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       // Determine repeat-specific adjustments |  | ||||||
|       let repeatWeekdays = base.repeatWeekdays |       let repeatWeekdays = base.repeatWeekdays | ||||||
|       if (base.repeat === 'weeks' && Array.isArray(base.repeatWeekdays)) { |       if (base.repeat === 'weeks' && Array.isArray(base.repeatWeekdays)) { | ||||||
|         // Rotate pattern so that the moved occurrence weekday stays active relative to new anchor |  | ||||||
|         const origWeekday = occurrenceDate.getDay() |         const origWeekday = occurrenceDate.getDay() | ||||||
|         const newWeekday = fromLocalString(newStartStr, DEFAULT_TZ).getDay() |         const newWeekday = fromLocalString(newStartStr, DEFAULT_TZ).getDay() | ||||||
|         const shift = newWeekday - origWeekday |         const shift = newWeekday - origWeekday | ||||||
| @@ -472,7 +360,6 @@ export const useCalendarStore = defineStore('calendar', { | |||||||
|           repeatWeekdays = rotated |           repeatWeekdays = rotated | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       // Create continuation series starting at newStartStr |  | ||||||
|       this.createEvent({ |       this.createEvent({ | ||||||
|         title: base.title, |         title: base.title, | ||||||
|         startDate: newStartStr, |         startDate: newStartStr, | ||||||
| @@ -485,15 +372,11 @@ export const useCalendarStore = defineStore('calendar', { | |||||||
|       }) |       }) | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // Split a repeating series at a given occurrence index; returns new series id |     splitRepeatSeries(baseId, occurrenceIndex, newStartStr, newEndStr) { | ||||||
|     splitRepeatSeries(baseId, occurrenceIndex, newStartStr, newEndStr, _grabbedWeekday) { |  | ||||||
|       const base = this.events.get(baseId) |       const base = this.events.get(baseId) | ||||||
|       if (!base || !base.isRepeating) return null |       if (!base || !base.isRepeating) return null | ||||||
|       // Capture original repeatCount BEFORE truncation |  | ||||||
|       const originalCountRaw = base.repeatCount |       const originalCountRaw = base.repeatCount | ||||||
|       // Truncate base to keep occurrences before split point (indices 0..occurrenceIndex-1) |  | ||||||
|       this._terminateRepeatSeriesAtIndex(baseId, occurrenceIndex) |       this._terminateRepeatSeriesAtIndex(baseId, occurrenceIndex) | ||||||
|       // Compute new series repeatCount (remaining occurrences starting at occurrenceIndex) |  | ||||||
|       let newSeriesCount = 'unlimited' |       let newSeriesCount = 'unlimited' | ||||||
|       if (originalCountRaw !== 'unlimited') { |       if (originalCountRaw !== 'unlimited') { | ||||||
|         const originalNum = parseInt(originalCountRaw, 10) |         const originalNum = parseInt(originalCountRaw, 10) | ||||||
| @@ -502,7 +385,7 @@ export const useCalendarStore = defineStore('calendar', { | |||||||
|           newSeriesCount = String(Math.max(1, remaining)) |           newSeriesCount = String(Math.max(1, remaining)) | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       const newId = this.createEvent({ |       return this.createEvent({ | ||||||
|         title: base.title, |         title: base.title, | ||||||
|         startDate: newStartStr, |         startDate: newStartStr, | ||||||
|         endDate: newEndStr, |         endDate: newEndStr, | ||||||
| @@ -512,7 +395,6 @@ export const useCalendarStore = defineStore('calendar', { | |||||||
|         repeatCount: newSeriesCount, |         repeatCount: newSeriesCount, | ||||||
|         repeatWeekdays: base.repeatWeekdays, |         repeatWeekdays: base.repeatWeekdays, | ||||||
|       }) |       }) | ||||||
|       return newId |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     _terminateRepeatSeriesAtIndex(baseId, index) { |     _terminateRepeatSeriesAtIndex(baseId, index) { | ||||||
| @@ -525,15 +407,10 @@ export const useCalendarStore = defineStore('calendar', { | |||||||
|         if (!isNaN(rc)) ev.repeatCount = String(Math.min(rc, index)) |         if (!isNaN(rc)) ev.repeatCount = String(Math.min(rc, index)) | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     // _findEventInAnyList removed (direct map access) |  | ||||||
|  |  | ||||||
|     // NOTE: legacy dynamic getEventById for synthetic occurrences removed. |  | ||||||
|   }, |   }, | ||||||
|   persist: { |   persist: { | ||||||
|     key: 'calendar-store', |     key: 'calendar-store', | ||||||
|     storage: localStorage, |     storage: localStorage, | ||||||
|     // Persist only events map, no dates indexing |  | ||||||
|     paths: ['today', 'config', 'events'], |     paths: ['today', 'config', 'events'], | ||||||
|     serializer: { |     serializer: { | ||||||
|       serialize(value) { |       serialize(value) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Leo Vasanko
					Leo Vasanko