Major new version #2
@ -44,7 +44,7 @@ const fallbackWeekdays = computed(() => {
|
|||||||
return fallback
|
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({
|
const repeat = computed({
|
||||||
get() {
|
get() {
|
||||||
if (!recurrenceEnabled.value) return 'none'
|
if (!recurrenceEnabled.value) return 'none'
|
||||||
@ -56,8 +56,8 @@ const repeat = computed({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
recurrenceEnabled.value = true
|
recurrenceEnabled.value = true
|
||||||
if (val === 'weeks' || val === 'weekly') recurrenceFrequency.value = 'weeks'
|
if (val === 'weeks') recurrenceFrequency.value = 'weeks'
|
||||||
else if (val === 'months' || val === 'monthly') recurrenceFrequency.value = 'months'
|
else if (val === 'months') recurrenceFrequency.value = 'months'
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -164,18 +164,31 @@ function openEditDialog(eventInstanceId) {
|
|||||||
}
|
}
|
||||||
const event = calendarStore.getEventById(baseId)
|
const event = calendarStore.getEventById(baseId)
|
||||||
if (!event) return
|
if (!event) return
|
||||||
// Derive occurrence date if weekly occurrence
|
// Derive occurrence date for repeat occurrences (occurrenceIndex > 0 means not the base)
|
||||||
if (weekday != null) {
|
if (
|
||||||
// Recompute occurrence date: iterate days accumulating selected weekdays
|
event.isRepeating &&
|
||||||
const repeatWeekdaysLocal = event.repeatWeekdays
|
((event.repeat === 'weeks' && occurrenceIndex >= 0) ||
|
||||||
let idx = 0
|
(event.repeat === 'months' && occurrenceIndex > 0))
|
||||||
let cur = new Date(event.startDate + 'T00:00:00')
|
) {
|
||||||
while (idx < occurrenceIndex && idx < 10000) {
|
if (event.repeat === 'weeks' && occurrenceIndex >= 0) {
|
||||||
// safety bound
|
const repeatWeekdaysLocal = event.repeatWeekdays || []
|
||||||
cur.setDate(cur.getDate() + 1)
|
const baseDate = new Date(event.startDate + 'T00:00:00')
|
||||||
if (repeatWeekdaysLocal[cur.getDay()]) idx++
|
// 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'
|
dialogMode.value = 'edit'
|
||||||
editingEventId.value = baseId
|
editingEventId.value = baseId
|
||||||
@ -188,8 +201,13 @@ function openEditDialog(eventInstanceId) {
|
|||||||
recurrenceOccurrences.value = rc === 'unlimited' ? 0 : parseInt(rc, 10) || 0
|
recurrenceOccurrences.value = rc === 'unlimited' ? 0 : parseInt(rc, 10) || 0
|
||||||
colorId.value = event.colorId
|
colorId.value = event.colorId
|
||||||
eventSaved.value = false
|
eventSaved.value = false
|
||||||
if (event.isRepeating && occurrenceIndex >= 0 && weekday != null) {
|
// Build occurrence context: treat any occurrenceIndex > 0 as a specific occurrence (weekday only relevant for weekly)
|
||||||
occurrenceContext.value = { baseId, occurrenceIndex, weekday, occurrenceDate }
|
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
|
showDialog.value = true
|
||||||
|
|
||||||
|
@ -55,12 +55,8 @@ 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,
|
||||||
repeat:
|
// Normalized repeat value: only 'weeks', 'months', or 'none'
|
||||||
(eventData.repeat === 'weekly'
|
repeat: ['weeks', 'months'].includes(eventData.repeat) ? eventData.repeat : 'none',
|
||||||
? 'weeks'
|
|
||||||
: eventData.repeat === 'monthly'
|
|
||||||
? 'months'
|
|
||||||
: 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,
|
||||||
@ -134,35 +130,86 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
deleteSingleOccurrence(ctx) {
|
deleteSingleOccurrence(ctx) {
|
||||||
const { baseId, occurrenceIndex } = ctx
|
const { baseId, occurrenceIndex } = ctx
|
||||||
const base = this.getEventById(baseId)
|
const base = this.getEventById(baseId)
|
||||||
if (!base || base.repeat !== 'weekly') return
|
if (!base || !base.isRepeating) return
|
||||||
if (!base || base.repeat !== 'weeks') return
|
// WEEKLY SERIES ------------------------------------------------------
|
||||||
// Strategy: clone pattern into two: reduce base repeatCount to exclude this occurrence; create new series for remainder excluding this one
|
if (base.repeat === 'weeks') {
|
||||||
// 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.
|
// Strategy: split series around the target occurrence, omitting it.
|
||||||
// Implementation (approx): terminate at occurrenceIndex; create a new series starting next occurrence with remaining occurrences.
|
const remaining =
|
||||||
const remaining =
|
base.repeatCount === 'unlimited'
|
||||||
base.repeatCount === 'unlimited'
|
? 'unlimited'
|
||||||
? 'unlimited'
|
: String(Math.max(0, parseInt(base.repeatCount, 10) - (occurrenceIndex + 1)))
|
||||||
: String(Math.max(0, parseInt(base.repeatCount, 10) - (occurrenceIndex + 1)))
|
// Keep occurrences before the deleted one
|
||||||
this._terminateRepeatSeriesAtIndex(baseId, occurrenceIndex)
|
this._terminateRepeatSeriesAtIndex(baseId, occurrenceIndex)
|
||||||
if (remaining === '0') return
|
if (remaining === '0') return
|
||||||
// Find date of next occurrence
|
// Find date of next occurrence (first after deleted)
|
||||||
const startDate = new Date(base.startDate + 'T00:00:00')
|
const startDate = new Date(base.startDate + 'T00:00:00')
|
||||||
let idx = 0
|
let idx = 0
|
||||||
let cur = new Date(startDate)
|
let cur = new Date(startDate)
|
||||||
while (idx <= occurrenceIndex && idx < 10000) {
|
while (idx <= occurrenceIndex && idx < 10000) {
|
||||||
cur.setDate(cur.getDate() + 1)
|
cur.setDate(cur.getDate() + 1)
|
||||||
if (base.repeatWeekdays[cur.getDay()]) idx++
|
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) {
|
deleteFromOccurrence(ctx) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user