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
// 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) {