Major new version #2
@ -259,6 +259,61 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
if (!base || !base.isRepeating) return
|
||||
// WEEKLY SERIES ------------------------------------------------------
|
||||
if (base.repeat === 'weeks') {
|
||||
// Special case: deleting the first occurrence (index 0) should shift the series forward
|
||||
if (occurrenceIndex === 0) {
|
||||
const baseStart = fromLocalString(base.startDate)
|
||||
const baseEnd = fromLocalString(base.endDate)
|
||||
const spanDays = Math.max(0, Math.round((baseEnd - baseStart) / (24 * 60 * 60 * 1000)))
|
||||
const pattern = base.repeatWeekdays || []
|
||||
if (!pattern.some(Boolean)) {
|
||||
// No pattern to continue -> delete whole series
|
||||
this.deleteEvent(baseId)
|
||||
return
|
||||
}
|
||||
const interval = base.repeatInterval || 1
|
||||
const WEEK_MS = 7 * 86400000
|
||||
const baseBlockStart = getMondayOfISOWeek(baseStart)
|
||||
const isAligned = (d) => {
|
||||
const blk = getMondayOfISOWeek(d)
|
||||
const diff = Math.floor((blk - baseBlockStart) / WEEK_MS)
|
||||
return diff % interval === 0
|
||||
}
|
||||
const probe = new Date(baseStart)
|
||||
let safety = 0
|
||||
let found = null
|
||||
while (safety < 5000) {
|
||||
probe.setDate(probe.getDate() + 1)
|
||||
if (pattern[probe.getDay()] && isAligned(probe)) {
|
||||
found = new Date(probe)
|
||||
break
|
||||
}
|
||||
safety++
|
||||
}
|
||||
if (!found) {
|
||||
// Nothing after first -> delete series
|
||||
this.deleteEvent(baseId)
|
||||
return
|
||||
}
|
||||
// Adjust repeat count
|
||||
if (base.repeatCount !== 'unlimited') {
|
||||
const rc = parseInt(base.repeatCount, 10)
|
||||
if (!isNaN(rc)) {
|
||||
const newRc = rc - 1
|
||||
if (newRc <= 0) {
|
||||
this.deleteEvent(baseId)
|
||||
return
|
||||
}
|
||||
base.repeatCount = String(newRc)
|
||||
}
|
||||
}
|
||||
const newEnd = new Date(found)
|
||||
newEnd.setDate(newEnd.getDate() + spanDays)
|
||||
base.startDate = toLocalString(found)
|
||||
base.endDate = toLocalString(newEnd)
|
||||
base.isSpanning = base.startDate < base.endDate
|
||||
this.events.set(base.id, base)
|
||||
return
|
||||
}
|
||||
const interval = base.repeatInterval || 1
|
||||
const pattern = base.repeatWeekdays || []
|
||||
if (!pattern.some(Boolean)) return
|
||||
@ -372,6 +427,36 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
}
|
||||
// MONTHLY SERIES -----------------------------------------------------
|
||||
if (base.repeat === 'months') {
|
||||
if (occurrenceIndex === 0) {
|
||||
const baseStart = fromLocalString(base.startDate)
|
||||
const baseEnd = fromLocalString(base.endDate)
|
||||
const spanDays = Math.max(0, Math.round((baseEnd - baseStart) / (24 * 60 * 60 * 1000)))
|
||||
const interval = base.repeatInterval || 1
|
||||
const targetMonthIndex = baseStart.getMonth() + interval
|
||||
const targetYear = baseStart.getFullYear() + Math.floor(targetMonthIndex / 12)
|
||||
const targetMonth = targetMonthIndex % 12
|
||||
const daysInTarget = new Date(targetYear, targetMonth + 1, 0).getDate()
|
||||
const dom = Math.min(baseStart.getDate(), daysInTarget)
|
||||
const newStart = new Date(targetYear, targetMonth, dom)
|
||||
if (base.repeatCount !== 'unlimited') {
|
||||
const rc = parseInt(base.repeatCount, 10)
|
||||
if (!isNaN(rc)) {
|
||||
const newRc = rc - 1
|
||||
if (newRc <= 0) {
|
||||
this.deleteEvent(baseId)
|
||||
return
|
||||
}
|
||||
base.repeatCount = String(newRc)
|
||||
}
|
||||
}
|
||||
const newEnd = new Date(newStart)
|
||||
newEnd.setDate(newEnd.getDate() + spanDays)
|
||||
base.startDate = toLocalString(newStart)
|
||||
base.endDate = toLocalString(newEnd)
|
||||
base.isSpanning = base.startDate < base.endDate
|
||||
this.events.set(base.id, base)
|
||||
return
|
||||
}
|
||||
const interval = base.repeatInterval || 1
|
||||
// Sequential index: base=0, first repeat=1
|
||||
if (occurrenceIndex <= 0) return // base deletion handled elsewhere
|
||||
@ -434,47 +519,67 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
deleteFirstOccurrence(baseId) {
|
||||
const base = this.getEventById(baseId)
|
||||
if (!base || !base.isRepeating) return
|
||||
const oldStart = new Date(fromLocalString(base.startDate))
|
||||
const oldEnd = new Date(fromLocalString(base.endDate))
|
||||
const spanDays = Math.round((oldEnd - oldStart) / (24 * 60 * 60 * 1000))
|
||||
let newStart = null
|
||||
const oldStart = fromLocalString(base.startDate)
|
||||
const oldEnd = fromLocalString(base.endDate)
|
||||
const spanDays = Math.max(0, Math.round((oldEnd - oldStart) / (24 * 60 * 60 * 1000)))
|
||||
|
||||
if (base.repeat === 'weeks' && base.repeatWeekdays) {
|
||||
let newStartDate = null
|
||||
|
||||
if (base.repeat === 'weeks') {
|
||||
const pattern = base.repeatWeekdays || []
|
||||
if (!pattern.some(Boolean)) {
|
||||
// No valid pattern -> delete series
|
||||
this.deleteEvent(baseId)
|
||||
return
|
||||
}
|
||||
const interval = base.repeatInterval || 1
|
||||
const baseBlockStart = getMondayOfISOWeek(oldStart)
|
||||
const WEEK_MS = 7 * 86400000
|
||||
const isAligned = (d) => {
|
||||
const block = getMondayOfISOWeek(d)
|
||||
const diff = Math.floor((block - baseBlockStart) / WEEK_MS)
|
||||
return diff % interval === 0
|
||||
}
|
||||
// search forward for next valid weekday respecting interval alignment
|
||||
const probe = new Date(oldStart)
|
||||
for (let i = 0; i < 14; i++) {
|
||||
// search ahead up to 2 weeks
|
||||
let safety = 0
|
||||
while (safety < 5000) {
|
||||
probe.setDate(probe.getDate() + 1)
|
||||
if (base.repeatWeekdays[probe.getDay()]) {
|
||||
newStart = new Date(probe)
|
||||
if (pattern[probe.getDay()] && isAligned(probe)) {
|
||||
newStartDate = new Date(probe)
|
||||
break
|
||||
}
|
||||
safety++
|
||||
}
|
||||
} else if (base.repeat === 'months') {
|
||||
// Advance one month, clamping to last day if necessary
|
||||
const o = oldStart
|
||||
const nextMonthIndex = o.getMonth() + 1
|
||||
const y = o.getFullYear() + Math.floor(nextMonthIndex / 12)
|
||||
const m = nextMonthIndex % 12
|
||||
const daysInTargetMonth = new Date(y, m + 1, 0).getDate()
|
||||
const dom = Math.min(o.getDate(), daysInTargetMonth)
|
||||
newStart = new Date(y, m, dom)
|
||||
const interval = base.repeatInterval || 1
|
||||
const y = oldStart.getFullYear()
|
||||
const m = oldStart.getMonth()
|
||||
const targetMonthIndex = m + interval
|
||||
const targetYear = y + Math.floor(targetMonthIndex / 12)
|
||||
const targetMonth = targetMonthIndex % 12
|
||||
const daysInTargetMonth = new Date(targetYear, targetMonth + 1, 0).getDate()
|
||||
const dom = Math.min(oldStart.getDate(), daysInTargetMonth)
|
||||
newStartDate = new Date(targetYear, targetMonth, dom)
|
||||
} else {
|
||||
// Unknown pattern: delete entire series
|
||||
// Unsupported repeat type
|
||||
this.deleteEvent(baseId)
|
||||
return
|
||||
}
|
||||
|
||||
if (!newStart) {
|
||||
// No subsequent occurrence -> delete entire series
|
||||
if (!newStartDate) {
|
||||
// No continuation; deleting first removes series
|
||||
this.deleteEvent(baseId)
|
||||
return
|
||||
}
|
||||
|
||||
// Decrement repeatCount if limited
|
||||
if (base.repeatCount !== 'unlimited') {
|
||||
const rc = parseInt(base.repeatCount, 10)
|
||||
if (!isNaN(rc)) {
|
||||
const newRc = Math.max(0, rc - 1)
|
||||
if (newRc === 0) {
|
||||
const newRc = rc - 1
|
||||
if (newRc <= 0) {
|
||||
// After removing first occurrence there are none left
|
||||
this.deleteEvent(baseId)
|
||||
return
|
||||
}
|
||||
@ -482,65 +587,14 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
}
|
||||
}
|
||||
|
||||
const newEnd = new Date(newStart)
|
||||
newEnd.setDate(newEnd.getDate() + spanDays)
|
||||
base.startDate = toLocalString(newStart)
|
||||
base.endDate = toLocalString(newEnd)
|
||||
// old occurrence expansion removed (series handled differently now)
|
||||
const originalRepeatCount = base.repeatCount
|
||||
// Always cap original series at the split occurrence index (occurrences 0..index-1)
|
||||
// Keep its weekday pattern unchanged.
|
||||
this._terminateRepeatSeriesAtIndex(baseId, index)
|
||||
|
||||
let newRepeatCount = 'unlimited'
|
||||
if (originalRepeatCount !== 'unlimited') {
|
||||
const originalCount = parseInt(originalRepeatCount, 10)
|
||||
if (!isNaN(originalCount)) {
|
||||
const remaining = originalCount - index
|
||||
// remaining occurrences go to new series; ensure at least 1 (the dragged occurrence itself)
|
||||
newRepeatCount = remaining > 0 ? String(remaining) : '1'
|
||||
}
|
||||
} else {
|
||||
// Original was unlimited: original now capped, new stays unlimited
|
||||
newRepeatCount = 'unlimited'
|
||||
}
|
||||
|
||||
// Handle weekdays for weekly repeats
|
||||
let newRepeatWeekdays = base.repeatWeekdays
|
||||
if (base.repeat === 'weeks' && base.repeatWeekdays) {
|
||||
const newStartDate = new Date(fromLocalString(startDate))
|
||||
let dayShift = 0
|
||||
if (grabbedWeekday != null) {
|
||||
// Rotate so that the grabbed weekday maps to the new start weekday
|
||||
dayShift = newStartDate.getDay() - grabbedWeekday
|
||||
} else {
|
||||
// Fallback: rotate by difference between new and original start weekday
|
||||
const originalStartDate = new Date(fromLocalString(base.startDate))
|
||||
dayShift = newStartDate.getDay() - originalStartDate.getDay()
|
||||
}
|
||||
if (dayShift !== 0) {
|
||||
const rotatedWeekdays = [false, false, false, false, false, false, false]
|
||||
for (let i = 0; i < 7; i++) {
|
||||
if (base.repeatWeekdays[i]) {
|
||||
let nd = (i + dayShift) % 7
|
||||
if (nd < 0) nd += 7
|
||||
rotatedWeekdays[nd] = true
|
||||
}
|
||||
}
|
||||
newRepeatWeekdays = rotatedWeekdays
|
||||
}
|
||||
}
|
||||
|
||||
const newId = this.createEvent({
|
||||
title: base.title,
|
||||
startDate,
|
||||
endDate,
|
||||
colorId: base.colorId,
|
||||
repeat: base.repeat,
|
||||
repeatCount: newRepeatCount,
|
||||
repeatWeekdays: newRepeatWeekdays,
|
||||
})
|
||||
return newId
|
||||
const newEndDate = new Date(newStartDate)
|
||||
newEndDate.setDate(newEndDate.getDate() + spanDays)
|
||||
base.startDate = toLocalString(newStartDate)
|
||||
base.endDate = toLocalString(newEndDate)
|
||||
base.isSpanning = base.startDate < base.endDate
|
||||
// Persist updated base event
|
||||
this.events.set(base.id, base)
|
||||
return base.id
|
||||
},
|
||||
|
||||
_snapshotBaseEvent(eventId) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user