Major new version #2

Merged
LeoVasanko merged 86 commits from vol002 into main 2025-08-26 05:58:24 +01:00
2 changed files with 143 additions and 89 deletions
Showing only changes of commit f1f172d55f - Show all commits

View File

@ -259,6 +259,61 @@ export const useCalendarStore = defineStore('calendar', {
if (!base || !base.isRepeating) return if (!base || !base.isRepeating) return
// WEEKLY SERIES ------------------------------------------------------ // WEEKLY SERIES ------------------------------------------------------
if (base.repeat === 'weeks') { 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 interval = base.repeatInterval || 1
const pattern = base.repeatWeekdays || [] const pattern = base.repeatWeekdays || []
if (!pattern.some(Boolean)) return if (!pattern.some(Boolean)) return
@ -372,6 +427,36 @@ export const useCalendarStore = defineStore('calendar', {
} }
// MONTHLY SERIES ----------------------------------------------------- // MONTHLY SERIES -----------------------------------------------------
if (base.repeat === 'months') { 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 const interval = base.repeatInterval || 1
// Sequential index: base=0, first repeat=1 // Sequential index: base=0, first repeat=1
if (occurrenceIndex <= 0) return // base deletion handled elsewhere if (occurrenceIndex <= 0) return // base deletion handled elsewhere
@ -434,47 +519,67 @@ export const useCalendarStore = defineStore('calendar', {
deleteFirstOccurrence(baseId) { deleteFirstOccurrence(baseId) {
const base = this.getEventById(baseId) const base = this.getEventById(baseId)
if (!base || !base.isRepeating) return if (!base || !base.isRepeating) return
const oldStart = new Date(fromLocalString(base.startDate)) const oldStart = fromLocalString(base.startDate)
const oldEnd = new Date(fromLocalString(base.endDate)) const oldEnd = fromLocalString(base.endDate)
const spanDays = Math.round((oldEnd - oldStart) / (24 * 60 * 60 * 1000)) const spanDays = Math.max(0, Math.round((oldEnd - oldStart) / (24 * 60 * 60 * 1000)))
let newStart = null
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) const probe = new Date(oldStart)
for (let i = 0; i < 14; i++) { let safety = 0
// search ahead up to 2 weeks while (safety < 5000) {
probe.setDate(probe.getDate() + 1) probe.setDate(probe.getDate() + 1)
if (base.repeatWeekdays[probe.getDay()]) { if (pattern[probe.getDay()] && isAligned(probe)) {
newStart = new Date(probe) newStartDate = new Date(probe)
break break
} }
safety++
} }
} else if (base.repeat === 'months') { } else if (base.repeat === 'months') {
// Advance one month, clamping to last day if necessary const interval = base.repeatInterval || 1
const o = oldStart const y = oldStart.getFullYear()
const nextMonthIndex = o.getMonth() + 1 const m = oldStart.getMonth()
const y = o.getFullYear() + Math.floor(nextMonthIndex / 12) const targetMonthIndex = m + interval
const m = nextMonthIndex % 12 const targetYear = y + Math.floor(targetMonthIndex / 12)
const daysInTargetMonth = new Date(y, m + 1, 0).getDate() const targetMonth = targetMonthIndex % 12
const dom = Math.min(o.getDate(), daysInTargetMonth) const daysInTargetMonth = new Date(targetYear, targetMonth + 1, 0).getDate()
newStart = new Date(y, m, dom) const dom = Math.min(oldStart.getDate(), daysInTargetMonth)
newStartDate = new Date(targetYear, targetMonth, dom)
} else { } else {
// Unknown pattern: delete entire series // Unsupported repeat type
this.deleteEvent(baseId) this.deleteEvent(baseId)
return return
} }
if (!newStart) { if (!newStartDate) {
// No subsequent occurrence -> delete entire series // No continuation; deleting first removes series
this.deleteEvent(baseId) this.deleteEvent(baseId)
return return
} }
// Decrement repeatCount if limited
if (base.repeatCount !== 'unlimited') { if (base.repeatCount !== 'unlimited') {
const rc = parseInt(base.repeatCount, 10) const rc = parseInt(base.repeatCount, 10)
if (!isNaN(rc)) { if (!isNaN(rc)) {
const newRc = Math.max(0, rc - 1) const newRc = rc - 1
if (newRc === 0) { if (newRc <= 0) {
// After removing first occurrence there are none left
this.deleteEvent(baseId) this.deleteEvent(baseId)
return return
} }
@ -482,65 +587,14 @@ export const useCalendarStore = defineStore('calendar', {
} }
} }
const newEnd = new Date(newStart) const newEndDate = new Date(newStartDate)
newEnd.setDate(newEnd.getDate() + spanDays) newEndDate.setDate(newEndDate.getDate() + spanDays)
base.startDate = toLocalString(newStart) base.startDate = toLocalString(newStartDate)
base.endDate = toLocalString(newEnd) base.endDate = toLocalString(newEndDate)
// old occurrence expansion removed (series handled differently now) base.isSpanning = base.startDate < base.endDate
const originalRepeatCount = base.repeatCount // Persist updated base event
// Always cap original series at the split occurrence index (occurrences 0..index-1) this.events.set(base.id, base)
// Keep its weekday pattern unchanged. return base.id
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
}, },
_snapshotBaseEvent(eventId) { _snapshotBaseEvent(eventId) {