Recurrent/weekday input fixes. Refactored event store to use recur map rather than separate properties.
This commit is contained in:
parent
b69a299309
commit
29246af591
@ -29,6 +29,7 @@ const dialogMode = ref('create') // 'create' or 'edit'
|
|||||||
const editingEventId = ref(null)
|
const editingEventId = ref(null)
|
||||||
const unsavedCreateId = ref(null)
|
const unsavedCreateId = ref(null)
|
||||||
const occurrenceContext = ref(null) // { baseId, occurrenceIndex, weekday, occurrenceDate }
|
const occurrenceContext = ref(null) // { baseId, occurrenceIndex, weekday, occurrenceDate }
|
||||||
|
const initialWeekday = ref(null)
|
||||||
const title = computed({
|
const title = computed({
|
||||||
get() {
|
get() {
|
||||||
if (editingEventId.value && calendarStore.events?.has(editingEventId.value)) {
|
if (editingEventId.value && calendarStore.events?.has(editingEventId.value)) {
|
||||||
@ -63,10 +64,13 @@ function getStartingWeekday(selectionData = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fallbackWeekdays = computed(() => {
|
const fallbackWeekdays = computed(() => {
|
||||||
const startingDay = getStartingWeekday()
|
let weekday = initialWeekday.value
|
||||||
const fallback = [false, false, false, false, false, false, false]
|
if (weekday == null) {
|
||||||
fallback[startingDay] = true
|
weekday = getStartingWeekday()
|
||||||
return fallback
|
}
|
||||||
|
const fb = [false, false, false, false, false, false, false]
|
||||||
|
fb[weekday] = true
|
||||||
|
return fb
|
||||||
})
|
})
|
||||||
|
|
||||||
// Maps UI frequency display (including years) to store frequency (weeks/months only)
|
// Maps UI frequency display (including years) to store frequency (weeks/months only)
|
||||||
@ -140,14 +144,24 @@ const selectedColor = computed({
|
|||||||
const repeatCountBinding = computed({
|
const repeatCountBinding = computed({
|
||||||
get() {
|
get() {
|
||||||
if (editingEventId.value && calendarStore.events?.has(editingEventId.value)) {
|
if (editingEventId.value && calendarStore.events?.has(editingEventId.value)) {
|
||||||
const rc = calendarStore.events.get(editingEventId.value).repeatCount
|
const ev = calendarStore.events.get(editingEventId.value)
|
||||||
|
const rc = ev.recur?.count ?? 'unlimited'
|
||||||
return rc === 'unlimited' ? 0 : parseInt(rc, 10) || 0
|
return rc === 'unlimited' ? 0 : parseInt(rc, 10) || 0
|
||||||
}
|
}
|
||||||
return recurrenceOccurrences.value
|
return recurrenceOccurrences.value
|
||||||
},
|
},
|
||||||
set(v) {
|
set(v) {
|
||||||
if (editingEventId.value && calendarStore.events?.has(editingEventId.value)) {
|
if (editingEventId.value && calendarStore.events?.has(editingEventId.value)) {
|
||||||
calendarStore.events.get(editingEventId.value).repeatCount = v === 0 ? 'unlimited' : String(v)
|
const ev = calendarStore.events.get(editingEventId.value)
|
||||||
|
if (!ev.recur && v !== 0) {
|
||||||
|
ev.recur = {
|
||||||
|
freq: recurrenceFrequency.value,
|
||||||
|
interval: recurrenceInterval.value,
|
||||||
|
count: 'unlimited',
|
||||||
|
weekdays: recurrenceFrequency.value === 'weeks' ? buildStoreWeekdayPattern() : null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.recur) ev.recur.count = v === 0 ? 'unlimited' : String(v)
|
||||||
calendarStore.touchEvents()
|
calendarStore.touchEvents()
|
||||||
}
|
}
|
||||||
recurrenceOccurrences.value = v
|
recurrenceOccurrences.value = v
|
||||||
@ -178,14 +192,7 @@ const repeat = computed({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function buildStoreWeekdayPattern() {
|
function buildStoreWeekdayPattern() {
|
||||||
let sunFirst = [...recurrenceWeekdays.value]
|
return [...recurrenceWeekdays.value]
|
||||||
|
|
||||||
if (!sunFirst.some(Boolean)) {
|
|
||||||
const startingDay = getStartingWeekday()
|
|
||||||
sunFirst[startingDay] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return sunFirst
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadWeekdayPatternFromStore(storePattern) {
|
function loadWeekdayPatternFromStore(storePattern) {
|
||||||
@ -222,6 +229,7 @@ function openCreateDialog(selectionData = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
occurrenceContext.value = null
|
occurrenceContext.value = null
|
||||||
|
initialWeekday.value = null
|
||||||
dialogMode.value = 'create'
|
dialogMode.value = 'create'
|
||||||
recurrenceEnabled.value = false
|
recurrenceEnabled.value = false
|
||||||
recurrenceInterval.value = 1
|
recurrenceInterval.value = 1
|
||||||
@ -234,17 +242,23 @@ function openCreateDialog(selectionData = null) {
|
|||||||
|
|
||||||
const startingDay = getStartingWeekday({ start, end })
|
const startingDay = getStartingWeekday({ start, end })
|
||||||
recurrenceWeekdays.value[startingDay] = true
|
recurrenceWeekdays.value[startingDay] = true
|
||||||
|
initialWeekday.value = startingDay
|
||||||
|
|
||||||
editingEventId.value = calendarStore.createEvent({
|
editingEventId.value = calendarStore.createEvent({
|
||||||
title: '',
|
title: '',
|
||||||
startDate: start,
|
startDate: start,
|
||||||
endDate: end,
|
endDate: end,
|
||||||
colorId: colorId.value,
|
colorId: colorId.value,
|
||||||
repeat: repeat.value,
|
recur:
|
||||||
repeatInterval: recurrenceInterval.value,
|
recurrenceEnabled.value && repeat.value !== 'none'
|
||||||
repeatCount:
|
? {
|
||||||
recurrenceOccurrences.value === 0 ? 'for now' : String(recurrenceOccurrences.value),
|
freq: recurrenceFrequency.value,
|
||||||
repeatWeekdays: buildStoreWeekdayPattern(),
|
interval: recurrenceInterval.value,
|
||||||
|
count:
|
||||||
|
recurrenceOccurrences.value === 0 ? 'unlimited' : String(recurrenceOccurrences.value),
|
||||||
|
weekdays: recurrenceFrequency.value === 'weeks' ? buildStoreWeekdayPattern() : null,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
})
|
})
|
||||||
unsavedCreateId.value = editingEventId.value
|
unsavedCreateId.value = editingEventId.value
|
||||||
|
|
||||||
@ -277,6 +291,7 @@ function openEditDialog(payload) {
|
|||||||
unsavedCreateId.value = null
|
unsavedCreateId.value = null
|
||||||
}
|
}
|
||||||
occurrenceContext.value = null
|
occurrenceContext.value = null
|
||||||
|
initialWeekday.value = null
|
||||||
if (!payload) return
|
if (!payload) return
|
||||||
|
|
||||||
const baseId = payload.id
|
const baseId = payload.id
|
||||||
@ -287,16 +302,16 @@ function openEditDialog(payload) {
|
|||||||
const event = calendarStore.getEventById(baseId)
|
const event = calendarStore.getEventById(baseId)
|
||||||
if (!event) return
|
if (!event) return
|
||||||
|
|
||||||
if (event.isRepeating) {
|
if (event.recur) {
|
||||||
if (event.repeat === 'weeks' && occurrenceIndex >= 0) {
|
if (event.recur.freq === 'weeks' && occurrenceIndex >= 0) {
|
||||||
const pattern = event.repeatWeekdays || []
|
const pattern = event.recur.weekdays || []
|
||||||
const baseStart = fromLocalString(event.startDate, DEFAULT_TZ)
|
const baseStart = fromLocalString(event.startDate, DEFAULT_TZ)
|
||||||
const baseEnd = fromLocalString(event.endDate, DEFAULT_TZ)
|
const baseEnd = fromLocalString(event.endDate, DEFAULT_TZ)
|
||||||
if (occurrenceIndex === 0) {
|
if (occurrenceIndex === 0) {
|
||||||
occurrenceDate = baseStart
|
occurrenceDate = baseStart
|
||||||
weekday = baseStart.getDay()
|
weekday = baseStart.getDay()
|
||||||
} else {
|
} else {
|
||||||
const interval = event.repeatInterval || 1
|
const interval = event.recur.interval || 1
|
||||||
const WEEK_MS = 7 * 86400000
|
const WEEK_MS = 7 * 86400000
|
||||||
const baseBlockStart = getMondayOfISOWeek(baseStart)
|
const baseBlockStart = getMondayOfISOWeek(baseStart)
|
||||||
function isAligned(d) {
|
function isAligned(d) {
|
||||||
@ -318,22 +333,24 @@ function openEditDialog(payload) {
|
|||||||
occurrenceDate = cur
|
occurrenceDate = cur
|
||||||
weekday = cur.getDay()
|
weekday = cur.getDay()
|
||||||
}
|
}
|
||||||
} else if (event.repeat === 'months' && occurrenceIndex >= 0) {
|
} else if (event.recur.freq === 'months' && occurrenceIndex >= 0) {
|
||||||
const baseDate = fromLocalString(event.startDate, DEFAULT_TZ)
|
const baseDate = fromLocalString(event.startDate, DEFAULT_TZ)
|
||||||
occurrenceDate = addMonths(baseDate, occurrenceIndex)
|
occurrenceDate = addMonths(baseDate, occurrenceIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialogMode.value = 'edit'
|
dialogMode.value = 'edit'
|
||||||
editingEventId.value = baseId
|
editingEventId.value = baseId
|
||||||
loadWeekdayPatternFromStore(event.repeatWeekdays)
|
loadWeekdayPatternFromStore(event.recur?.weekdays)
|
||||||
repeat.value = event.repeat // triggers setter mapping into recurrence state
|
initialWeekday.value =
|
||||||
if (event.repeatInterval) recurrenceInterval.value = event.repeatInterval
|
weekday != null ? weekday : fromLocalString(event.startDate, DEFAULT_TZ).getDay()
|
||||||
|
repeat.value = event.recur ? event.recur.freq : 'none'
|
||||||
|
if (event.recur?.interval) recurrenceInterval.value = event.recur.interval
|
||||||
|
|
||||||
// Set UI display frequency based on loaded data
|
// Set UI display frequency based on loaded data
|
||||||
if (event.repeat === 'weeks') {
|
if (event.recur?.freq === 'weeks') {
|
||||||
uiDisplayFrequency.value = 'weeks'
|
uiDisplayFrequency.value = 'weeks'
|
||||||
} else if (event.repeat === 'months') {
|
} else if (event.recur?.freq === 'months') {
|
||||||
if (event.repeatInterval && event.repeatInterval % 12 === 0 && event.repeatInterval >= 12) {
|
if (event.recur.interval && event.recur.interval % 12 === 0 && event.recur.interval >= 12) {
|
||||||
uiDisplayFrequency.value = 'years'
|
uiDisplayFrequency.value = 'years'
|
||||||
} else {
|
} else {
|
||||||
uiDisplayFrequency.value = 'months'
|
uiDisplayFrequency.value = 'months'
|
||||||
@ -342,15 +359,15 @@ function openEditDialog(payload) {
|
|||||||
uiDisplayFrequency.value = 'weeks'
|
uiDisplayFrequency.value = 'weeks'
|
||||||
}
|
}
|
||||||
|
|
||||||
const rc = event.repeatCount ?? 'unlimited'
|
const rc = event.recur?.count ?? 'unlimited'
|
||||||
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) {
|
if (event.recur) {
|
||||||
if (event.repeat === 'weeks' && occurrenceIndex >= 0) {
|
if (event.recur.freq === 'weeks' && occurrenceIndex >= 0) {
|
||||||
occurrenceContext.value = { baseId, occurrenceIndex, weekday, occurrenceDate }
|
occurrenceContext.value = { baseId, occurrenceIndex, weekday, occurrenceDate }
|
||||||
} else if (event.repeat === 'months' && occurrenceIndex > 0) {
|
} else if (event.recur.freq === 'months' && occurrenceIndex > 0) {
|
||||||
occurrenceContext.value = { baseId, occurrenceIndex, weekday: null, occurrenceDate }
|
occurrenceContext.value = { baseId, occurrenceIndex, weekday: null, occurrenceDate }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -379,12 +396,17 @@ function updateEventInStore() {
|
|||||||
if (calendarStore.events?.has(editingEventId.value)) {
|
if (calendarStore.events?.has(editingEventId.value)) {
|
||||||
const event = calendarStore.events.get(editingEventId.value)
|
const event = calendarStore.events.get(editingEventId.value)
|
||||||
event.colorId = colorId.value
|
event.colorId = colorId.value
|
||||||
event.repeat = repeat.value
|
if (recurrenceEnabled.value && repeat.value !== 'none') {
|
||||||
event.repeatInterval = recurrenceInterval.value
|
event.recur = {
|
||||||
event.repeatWeekdays = buildStoreWeekdayPattern()
|
freq: recurrenceFrequency.value,
|
||||||
event.repeatCount =
|
interval: recurrenceInterval.value,
|
||||||
recurrenceOccurrences.value === 0 ? 'unlimited' : String(recurrenceOccurrences.value)
|
count:
|
||||||
event.isRepeating = recurrenceEnabled.value && repeat.value !== 'none'
|
recurrenceOccurrences.value === 0 ? 'unlimited' : String(recurrenceOccurrences.value),
|
||||||
|
weekdays: recurrenceFrequency.value === 'weeks' ? buildStoreWeekdayPattern() : null,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
event.recur = null
|
||||||
|
}
|
||||||
calendarStore.touchEvents()
|
calendarStore.touchEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -468,11 +490,9 @@ const isRepeatingBaseEdit = computed(() => isRepeatingEdit.value && !occurrenceC
|
|||||||
const isLastOccurrence = computed(() => {
|
const isLastOccurrence = computed(() => {
|
||||||
if (!occurrenceContext.value || !editingEventId.value) return false
|
if (!occurrenceContext.value || !editingEventId.value) return false
|
||||||
const event = calendarStore.getEventById(editingEventId.value)
|
const event = calendarStore.getEventById(editingEventId.value)
|
||||||
if (!event || !event.isRepeating) return false
|
if (!event || !event.recur) return false
|
||||||
|
if (event.recur.count === 'unlimited' || recurrenceOccurrences.value === 0) return false
|
||||||
if (event.repeatCount === 'unlimited' || recurrenceOccurrences.value === 0) return false
|
const totalCount = parseInt(event.recur.count, 10) || 0
|
||||||
|
|
||||||
const totalCount = parseInt(event.repeatCount, 10) || 0
|
|
||||||
return occurrenceContext.value.occurrenceIndex === totalCount - 1
|
return occurrenceContext.value.occurrenceIndex === totalCount - 1
|
||||||
})
|
})
|
||||||
const formattedOccurrenceShort = computed(() => {
|
const formattedOccurrenceShort = computed(() => {
|
||||||
|
@ -176,11 +176,11 @@ function startLocalDrag(init, evt) {
|
|||||||
const baseEv = store.getEventById(init.id)
|
const baseEv = store.getEventById(init.id)
|
||||||
if (
|
if (
|
||||||
baseEv &&
|
baseEv &&
|
||||||
baseEv.isRepeating &&
|
baseEv.recur &&
|
||||||
baseEv.repeat === 'weeks' &&
|
baseEv.recur.freq === 'weeks' &&
|
||||||
Array.isArray(baseEv.repeatWeekdays)
|
Array.isArray(baseEv.recur.weekdays)
|
||||||
) {
|
) {
|
||||||
originalPattern = [...baseEv.repeatWeekdays]
|
originalPattern = [...baseEv.recur.weekdays]
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
@ -286,8 +286,8 @@ function onDragPointerMove(e) {
|
|||||||
const shift = currentWeekday - st.originalWeekday
|
const shift = currentWeekday - st.originalWeekday
|
||||||
const rotated = store._rotateWeekdayPattern([...st.originalPattern], shift)
|
const rotated = store._rotateWeekdayPattern([...st.originalPattern], shift)
|
||||||
const ev = store.getEventById(st.id)
|
const ev = store.getEventById(st.id)
|
||||||
if (ev && ev.repeat === 'weeks') {
|
if (ev && ev.recur && ev.recur.freq === 'weeks') {
|
||||||
ev.repeatWeekdays = rotated
|
ev.recur.weekdays = rotated
|
||||||
store.touchEvents()
|
store.touchEvents()
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import {
|
import {
|
||||||
getLocalizedWeekdayNames,
|
getLocalizedWeekdayNames,
|
||||||
getLocaleFirstDay,
|
getLocaleFirstDay,
|
||||||
@ -44,7 +44,10 @@ import {
|
|||||||
const model = defineModel({
|
const model = defineModel({
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [false, false, false, false, false, false, false],
|
default: () => [false, false, false, false, false, false, false],
|
||||||
})
|
}) // external value consumers see
|
||||||
|
|
||||||
|
// Internal state preserves the user's explicit picks even if all false
|
||||||
|
const internal = ref([false, false, false, false, false, false, false])
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
weekend: { type: Array, default: undefined },
|
weekend: { type: Array, default: undefined },
|
||||||
@ -55,12 +58,11 @@ const props = defineProps({
|
|||||||
firstDay: { type: Number, default: null },
|
firstDay: { type: Number, default: null },
|
||||||
})
|
})
|
||||||
|
|
||||||
// If external model provided is entirely false, keep as-is (user will see fallback styling),
|
// Initialize internal from external if it has any true; else keep empty (fallback handled on emit)
|
||||||
// only overwrite if null/undefined.
|
if (model.value?.some?.(Boolean)) internal.value = [...model.value]
|
||||||
if (!model.value) model.value = [...props.fallback]
|
|
||||||
const labelsMondayFirst = getLocalizedWeekdayNames()
|
const labelsMondayFirst = getLocalizedWeekdayNames()
|
||||||
const labels = [labelsMondayFirst[6], ...labelsMondayFirst.slice(0, 6)]
|
const labels = [labelsMondayFirst[6], ...labelsMondayFirst.slice(0, 6)]
|
||||||
const anySelected = computed(() => model.value.some(Boolean))
|
const anySelected = computed(() => internal.value.some(Boolean))
|
||||||
const localeFirst = getLocaleFirstDay()
|
const localeFirst = getLocaleFirstDay()
|
||||||
const localeWeekend = getLocaleWeekendDays()
|
const localeWeekend = getLocaleWeekendDays()
|
||||||
const firstDay = computed(() => (props.firstDay ?? localeFirst) % 7)
|
const firstDay = computed(() => (props.firstDay ?? localeFirst) % 7)
|
||||||
@ -71,10 +73,38 @@ const weekendDays = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const displayLabels = computed(() => reorderByFirstDay(labels, firstDay.value))
|
const displayLabels = computed(() => reorderByFirstDay(labels, firstDay.value))
|
||||||
const displayValuesCommitted = computed(() => reorderByFirstDay(model.value, firstDay.value))
|
const displayValuesCommitted = computed(() => reorderByFirstDay(internal.value, firstDay.value))
|
||||||
const displayWorking = computed(() => reorderByFirstDay(weekendDays.value, firstDay.value))
|
const displayWorking = computed(() => reorderByFirstDay(weekendDays.value, firstDay.value))
|
||||||
const displayDefault = computed(() => reorderByFirstDay(props.fallback, firstDay.value))
|
const displayDefault = computed(() => reorderByFirstDay(props.fallback, firstDay.value))
|
||||||
|
|
||||||
|
// Expose a normalized pattern (Sunday-first) that substitutes the fallback day if none selected.
|
||||||
|
// This keeps UI visually showing fallback (muted) but downstream logic can opt-in by reading this.
|
||||||
|
function computeFallbackPattern() {
|
||||||
|
const fb = props.fallback && props.fallback.length === 7 ? props.fallback : null
|
||||||
|
if (fb && fb.some(Boolean)) return [...fb]
|
||||||
|
const arr = [false, false, false, false, false, false, false]
|
||||||
|
const idx = fb ? fb.findIndex(Boolean) : -1
|
||||||
|
if (idx >= 0) arr[idx] = true
|
||||||
|
else arr[0] = true
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
function emitExternal() {
|
||||||
|
model.value = internal.value.some(Boolean) ? [...internal.value] : computeFallbackPattern()
|
||||||
|
}
|
||||||
|
emitExternal()
|
||||||
|
watch(
|
||||||
|
() => model.value,
|
||||||
|
(nv) => {
|
||||||
|
if (!nv) return
|
||||||
|
if (!nv.some(Boolean)) return
|
||||||
|
const fb = computeFallbackPattern()
|
||||||
|
const isFallback = fb.every((v, i) => v === nv[i])
|
||||||
|
// If internal is empty and model only reflects fallback, do not sync into internal
|
||||||
|
if (isFallback && !internal.value.some(Boolean)) return
|
||||||
|
internal.value = [...nv]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// Mapping from display index to original model index
|
// Mapping from display index to original model index
|
||||||
const orderIndices = computed(() => Array.from({ length: 7 }, (_, i) => (i + firstDay.value) % 7))
|
const orderIndices = computed(() => Array.from({ length: 7 }, (_, i) => (i + firstDay.value) % 7))
|
||||||
|
|
||||||
@ -135,8 +165,8 @@ function isPressing(di) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onPointerDown(di) {
|
function onPointerDown(di) {
|
||||||
originalValues = [...model.value]
|
originalValues = [...internal.value]
|
||||||
dragVal.value = !model.value[(di + firstDay.value) % 7]
|
dragVal.value = !internal.value[(di + firstDay.value) % 7]
|
||||||
dragStart.value = di
|
dragStart.value = di
|
||||||
previewEnd.value = di
|
previewEnd.value = di
|
||||||
dragging.value = true
|
dragging.value = true
|
||||||
@ -155,7 +185,8 @@ function onPointerUp() {
|
|||||||
// simple click: toggle single
|
// simple click: toggle single
|
||||||
const next = [...originalValues]
|
const next = [...originalValues]
|
||||||
next[(dragStart.value + firstDay.value) % 7] = dragVal.value
|
next[(dragStart.value + firstDay.value) % 7] = dragVal.value
|
||||||
model.value = next
|
internal.value = next
|
||||||
|
emitExternal()
|
||||||
cleanupDrag()
|
cleanupDrag()
|
||||||
} else {
|
} else {
|
||||||
commitDrag()
|
commitDrag()
|
||||||
@ -169,7 +200,8 @@ function commitDrag() {
|
|||||||
: [previewEnd.value, dragStart.value]
|
: [previewEnd.value, dragStart.value]
|
||||||
const next = [...originalValues]
|
const next = [...originalValues]
|
||||||
for (let di = s; di <= e; di++) next[orderIndices.value[di]] = dragVal.value
|
for (let di = s; di <= e; di++) next[orderIndices.value[di]] = dragVal.value
|
||||||
model.value = next
|
internal.value = next
|
||||||
|
emitExternal()
|
||||||
cleanupDrag()
|
cleanupDrag()
|
||||||
}
|
}
|
||||||
function cancelDrag() {
|
function cancelDrag() {
|
||||||
@ -185,14 +217,15 @@ function cleanupDrag() {
|
|||||||
function toggleWeekend(work) {
|
function toggleWeekend(work) {
|
||||||
const base = weekendDays.value
|
const base = weekendDays.value
|
||||||
const target = work ? base : base.map((v) => !v)
|
const target = work ? base : base.map((v) => !v)
|
||||||
const current = model.value
|
const current = internal.value
|
||||||
const allOn = current.every(Boolean)
|
const allOn = current.every(Boolean)
|
||||||
const isTargetActive = current.every((v, i) => v === target[i])
|
const isTargetActive = current.every((v, i) => v === target[i])
|
||||||
if (allOn || isTargetActive) {
|
if (allOn || isTargetActive) {
|
||||||
model.value = [false, false, false, false, false, false, false]
|
internal.value = [false, false, false, false, false, false, false]
|
||||||
} else {
|
} else {
|
||||||
model.value = [...target]
|
internal.value = [...target]
|
||||||
}
|
}
|
||||||
|
emitExternal()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -65,14 +65,14 @@ export function createVirtualWeekManager({
|
|||||||
const repeatingBases = []
|
const repeatingBases = []
|
||||||
if (calendarStore.events) {
|
if (calendarStore.events) {
|
||||||
for (const ev of calendarStore.events.values()) {
|
for (const ev of calendarStore.events.values()) {
|
||||||
if (ev.isRepeating) repeatingBases.push(ev)
|
if (ev.recur) repeatingBases.push(ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const collectEventsForDate = (dateStr, curDateObj) => {
|
const collectEventsForDate = (dateStr, curDateObj) => {
|
||||||
const storedEvents = []
|
const storedEvents = []
|
||||||
for (const ev of calendarStore.events.values()) {
|
for (const ev of calendarStore.events.values()) {
|
||||||
if (!ev.isRepeating && dateStr >= ev.startDate && dateStr <= ev.endDate) {
|
if (!ev.recur && dateStr >= ev.startDate && dateStr <= ev.endDate) {
|
||||||
storedEvents.push(ev)
|
storedEvents.push(ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -289,7 +289,7 @@ export function createVirtualWeekManager({
|
|||||||
if (!visibleWeeks.value.length) return
|
if (!visibleWeeks.value.length) return
|
||||||
const repeatingBases = []
|
const repeatingBases = []
|
||||||
if (calendarStore.events) {
|
if (calendarStore.events) {
|
||||||
for (const ev of calendarStore.events.values()) if (ev.isRepeating) repeatingBases.push(ev)
|
for (const ev of calendarStore.events.values()) if (ev.recur) repeatingBases.push(ev)
|
||||||
}
|
}
|
||||||
const selStart = selection.value.startDate
|
const selStart = selection.value.startDate
|
||||||
const selCount = selection.value.dayCount
|
const selCount = selection.value.dayCount
|
||||||
@ -303,7 +303,7 @@ export function createVirtualWeekManager({
|
|||||||
// Rebuild events list for this day
|
// Rebuild events list for this day
|
||||||
const storedEvents = []
|
const storedEvents = []
|
||||||
for (const ev of calendarStore.events.values()) {
|
for (const ev of calendarStore.events.values()) {
|
||||||
if (!ev.isRepeating && dateStr >= ev.startDate && dateStr <= ev.endDate) {
|
if (!ev.recur && dateStr >= ev.startDate && dateStr <= ev.endDate) {
|
||||||
storedEvents.push(ev)
|
storedEvents.push(ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,11 +137,17 @@ 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: ['weeks', 'months'].includes(eventData.repeat) ? eventData.repeat : 'none',
|
recur:
|
||||||
repeatInterval: eventData.repeatInterval || 1,
|
eventData.recur && ['weeks', 'months'].includes(eventData.recur.freq)
|
||||||
repeatCount: eventData.repeatCount || 'unlimited',
|
? {
|
||||||
repeatWeekdays: eventData.repeatWeekdays,
|
freq: eventData.recur.freq,
|
||||||
isRepeating: eventData.repeat && eventData.repeat !== 'none',
|
interval: eventData.recur.interval || 1,
|
||||||
|
count: eventData.recur.count ?? 'unlimited',
|
||||||
|
weekdays: Array.isArray(eventData.recur.weekdays)
|
||||||
|
? [...eventData.recur.weekdays]
|
||||||
|
: null,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
}
|
}
|
||||||
this.events.set(event.id, { ...event, isSpanning: event.startDate < event.endDate })
|
this.events.set(event.id, { ...event, isSpanning: event.startDate < event.endDate })
|
||||||
this.notifyEventsChanged()
|
this.notifyEventsChanged()
|
||||||
@ -181,12 +187,12 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
deleteFirstOccurrence(baseId) {
|
deleteFirstOccurrence(baseId) {
|
||||||
const base = this.getEventById(baseId)
|
const base = this.getEventById(baseId)
|
||||||
if (!base) return
|
if (!base) return
|
||||||
if (!base.isRepeating) {
|
if (!base.recur) {
|
||||||
this.deleteEvent(baseId)
|
this.deleteEvent(baseId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const numericCount =
|
const numericCount =
|
||||||
base.repeatCount === 'unlimited' ? Infinity : parseInt(base.repeatCount, 10)
|
base.recur.count === 'unlimited' ? Infinity : parseInt(base.recur.count, 10)
|
||||||
if (numericCount <= 1) {
|
if (numericCount <= 1) {
|
||||||
this.deleteEvent(baseId)
|
this.deleteEvent(baseId)
|
||||||
return
|
return
|
||||||
@ -205,7 +211,7 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
)
|
)
|
||||||
base.startDate = nextStartStr
|
base.startDate = nextStartStr
|
||||||
base.endDate = newEndStr
|
base.endDate = newEndStr
|
||||||
if (numericCount !== Infinity) base.repeatCount = String(Math.max(1, numericCount - 1))
|
if (numericCount !== Infinity) base.recur.count = String(Math.max(1, numericCount - 1))
|
||||||
this.events.set(baseId, { ...base, isSpanning: base.startDate < base.endDate })
|
this.events.set(baseId, { ...base, isSpanning: base.startDate < base.endDate })
|
||||||
this.notifyEventsChanged()
|
this.notifyEventsChanged()
|
||||||
},
|
},
|
||||||
@ -215,7 +221,7 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
if (occurrenceIndex == null) return
|
if (occurrenceIndex == null) return
|
||||||
const base = this.getEventById(baseId)
|
const base = this.getEventById(baseId)
|
||||||
if (!base) return
|
if (!base) return
|
||||||
if (!base.isRepeating) {
|
if (!base.recur) {
|
||||||
if (occurrenceIndex === 0) this.deleteEvent(baseId)
|
if (occurrenceIndex === 0) this.deleteEvent(baseId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -224,7 +230,8 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const snapshot = { ...base }
|
const snapshot = { ...base }
|
||||||
base.repeatCount = occurrenceIndex
|
snapshot.recur = snapshot.recur ? { ...snapshot.recur } : null
|
||||||
|
base.recur.count = occurrenceIndex
|
||||||
const nextStartStr = getOccurrenceDate(snapshot, occurrenceIndex + 1, DEFAULT_TZ)
|
const nextStartStr = getOccurrenceDate(snapshot, occurrenceIndex + 1, DEFAULT_TZ)
|
||||||
if (!nextStartStr) return
|
if (!nextStartStr) return
|
||||||
const durationDays = Math.max(
|
const durationDays = Math.max(
|
||||||
@ -236,7 +243,7 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
)
|
)
|
||||||
const newEndStr = toLocalString(addDays(fromLocalString(nextStartStr), durationDays))
|
const newEndStr = toLocalString(addDays(fromLocalString(nextStartStr), durationDays))
|
||||||
const originalNumeric =
|
const originalNumeric =
|
||||||
snapshot.repeatCount === 'unlimited' ? Infinity : parseInt(snapshot.repeatCount, 10)
|
snapshot.recur.count === 'unlimited' ? Infinity : parseInt(snapshot.recur.count, 10)
|
||||||
let remainingCount = 'unlimited'
|
let remainingCount = 'unlimited'
|
||||||
if (originalNumeric !== Infinity) {
|
if (originalNumeric !== Infinity) {
|
||||||
const rem = originalNumeric - (occurrenceIndex + 1)
|
const rem = originalNumeric - (occurrenceIndex + 1)
|
||||||
@ -248,10 +255,14 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
startDate: nextStartStr,
|
startDate: nextStartStr,
|
||||||
endDate: newEndStr,
|
endDate: newEndStr,
|
||||||
colorId: snapshot.colorId,
|
colorId: snapshot.colorId,
|
||||||
repeat: snapshot.repeat,
|
recur: snapshot.recur
|
||||||
repeatInterval: snapshot.repeatInterval,
|
? {
|
||||||
repeatCount: remainingCount,
|
freq: snapshot.recur.freq,
|
||||||
repeatWeekdays: snapshot.repeatWeekdays,
|
interval: snapshot.recur.interval,
|
||||||
|
count: remainingCount,
|
||||||
|
weekdays: snapshot.recur.weekdays ? [...snapshot.recur.weekdays] : null,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
})
|
})
|
||||||
this.notifyEventsChanged()
|
this.notifyEventsChanged()
|
||||||
},
|
},
|
||||||
@ -259,7 +270,7 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
deleteFromOccurrence(ctx) {
|
deleteFromOccurrence(ctx) {
|
||||||
const { baseId, occurrenceIndex } = ctx
|
const { baseId, occurrenceIndex } = ctx
|
||||||
const base = this.getEventById(baseId)
|
const base = this.getEventById(baseId)
|
||||||
if (!base || !base.isRepeating) return
|
if (!base || !base.recur) return
|
||||||
if (occurrenceIndex === 0) {
|
if (occurrenceIndex === 0) {
|
||||||
this.deleteEvent(baseId)
|
this.deleteEvent(baseId)
|
||||||
return
|
return
|
||||||
@ -288,15 +299,15 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
if (
|
if (
|
||||||
rotatePattern &&
|
rotatePattern &&
|
||||||
(mode === 'move' || mode === 'resize-left') &&
|
(mode === 'move' || mode === 'resize-left') &&
|
||||||
snapshot.isRepeating &&
|
snapshot.recur &&
|
||||||
snapshot.repeat === 'weeks' &&
|
snapshot.recur.freq === 'weeks' &&
|
||||||
Array.isArray(snapshot.repeatWeekdays)
|
Array.isArray(snapshot.recur.weekdays)
|
||||||
) {
|
) {
|
||||||
const oldDow = prevStart.getDay()
|
const oldDow = prevStart.getDay()
|
||||||
const newDow = newStart.getDay()
|
const newDow = newStart.getDay()
|
||||||
const shift = newDow - oldDow
|
const shift = newDow - oldDow
|
||||||
if (shift !== 0) {
|
if (shift !== 0) {
|
||||||
snapshot.repeatWeekdays = this._rotateWeekdayPattern(snapshot.repeatWeekdays, shift)
|
snapshot.recur.weekdays = this._rotateWeekdayPattern(snapshot.recur.weekdays, shift)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.events.set(eventId, { ...snapshot, isSpanning: snapshot.startDate < snapshot.endDate })
|
this.events.set(eventId, { ...snapshot, isSpanning: snapshot.startDate < snapshot.endDate })
|
||||||
@ -305,8 +316,8 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
|
|
||||||
splitMoveVirtualOccurrence(baseId, occurrenceDateStr, newStartStr, newEndStr) {
|
splitMoveVirtualOccurrence(baseId, occurrenceDateStr, newStartStr, newEndStr) {
|
||||||
const base = this.events.get(baseId)
|
const base = this.events.get(baseId)
|
||||||
if (!base || !base.isRepeating) return
|
if (!base || !base.recur) return
|
||||||
const originalCountRaw = base.repeatCount
|
const originalCountRaw = base.recur.count
|
||||||
const occurrenceDate = fromLocalString(occurrenceDateStr, DEFAULT_TZ)
|
const occurrenceDate = fromLocalString(occurrenceDateStr, DEFAULT_TZ)
|
||||||
const baseStart = fromLocalString(base.startDate, DEFAULT_TZ)
|
const baseStart = fromLocalString(base.startDate, DEFAULT_TZ)
|
||||||
// If series effectively has <=1 occurrence, treat as simple move (no split) and flatten
|
// If series effectively has <=1 occurrence, treat as simple move (no split) and flatten
|
||||||
@ -317,9 +328,8 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
}
|
}
|
||||||
if (totalOccurrences <= 1) {
|
if (totalOccurrences <= 1) {
|
||||||
// Flatten to non-repeating if not already
|
// Flatten to non-repeating if not already
|
||||||
if (base.isRepeating) {
|
if (base.recur) {
|
||||||
base.repeat = 'none'
|
base.recur = null
|
||||||
base.isRepeating = false
|
|
||||||
this.events.set(baseId, { ...base })
|
this.events.set(baseId, { ...base })
|
||||||
}
|
}
|
||||||
this.setEventRange(baseId, newStartStr, newEndStr, { mode: 'move', rotatePattern: true })
|
this.setEventRange(baseId, newStartStr, newEndStr, { mode: 'move', rotatePattern: true })
|
||||||
@ -330,9 +340,9 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
return baseId
|
return baseId
|
||||||
}
|
}
|
||||||
let keptOccurrences = 0
|
let keptOccurrences = 0
|
||||||
if (base.repeat === 'weeks') {
|
if (base.recur.freq === 'weeks') {
|
||||||
const interval = base.repeatInterval || 1
|
const interval = base.recur.interval || 1
|
||||||
const pattern = base.repeatWeekdays || []
|
const pattern = base.recur.weekdays || []
|
||||||
if (!pattern.some(Boolean)) return
|
if (!pattern.some(Boolean)) return
|
||||||
const WEEK_MS = 7 * 86400000
|
const WEEK_MS = 7 * 86400000
|
||||||
const blockStartBase = getMondayOfISOWeek(baseStart)
|
const blockStartBase = getMondayOfISOWeek(baseStart)
|
||||||
@ -346,11 +356,11 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
if (pattern[cursor.getDay()] && isAligned(cursor)) keptOccurrences++
|
if (pattern[cursor.getDay()] && isAligned(cursor)) keptOccurrences++
|
||||||
cursor = addDays(cursor, 1)
|
cursor = addDays(cursor, 1)
|
||||||
}
|
}
|
||||||
} else if (base.repeat === 'months') {
|
} else if (base.recur.freq === 'months') {
|
||||||
const diffMonths =
|
const diffMonths =
|
||||||
(occurrenceDate.getFullYear() - baseStart.getFullYear()) * 12 +
|
(occurrenceDate.getFullYear() - baseStart.getFullYear()) * 12 +
|
||||||
(occurrenceDate.getMonth() - baseStart.getMonth())
|
(occurrenceDate.getMonth() - baseStart.getMonth())
|
||||||
const interval = base.repeatInterval || 1
|
const interval = base.recur.interval || 1
|
||||||
if (diffMonths <= 0 || diffMonths % interval !== 0) return
|
if (diffMonths <= 0 || diffMonths % interval !== 0) return
|
||||||
keptOccurrences = diffMonths
|
keptOccurrences = diffMonths
|
||||||
} else {
|
} else {
|
||||||
@ -359,7 +369,12 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
this._terminateRepeatSeriesAtIndex(baseId, keptOccurrences)
|
this._terminateRepeatSeriesAtIndex(baseId, keptOccurrences)
|
||||||
// After truncation compute base kept count
|
// After truncation compute base kept count
|
||||||
const truncated = this.events.get(baseId)
|
const truncated = this.events.get(baseId)
|
||||||
if (truncated && truncated.repeatCount && truncated.repeatCount !== 'unlimited') {
|
if (
|
||||||
|
truncated &&
|
||||||
|
truncated.recur &&
|
||||||
|
truncated.recur.count &&
|
||||||
|
truncated.recur.count !== 'unlimited'
|
||||||
|
) {
|
||||||
// keptOccurrences already reflects number before split; adjust not needed further
|
// keptOccurrences already reflects number before split; adjust not needed further
|
||||||
}
|
}
|
||||||
let remainingCount = 'unlimited'
|
let remainingCount = 'unlimited'
|
||||||
@ -371,13 +386,13 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
remainingCount = String(rem)
|
remainingCount = String(rem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let repeatWeekdays = base.repeatWeekdays
|
let weekdays = base.recur.weekdays
|
||||||
if (base.repeat === 'weeks' && Array.isArray(base.repeatWeekdays)) {
|
if (base.recur.freq === 'weeks' && Array.isArray(base.recur.weekdays)) {
|
||||||
const origWeekday = occurrenceDate.getDay()
|
const origWeekday = occurrenceDate.getDay()
|
||||||
const newWeekday = fromLocalString(newStartStr, DEFAULT_TZ).getDay()
|
const newWeekday = fromLocalString(newStartStr, DEFAULT_TZ).getDay()
|
||||||
const shift = newWeekday - origWeekday
|
const shift = newWeekday - origWeekday
|
||||||
if (shift !== 0) {
|
if (shift !== 0) {
|
||||||
repeatWeekdays = this._rotateWeekdayPattern(base.repeatWeekdays, shift)
|
weekdays = this._rotateWeekdayPattern(base.recur.weekdays, shift)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const newId = this.createEvent({
|
const newId = this.createEvent({
|
||||||
@ -385,29 +400,29 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
startDate: newStartStr,
|
startDate: newStartStr,
|
||||||
endDate: newEndStr,
|
endDate: newEndStr,
|
||||||
colorId: base.colorId,
|
colorId: base.colorId,
|
||||||
repeat: base.repeat,
|
recur: {
|
||||||
repeatInterval: base.repeatInterval,
|
freq: base.recur.freq,
|
||||||
repeatCount: remainingCount,
|
interval: base.recur.interval,
|
||||||
repeatWeekdays,
|
count: remainingCount,
|
||||||
|
weekdays,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
// Flatten base if single occurrence now
|
// Flatten base if single occurrence now
|
||||||
if (truncated && truncated.isRepeating) {
|
if (truncated && truncated.recur) {
|
||||||
const baseCountNum =
|
const baseCountNum =
|
||||||
truncated.repeatCount === 'unlimited' ? Infinity : parseInt(truncated.repeatCount, 10)
|
truncated.recur.count === 'unlimited' ? Infinity : parseInt(truncated.recur.count, 10)
|
||||||
if (baseCountNum <= 1) {
|
if (baseCountNum <= 1) {
|
||||||
truncated.repeat = 'none'
|
truncated.recur = null
|
||||||
truncated.isRepeating = false
|
|
||||||
this.events.set(baseId, { ...truncated })
|
this.events.set(baseId, { ...truncated })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Flatten new if single occurrence only
|
// Flatten new if single occurrence only
|
||||||
const newly = this.events.get(newId)
|
const newly = this.events.get(newId)
|
||||||
if (newly && newly.isRepeating) {
|
if (newly && newly.recur) {
|
||||||
const newCountNum =
|
const newCountNum =
|
||||||
newly.repeatCount === 'unlimited' ? Infinity : parseInt(newly.repeatCount, 10)
|
newly.recur.count === 'unlimited' ? Infinity : parseInt(newly.recur.count, 10)
|
||||||
if (newCountNum <= 1) {
|
if (newCountNum <= 1) {
|
||||||
newly.repeat = 'none'
|
newly.recur = null
|
||||||
newly.isRepeating = false
|
|
||||||
this.events.set(newId, { ...newly })
|
this.events.set(newId, { ...newly })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -417,8 +432,8 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
|
|
||||||
splitRepeatSeries(baseId, occurrenceIndex, newStartStr, newEndStr) {
|
splitRepeatSeries(baseId, occurrenceIndex, newStartStr, newEndStr) {
|
||||||
const base = this.events.get(baseId)
|
const base = this.events.get(baseId)
|
||||||
if (!base || !base.isRepeating) return null
|
if (!base || !base.recur) return null
|
||||||
const originalCountRaw = base.repeatCount
|
const originalCountRaw = base.recur.count
|
||||||
this._terminateRepeatSeriesAtIndex(baseId, occurrenceIndex)
|
this._terminateRepeatSeriesAtIndex(baseId, occurrenceIndex)
|
||||||
let newSeriesCount = 'unlimited'
|
let newSeriesCount = 'unlimited'
|
||||||
if (originalCountRaw !== 'unlimited') {
|
if (originalCountRaw !== 'unlimited') {
|
||||||
@ -433,21 +448,25 @@ export const useCalendarStore = defineStore('calendar', {
|
|||||||
startDate: newStartStr,
|
startDate: newStartStr,
|
||||||
endDate: newEndStr,
|
endDate: newEndStr,
|
||||||
colorId: base.colorId,
|
colorId: base.colorId,
|
||||||
repeat: base.repeat,
|
recur: base.recur
|
||||||
repeatInterval: base.repeatInterval,
|
? {
|
||||||
repeatCount: newSeriesCount,
|
freq: base.recur.freq,
|
||||||
repeatWeekdays: base.repeatWeekdays,
|
interval: base.recur.interval,
|
||||||
|
count: newSeriesCount,
|
||||||
|
weekdays: base.recur.weekdays ? [...base.recur.weekdays] : null,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
_terminateRepeatSeriesAtIndex(baseId, index) {
|
_terminateRepeatSeriesAtIndex(baseId, index) {
|
||||||
const ev = this.events.get(baseId)
|
const ev = this.events.get(baseId)
|
||||||
if (!ev || !ev.isRepeating) return
|
if (!ev || !ev.recur) return
|
||||||
if (ev.repeatCount === 'unlimited') {
|
if (ev.recur.count === 'unlimited') {
|
||||||
ev.repeatCount = String(index)
|
ev.recur.count = String(index)
|
||||||
} else {
|
} else {
|
||||||
const rc = parseInt(ev.repeatCount, 10)
|
const rc = parseInt(ev.recur.count, 10)
|
||||||
if (!isNaN(rc)) ev.repeatCount = String(Math.min(rc, index))
|
if (!isNaN(rc)) ev.recur.count = String(Math.min(rc, index))
|
||||||
}
|
}
|
||||||
this.notifyEventsChanged()
|
this.notifyEventsChanged()
|
||||||
},
|
},
|
||||||
|
@ -81,9 +81,14 @@ function countPatternDaysInInterval(startDate, endDate, patternArr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recurrence: Weekly ------------------------------------------------------
|
// Recurrence: Weekly ------------------------------------------------------
|
||||||
|
function _getRecur(event) {
|
||||||
|
return event && event.recur ? event.recur : null
|
||||||
|
}
|
||||||
|
|
||||||
function getWeeklyOccurrenceIndex(event, dateStr, timeZone = DEFAULT_TZ) {
|
function getWeeklyOccurrenceIndex(event, dateStr, timeZone = DEFAULT_TZ) {
|
||||||
if (!event?.isRepeating || event.repeat !== 'weeks') return null
|
const recur = _getRecur(event)
|
||||||
const pattern = event.repeatWeekdays || []
|
if (!recur || recur.freq !== 'weeks') return null
|
||||||
|
const pattern = recur.weekdays || []
|
||||||
if (!pattern.some(Boolean)) return null
|
if (!pattern.some(Boolean)) return null
|
||||||
|
|
||||||
const target = fromLocalString(dateStr, timeZone)
|
const target = fromLocalString(dateStr, timeZone)
|
||||||
@ -93,7 +98,7 @@ function getWeeklyOccurrenceIndex(event, dateStr, timeZone = DEFAULT_TZ) {
|
|||||||
const dow = dateFns.getDay(target)
|
const dow = dateFns.getDay(target)
|
||||||
if (!pattern[dow]) return null // target not active
|
if (!pattern[dow]) return null // target not active
|
||||||
|
|
||||||
const interval = event.repeatInterval || 1
|
const interval = recur.interval || 1
|
||||||
const baseBlockStart = getMondayOfISOWeek(baseStart, timeZone)
|
const baseBlockStart = getMondayOfISOWeek(baseStart, timeZone)
|
||||||
const currentBlockStart = getMondayOfISOWeek(target, timeZone)
|
const currentBlockStart = getMondayOfISOWeek(target, timeZone)
|
||||||
// Number of weeks between block starts (each block start is a Monday)
|
// Number of weeks between block starts (each block start is a Monday)
|
||||||
@ -106,8 +111,9 @@ function getWeeklyOccurrenceIndex(event, dateStr, timeZone = DEFAULT_TZ) {
|
|||||||
// Same ISO week as base: count pattern days from baseStart up to target (inclusive)
|
// Same ISO week as base: count pattern days from baseStart up to target (inclusive)
|
||||||
if (weekDiff === 0) {
|
if (weekDiff === 0) {
|
||||||
let n = countPatternDaysInInterval(baseStart, target, pattern) - 1
|
let n = countPatternDaysInInterval(baseStart, target, pattern) - 1
|
||||||
if (!baseCountsAsPattern) n += 1 // Shift indices so base occurrence stays 0
|
if (!baseCountsAsPattern) n += 1
|
||||||
return n < 0 || n >= event.repeatCount ? null : n
|
const maxCount = recur.count === 'unlimited' ? Infinity : parseInt(recur.count, 10)
|
||||||
|
return n < 0 || n >= maxCount ? null : n
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseWeekEnd = dateFns.addDays(baseBlockStart, 6)
|
const baseWeekEnd = dateFns.addDays(baseBlockStart, 6)
|
||||||
@ -120,42 +126,48 @@ function getWeeklyOccurrenceIndex(event, dateStr, timeZone = DEFAULT_TZ) {
|
|||||||
const currentWeekCount = countPatternDaysInInterval(currentBlockStart, target, pattern)
|
const currentWeekCount = countPatternDaysInInterval(currentBlockStart, target, pattern)
|
||||||
let n = firstWeekCount + middleWeeksCount + currentWeekCount - 1
|
let n = firstWeekCount + middleWeeksCount + currentWeekCount - 1
|
||||||
if (!baseCountsAsPattern) n += 1
|
if (!baseCountsAsPattern) n += 1
|
||||||
return n >= event.repeatCount ? null : n
|
const maxCount = recur.count === 'unlimited' ? Infinity : parseInt(recur.count, 10)
|
||||||
|
return n >= maxCount ? null : n
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurrence: Monthly -----------------------------------------------------
|
// Recurrence: Monthly -----------------------------------------------------
|
||||||
function getMonthlyOccurrenceIndex(event, dateStr, timeZone = DEFAULT_TZ) {
|
function getMonthlyOccurrenceIndex(event, dateStr, timeZone = DEFAULT_TZ) {
|
||||||
if (!event?.isRepeating || event.repeat !== 'months') return null
|
const recur = _getRecur(event)
|
||||||
|
if (!recur || recur.freq !== 'months') return null
|
||||||
const baseStart = fromLocalString(event.startDate, timeZone)
|
const baseStart = fromLocalString(event.startDate, timeZone)
|
||||||
const d = fromLocalString(dateStr, timeZone)
|
const d = fromLocalString(dateStr, timeZone)
|
||||||
const diffMonths = dateFns.differenceInCalendarMonths(d, baseStart)
|
const diffMonths = dateFns.differenceInCalendarMonths(d, baseStart)
|
||||||
if (diffMonths < 0) return null
|
if (diffMonths < 0) return null
|
||||||
const interval = event.repeatInterval || 1
|
const interval = recur.interval || 1
|
||||||
if (diffMonths % interval !== 0) return null
|
if (diffMonths % interval !== 0) return null
|
||||||
const baseDay = dateFns.getDate(baseStart)
|
const baseDay = dateFns.getDate(baseStart)
|
||||||
const effectiveDay = Math.min(baseDay, dateFns.getDaysInMonth(d))
|
const effectiveDay = Math.min(baseDay, dateFns.getDaysInMonth(d))
|
||||||
if (dateFns.getDate(d) !== effectiveDay) return null
|
if (dateFns.getDate(d) !== effectiveDay) return null
|
||||||
const n = diffMonths / interval
|
const n = diffMonths / interval
|
||||||
return n >= event.repeatCount ? null : n
|
const maxCount = recur.count === 'unlimited' ? Infinity : parseInt(recur.count, 10)
|
||||||
|
return n >= maxCount ? null : n
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOccurrenceIndex(event, dateStr, timeZone = DEFAULT_TZ) {
|
function getOccurrenceIndex(event, dateStr, timeZone = DEFAULT_TZ) {
|
||||||
if (!event?.isRepeating || event.repeat === 'none') return null
|
const recur = _getRecur(event)
|
||||||
|
if (!recur) return null
|
||||||
if (dateStr < event.startDate) return null
|
if (dateStr < event.startDate) return null
|
||||||
if (event.repeat === 'weeks') return getWeeklyOccurrenceIndex(event, dateStr, timeZone)
|
if (recur.freq === 'weeks') return getWeeklyOccurrenceIndex(event, dateStr, timeZone)
|
||||||
if (event.repeat === 'months') return getMonthlyOccurrenceIndex(event, dateStr, timeZone)
|
if (recur.freq === 'months') return getMonthlyOccurrenceIndex(event, dateStr, timeZone)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reverse lookup: given a recurrence index (0-based) return the occurrence start date string.
|
// Reverse lookup: given a recurrence index (0-based) return the occurrence start date string.
|
||||||
// Returns null if the index is out of range or the event is not repeating.
|
// Returns null if the index is out of range or the event is not repeating.
|
||||||
function getWeeklyOccurrenceDate(event, occurrenceIndex, timeZone = DEFAULT_TZ) {
|
function getWeeklyOccurrenceDate(event, occurrenceIndex, timeZone = DEFAULT_TZ) {
|
||||||
if (!event?.isRepeating || event.repeat !== 'weeks') return null
|
const recur = _getRecur(event)
|
||||||
|
if (!recur || recur.freq !== 'weeks') return null
|
||||||
if (occurrenceIndex < 0 || !Number.isInteger(occurrenceIndex)) return null
|
if (occurrenceIndex < 0 || !Number.isInteger(occurrenceIndex)) return null
|
||||||
if (event.repeatCount !== 'unlimited' && occurrenceIndex >= event.repeatCount) return null
|
const maxCount = recur.count === 'unlimited' ? Infinity : parseInt(recur.count, 10)
|
||||||
const pattern = event.repeatWeekdays || []
|
if (occurrenceIndex >= maxCount) return null
|
||||||
|
const pattern = recur.weekdays || []
|
||||||
if (!pattern.some(Boolean)) return null
|
if (!pattern.some(Boolean)) return null
|
||||||
const interval = event.repeatInterval || 1
|
const interval = recur.interval || 1
|
||||||
const baseStart = fromLocalString(event.startDate, timeZone)
|
const baseStart = fromLocalString(event.startDate, timeZone)
|
||||||
if (occurrenceIndex === 0) return toLocalString(baseStart, timeZone)
|
if (occurrenceIndex === 0) return toLocalString(baseStart, timeZone)
|
||||||
const baseWeekMonday = getMondayOfISOWeek(baseStart, timeZone)
|
const baseWeekMonday = getMondayOfISOWeek(baseStart, timeZone)
|
||||||
@ -192,10 +204,12 @@ function getWeeklyOccurrenceDate(event, occurrenceIndex, timeZone = DEFAULT_TZ)
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getMonthlyOccurrenceDate(event, occurrenceIndex, timeZone = DEFAULT_TZ) {
|
function getMonthlyOccurrenceDate(event, occurrenceIndex, timeZone = DEFAULT_TZ) {
|
||||||
if (!event?.isRepeating || event.repeat !== 'months') return null
|
const recur = _getRecur(event)
|
||||||
|
if (!recur || recur.freq !== 'months') return null
|
||||||
if (occurrenceIndex < 0 || !Number.isInteger(occurrenceIndex)) return null
|
if (occurrenceIndex < 0 || !Number.isInteger(occurrenceIndex)) return null
|
||||||
if (event.repeatCount !== 'unlimited' && occurrenceIndex >= event.repeatCount) return null
|
const maxCount = recur.count === 'unlimited' ? Infinity : parseInt(recur.count, 10)
|
||||||
const interval = event.repeatInterval || 1
|
if (occurrenceIndex >= maxCount) return null
|
||||||
|
const interval = recur.interval || 1
|
||||||
const baseStart = fromLocalString(event.startDate, timeZone)
|
const baseStart = fromLocalString(event.startDate, timeZone)
|
||||||
const targetMonthOffset = occurrenceIndex * interval
|
const targetMonthOffset = occurrenceIndex * interval
|
||||||
const monthDate = dateFns.addMonths(baseStart, targetMonthOffset)
|
const monthDate = dateFns.addMonths(baseStart, targetMonthOffset)
|
||||||
@ -208,9 +222,10 @@ function getMonthlyOccurrenceDate(event, occurrenceIndex, timeZone = DEFAULT_TZ)
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getOccurrenceDate(event, occurrenceIndex, timeZone = DEFAULT_TZ) {
|
function getOccurrenceDate(event, occurrenceIndex, timeZone = DEFAULT_TZ) {
|
||||||
if (!event?.isRepeating || event.repeat === 'none') return null
|
const recur = _getRecur(event)
|
||||||
if (event.repeat === 'weeks') return getWeeklyOccurrenceDate(event, occurrenceIndex, timeZone)
|
if (!recur) return null
|
||||||
if (event.repeat === 'months') return getMonthlyOccurrenceDate(event, occurrenceIndex, timeZone)
|
if (recur.freq === 'weeks') return getWeeklyOccurrenceDate(event, occurrenceIndex, timeZone)
|
||||||
|
if (recur.freq === 'months') return getMonthlyOccurrenceDate(event, occurrenceIndex, timeZone)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user