Recurrent event handling bugfixes.
This commit is contained in:
parent
d46aaa6106
commit
1257fba211
@ -44,7 +44,7 @@ const fallbackWeekdays = computed(() => {
|
||||
return fallback
|
||||
})
|
||||
|
||||
// Repeat mapping uses 'weeks' | 'months' | 'none' directly (legacy 'weekly'/'monthly' accepted on load)
|
||||
// Repeat mapping uses 'weeks' | 'months' | 'none' directly
|
||||
const repeat = computed({
|
||||
get() {
|
||||
if (!recurrenceEnabled.value) return 'none'
|
||||
@ -56,8 +56,8 @@ const repeat = computed({
|
||||
return
|
||||
}
|
||||
recurrenceEnabled.value = true
|
||||
if (val === 'weeks' || val === 'weekly') recurrenceFrequency.value = 'weeks'
|
||||
else if (val === 'months' || val === 'monthly') recurrenceFrequency.value = 'months'
|
||||
if (val === 'weeks') recurrenceFrequency.value = 'weeks'
|
||||
else if (val === 'months') recurrenceFrequency.value = 'months'
|
||||
},
|
||||
})
|
||||
|
||||
@ -164,18 +164,31 @@ function openEditDialog(eventInstanceId) {
|
||||
}
|
||||
const event = calendarStore.getEventById(baseId)
|
||||
if (!event) return
|
||||
// Derive occurrence date if weekly occurrence
|
||||
if (weekday != null) {
|
||||
// Recompute occurrence date: iterate days accumulating selected weekdays
|
||||
const repeatWeekdaysLocal = event.repeatWeekdays
|
||||
let idx = 0
|
||||
let cur = new Date(event.startDate + 'T00:00:00')
|
||||
while (idx < occurrenceIndex && idx < 10000) {
|
||||
// safety bound
|
||||
cur.setDate(cur.getDate() + 1)
|
||||
if (repeatWeekdaysLocal[cur.getDay()]) idx++
|
||||
// Derive occurrence date for repeat occurrences (occurrenceIndex > 0 means not the base)
|
||||
if (
|
||||
event.isRepeating &&
|
||||
((event.repeat === 'weeks' && occurrenceIndex >= 0) ||
|
||||
(event.repeat === 'months' && occurrenceIndex > 0))
|
||||
) {
|
||||
if (event.repeat === 'weeks' && occurrenceIndex >= 0) {
|
||||
const repeatWeekdaysLocal = event.repeatWeekdays || []
|
||||
const baseDate = new Date(event.startDate + 'T00:00:00')
|
||||
// occurrenceIndex counts prior occurrences AFTER base;
|
||||
// For occurrenceIndex = 0 we want first matching day after base.
|
||||
let cur = new Date(baseDate)
|
||||
let matched = -1
|
||||
let safety = 0
|
||||
while (matched < occurrenceIndex && safety < 10000) {
|
||||
cur.setDate(cur.getDate() + 1)
|
||||
if (repeatWeekdaysLocal[cur.getDay()]) matched++
|
||||
safety++
|
||||
}
|
||||
occurrenceDate = cur
|
||||
} else if (event.repeat === 'months') {
|
||||
const cur = new Date(event.startDate + 'T00:00:00')
|
||||
cur.setMonth(cur.getMonth() + occurrenceIndex)
|
||||
occurrenceDate = cur
|
||||
}
|
||||
occurrenceDate = cur
|
||||
}
|
||||
dialogMode.value = 'edit'
|
||||
editingEventId.value = baseId
|
||||
@ -188,8 +201,13 @@ function openEditDialog(eventInstanceId) {
|
||||
recurrenceOccurrences.value = rc === 'unlimited' ? 0 : parseInt(rc, 10) || 0
|
||||
colorId.value = event.colorId
|
||||
eventSaved.value = false
|
||||
if (event.isRepeating && occurrenceIndex >= 0 && weekday != null) {
|
||||
occurrenceContext.value = { baseId, occurrenceIndex, weekday, occurrenceDate }
|
||||
// Build occurrence context: treat any occurrenceIndex > 0 as a specific occurrence (weekday only relevant for weekly)
|
||||
if (event.isRepeating) {
|
||||
if (event.repeat === 'weeks' && weekday != null && occurrenceIndex >= 0) {
|
||||
occurrenceContext.value = { baseId, occurrenceIndex, weekday, occurrenceDate }
|
||||
} else if (event.repeat === 'months' && occurrenceIndex > 0) {
|
||||
occurrenceContext.value = { baseId, occurrenceIndex, weekday: null, occurrenceDate }
|
||||
}
|
||||
}
|
||||
showDialog.value = true
|
||||
|
||||
|
@ -55,12 +55,8 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
eventData.colorId ?? this.selectEventColorId(eventData.startDate, eventData.endDate),
|
||||
startTime: singleDay ? eventData.startTime || '09:00' : null,
|
||||
durationMinutes: singleDay ? eventData.durationMinutes || 60 : null,
|
||||
repeat:
|
||||
(eventData.repeat === 'weekly'
|
||||
? 'weeks'
|
||||
: eventData.repeat === 'monthly'
|
||||
? 'months'
|
||||
: eventData.repeat) || 'none',
|
||||
// Normalized repeat value: only 'weeks', 'months', or 'none'
|
||||
repeat: ['weeks', 'months'].includes(eventData.repeat) ? eventData.repeat : 'none',
|
||||
repeatInterval: eventData.repeatInterval || 1,
|
||||
repeatCount: eventData.repeatCount || 'unlimited',
|
||||
repeatWeekdays: eventData.repeatWeekdays,
|
||||
@ -134,35 +130,86 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
deleteSingleOccurrence(ctx) {
|
||||
const { baseId, occurrenceIndex } = ctx
|
||||
const base = this.getEventById(baseId)
|
||||
if (!base || base.repeat !== 'weekly') return
|
||||
if (!base || base.repeat !== 'weeks') return
|
||||
// Strategy: clone pattern into two: reduce base repeatCount to exclude this occurrence; create new series for remainder excluding this one
|
||||
// Simpler: convert base to explicit exception by creating a one-off non-repeating event? For now: create exception by inserting a blocking dummy? Instead just split by shifting repeatCount logic: decrement repeatCount and create a new series starting after this single occurrence.
|
||||
// Implementation (approx): terminate at occurrenceIndex; create a new series starting next occurrence with remaining occurrences.
|
||||
const remaining =
|
||||
base.repeatCount === 'unlimited'
|
||||
? 'unlimited'
|
||||
: String(Math.max(0, parseInt(base.repeatCount, 10) - (occurrenceIndex + 1)))
|
||||
this._terminateRepeatSeriesAtIndex(baseId, occurrenceIndex)
|
||||
if (remaining === '0') return
|
||||
// Find date of next occurrence
|
||||
const startDate = new Date(base.startDate + 'T00:00:00')
|
||||
let idx = 0
|
||||
let cur = new Date(startDate)
|
||||
while (idx <= occurrenceIndex && idx < 10000) {
|
||||
cur.setDate(cur.getDate() + 1)
|
||||
if (base.repeatWeekdays[cur.getDay()]) idx++
|
||||
if (!base || !base.isRepeating) return
|
||||
// WEEKLY SERIES ------------------------------------------------------
|
||||
if (base.repeat === 'weeks') {
|
||||
// Strategy: split series around the target occurrence, omitting it.
|
||||
const remaining =
|
||||
base.repeatCount === 'unlimited'
|
||||
? 'unlimited'
|
||||
: String(Math.max(0, parseInt(base.repeatCount, 10) - (occurrenceIndex + 1)))
|
||||
// Keep occurrences before the deleted one
|
||||
this._terminateRepeatSeriesAtIndex(baseId, occurrenceIndex)
|
||||
if (remaining === '0') return
|
||||
// Find date of next occurrence (first after deleted)
|
||||
const startDate = new Date(base.startDate + 'T00:00:00')
|
||||
let idx = 0
|
||||
let cur = new Date(startDate)
|
||||
while (idx <= occurrenceIndex && idx < 10000) {
|
||||
cur.setDate(cur.getDate() + 1)
|
||||
if (base.repeatWeekdays && base.repeatWeekdays[cur.getDay()]) idx++
|
||||
}
|
||||
const nextStartStr = toLocalString(cur)
|
||||
// Preserve multi‑day span if any
|
||||
const spanDays = Math.round(
|
||||
(fromLocalString(base.endDate) - fromLocalString(base.startDate)) / (24 * 60 * 60 * 1000),
|
||||
)
|
||||
const nextEnd = new Date(fromLocalString(nextStartStr))
|
||||
nextEnd.setDate(nextEnd.getDate() + spanDays)
|
||||
const nextEndStr = toLocalString(nextEnd)
|
||||
this.createEvent({
|
||||
title: base.title,
|
||||
startDate: nextStartStr,
|
||||
endDate: nextEndStr,
|
||||
colorId: base.colorId,
|
||||
repeat: 'weeks',
|
||||
repeatCount: remaining,
|
||||
repeatWeekdays: base.repeatWeekdays,
|
||||
})
|
||||
return
|
||||
}
|
||||
// MONTHLY SERIES -----------------------------------------------------
|
||||
if (base.repeat === 'months') {
|
||||
const interval = base.repeatInterval || 1
|
||||
// occurrenceIndex is the diff in months from base (1-based for first recurrence)
|
||||
if (occurrenceIndex <= 0) return // base itself handled elsewhere
|
||||
if (occurrenceIndex % interval !== 0) return // should not happen (synthetic id only for valid occurrences)
|
||||
// Count prior occurrences (including base) before the deleted one
|
||||
const priorOccurrences = Math.floor((occurrenceIndex - 1) / interval) + 1
|
||||
// Truncate base series to keep only priorOccurrences
|
||||
this._terminateRepeatSeriesAtIndex(baseId, priorOccurrences)
|
||||
// Compute span days for multi‑day events
|
||||
const spanDays = Math.round(
|
||||
(fromLocalString(base.endDate) - fromLocalString(base.startDate)) / (24 * 60 * 60 * 1000),
|
||||
)
|
||||
// Remaining occurrences after deletion
|
||||
let remainingCount = 'unlimited'
|
||||
if (base.repeatCount !== 'unlimited') {
|
||||
const total = parseInt(base.repeatCount, 10)
|
||||
if (!isNaN(total)) {
|
||||
const rem = total - priorOccurrences - 1 // subtract kept + deleted
|
||||
if (rem <= 0) return // nothing left
|
||||
remainingCount = String(rem)
|
||||
}
|
||||
}
|
||||
// Next occurrence after deleted one is at occurrenceIndex + interval months from base
|
||||
const baseStart = fromLocalString(base.startDate)
|
||||
const nextStart = new Date(baseStart)
|
||||
nextStart.setMonth(nextStart.getMonth() + occurrenceIndex + interval)
|
||||
const nextEnd = new Date(nextStart)
|
||||
nextEnd.setDate(nextEnd.getDate() + spanDays)
|
||||
const nextStartStr = toLocalString(nextStart)
|
||||
const nextEndStr = toLocalString(nextEnd)
|
||||
this.createEvent({
|
||||
title: base.title,
|
||||
startDate: nextStartStr,
|
||||
endDate: nextEndStr,
|
||||
colorId: base.colorId,
|
||||
repeat: 'months',
|
||||
repeatInterval: interval,
|
||||
repeatCount: remainingCount,
|
||||
})
|
||||
}
|
||||
const nextStartStr = toLocalString(cur)
|
||||
this.createEvent({
|
||||
title: base.title,
|
||||
startDate: nextStartStr,
|
||||
endDate: nextStartStr,
|
||||
colorId: base.colorId,
|
||||
repeat: 'weeks',
|
||||
repeatCount: remaining,
|
||||
repeatWeekdays: base.repeatWeekdays,
|
||||
})
|
||||
},
|
||||
|
||||
deleteFromOccurrence(ctx) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user