fix: synchronize event updates and expose updateEvent method in CalendarEventPanel

This commit is contained in:
Shariq Ansari 2025-08-28 19:31:41 +05:30
parent 4b4b188827
commit c69a468e35
2 changed files with 147 additions and 167 deletions

View File

@ -635,6 +635,7 @@ function close() {
function reset() { function reset() {
Object.assign(_event.value, oldEvent.value) Object.assign(_event.value, oldEvent.value)
sync()
} }
function showDiscardChangesModal(action) { function showDiscardChangesModal(action) {
@ -745,4 +746,10 @@ const toOptions = computed(() => {
} }
}) })
}) })
function updateEvent(_e) {
Object.assign(_event.value, _e)
}
defineExpose({ updateEvent })
</script> </script>

View File

@ -7,9 +7,7 @@
<Button <Button
variant="solid" variant="solid"
:label="__('Create')" :label="__('Create')"
:disabled=" :disabled="isCreateDisabled"
mode == 'edit' || mode == 'new-event' || mode == 'duplicate-event'
"
@click="newEvent" @click="newEvent"
> >
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
@ -31,7 +29,7 @@
}" }"
:events="events.data" :events="events.data"
@create="(event) => createEvent(event)" @create="(event) => createEvent(event)"
@update="(event) => updateEvent(event)" @update="(event) => updateEvent(event, true)"
@delete="(eventID) => deleteEvent(eventID)" @delete="(eventID) => deleteEvent(eventID)"
:onClick="showDetails" :onClick="showDetails"
:onDblClick="editDetails" :onDblClick="editDetails"
@ -100,6 +98,7 @@
" "
> >
<CalendarEventPanel <CalendarEventPanel
ref="eventPanel"
v-if="showEventPanel" v-if="showEventPanel"
v-model="showEventPanel" v-model="showEventPanel"
v-model:event="event" v-model:event="event"
@ -130,7 +129,7 @@ import {
CalendarActiveEvent as activeEvent, CalendarActiveEvent as activeEvent,
call, call,
} from 'frappe-ui' } from 'frappe-ui'
import { onMounted, ref } from 'vue' import { onMounted, ref, computed } from 'vue'
const { user } = sessionStore() const { user } = sessionStore()
const { $dialog } = globalStore() const { $dialog } = globalStore()
@ -156,82 +155,105 @@ const events = createListResource({
filters: { status: 'Open', owner: user }, filters: { status: 'Open', owner: user },
pageLength: 9999, pageLength: 9999,
auto: true, auto: true,
transform: (data) => { transform: (data) =>
return data.map((event) => { data.map((ev) => ({
let fromDate = dayjs(event.starts_on).format('YYYY-MM-DD') id: ev.name,
let toDate = dayjs(event.ends_on).format('YYYY-MM-DD') title: ev.subject,
let fromTime = dayjs(event.starts_on).format('HH:mm') description: ev.description,
let toTime = dayjs(event.ends_on).format('HH:mm') status: ev.status,
fromDate: dayjs(ev.starts_on).format('YYYY-MM-DD'),
return { toDate: dayjs(ev.ends_on).format('YYYY-MM-DD'),
id: event.name, fromTime: dayjs(ev.starts_on).format('HH:mm'),
title: event.subject, toTime: dayjs(ev.ends_on).format('HH:mm'),
description: event.description, isFullDay: ev.all_day,
status: event.status, eventType: ev.event_type,
fromDate, color: ev.color,
toDate, referenceDoctype: ev.reference_doctype,
fromTime, referenceDocname: ev.reference_docname,
toTime, })),
isFullDay: event.all_day,
eventType: event.event_type,
color: event.color,
referenceDoctype: event.reference_doctype,
referenceDocname: event.reference_docname,
}
})
},
}) })
const eventPanel = ref(null)
const showEventPanel = ref(false)
const event = ref({})
const mode = ref('')
const isCreateDisabled = computed(() =>
['edit', 'new-event', 'duplicate-event'].includes(mode.value),
)
// Temp event helpers
const TEMP_EVENT_IDS = new Set(['new-event', 'duplicate-event'])
const isTempEvent = (id) => TEMP_EVENT_IDS.has(id)
function removeTempEvents() {
if (!Array.isArray(events.data)) return
events.data = events.data.filter((ev) => !isTempEvent(ev.id))
}
function openEvent(e, nextMode) {
const _e = e?.calendarEvent || e
if (!_e?.id || isTempEvent(_e.id)) return
removeTempEvents()
showEventPanel.value = true
event.value = { id: _e.id }
activeEvent.value = _e.id
mode.value = nextMode
}
function saveEvent(_event) { function saveEvent(_event) {
if ( if (!_event?.id || isTempEvent(_event.id)) return createEvent(_event)
!_event.id || updateEvent(_event)
_event.id === 'new-event' || }
_event.id === 'duplicate-event'
) { function buildEventPayload(_event) {
createEvent(_event) return {
} else { subject: _event.title,
updateEvent(_event) description: _event.description,
starts_on: `${_event.fromDate} ${_event.fromTime}`,
ends_on: `${_event.toDate} ${_event.toTime}`,
all_day: _event.isFullDay || false,
event_type: _event.eventType,
color: _event.color,
reference_doctype: _event.referenceDoctype,
reference_docname: _event.referenceDocname,
event_participants: _event.event_participants,
} }
} }
function createEvent(_event) { function createEvent(_event) {
if (!_event.title) return if (!_event?.title) return
events.insert.submit(buildEventPayload(_event), {
events.insert.submit( onSuccess: async (e) => {
{ await events.reload()
subject: _event.title, showDetails({ id: e.name })
description: _event.description,
starts_on: _event.fromDate + ' ' + _event.fromTime,
ends_on: _event.toDate + ' ' + _event.toTime,
all_day: _event.isFullDay || false,
event_type: _event.eventType,
color: _event.color,
reference_doctype: _event.referenceDoctype,
reference_docname: _event.referenceDocname,
event_participants: _event.event_participants,
}, },
{ })
onSuccess: async (e) => {
await events.reload()
showDetails({ id: e.name })
},
},
)
} }
async function updateEvent(_event) { async function updateEvent(_event, afterDrag = false) {
if (!_event.id) return if (!_event.id) return
if ( if (
['duplicate', 'new'].includes(mode.value) && ['duplicate', 'new'].includes(mode.value) &&
!['duplicate-event', 'new-event'].includes(_event.id) !['duplicate-event', 'new-event'].includes(_event.id) &&
afterDrag
) { ) {
event.value = { id: _event.id } event.value = { id: _event.id }
activeEvent.value = _event.id activeEvent.value = _event.id
mode.value = 'details' mode.value = 'details'
} }
if (!mode.value || mode.value === 'edit' || mode.value === 'details') { if (mode.value == 'edit' && afterDrag) {
eventPanel.value.updateEvent({
fromDate: _event.fromDate,
toDate: _event.toDate,
fromTime: _event.fromTime,
toTime: _event.toTime,
})
return
}
if (!mode.value || mode.value == 'edit' || mode.value === 'details') {
// Ensure Contacts exist for participants referencing a new/unknown Contact, if not create them // Ensure Contacts exist for participants referencing a new/unknown Contact, if not create them
if ( if (
Array.isArray(_event.event_participants) && Array.isArray(_event.event_participants) &&
@ -243,19 +265,7 @@ async function updateEvent(_event) {
} }
events.setValue.submit( events.setValue.submit(
{ { name: _event.id, ...buildEventPayload(_event) },
name: _event.id,
subject: _event.title,
description: _event.description,
starts_on: _event.fromDate + ' ' + _event.fromTime,
ends_on: _event.toDate + ' ' + _event.toTime,
all_day: _event.isFullDay,
event_type: _event.eventType,
color: _event.color,
reference_doctype: _event.referenceDoctype,
reference_docname: _event.referenceDocname,
event_participants: _event.event_participants,
},
{ {
onSuccess: async (e) => { onSuccess: async (e) => {
await events.reload() await events.reload()
@ -305,67 +315,25 @@ onMounted(() => {
showEventPanel.value = false showEventPanel.value = false
}) })
const showEventPanel = ref(false)
const event = ref({})
const mode = ref('')
function showDetails(e) { function showDetails(e) {
let _e = e?.calendarEvent || e openEvent(e, 'details')
if (_e.id === 'new-event' || _e.id === 'duplicate-event') return
events.data = events.data.filter(
(ev) => ev.id !== 'new-event' && ev.id !== 'duplicate-event',
)
showEventPanel.value = true
event.value = { id: _e.id }
activeEvent.value = _e.id
mode.value = 'details'
} }
function editDetails(e) { function editDetails(e) {
let _e = e?.calendarEvent || e openEvent(e, 'edit')
if (_e.id === 'new-event' || _e.id === 'duplicate-event') return
events.data = events.data.filter(
(ev) => ev.id !== 'new-event' && ev.id !== 'duplicate-event',
)
showEventPanel.value = true
event.value = { id: _e.id }
activeEvent.value = _e.id
mode.value = 'edit'
} }
function newEvent(e, duplicate = false) { function buildTempEvent(e, duplicate) {
events.data = events.data.filter( const id = duplicate ? 'duplicate-event' : 'new-event'
(ev) => ev.id !== 'new-event' && ev.id !== 'duplicate-event', return {
) id,
let fromTime = e.fromTime
let toTime = e.toTime
let fromDate = e.fromDate
let toDate = e.toDate
let isFullDay = e.isFullDay
if (!duplicate) {
let t = getFromToTime(e.time)
fromTime = t[0]
toTime = t[1]
fromDate = dayjs(e.date).format('YYYY-MM-DD')
toDate = fromDate
e = { fromDate, toDate, fromTime, toTime, isFullDay }
}
event.value = {
id: duplicate ? 'duplicate-event' : 'new-event',
title: duplicate ? `${e.title} (Copy)` : '', title: duplicate ? `${e.title} (Copy)` : '',
description: e.description || '', description: e.description || '',
date: fromDate, date: e.fromDate,
fromDate, fromDate: e.fromDate,
toDate, toDate: e.toDate,
fromTime, fromTime: e.fromTime,
toTime, toTime: e.toTime,
isFullDay: e.isFullDay || false, isFullDay: e.isFullDay || false,
eventType: e.eventType || 'Public', eventType: e.eventType || 'Public',
color: e.color || 'green', color: e.color || 'green',
@ -373,11 +341,29 @@ function newEvent(e, duplicate = false) {
referenceDocname: e.referenceDocname, referenceDocname: e.referenceDocname,
event_participants: e.event_participants || [], event_participants: e.event_participants || [],
} }
}
function newEvent(e = {}, duplicate = false) {
removeTempEvents()
let base = { ...e }
if (!duplicate) {
const [fromTime, toTime] = getFromToTime(e.time)
const fromDate = dayjs(e.date).format('YYYY-MM-DD')
base = {
...base,
fromDate,
toDate: fromDate,
fromTime,
toTime,
isFullDay: e.isFullDay,
}
}
event.value = buildTempEvent(base, duplicate)
events.data.push(event.value) events.data.push(event.value)
showEventPanel.value = true showEventPanel.value = true
activeEvent.value = duplicate ? 'duplicate-event' : 'new-event' activeEvent.value = event.value.id
mode.value = duplicate ? 'duplicate' : 'new' mode.value = duplicate ? 'duplicate' : 'new'
} }
@ -391,50 +377,37 @@ function close() {
activeEvent.value = '' activeEvent.value = ''
mode.value = '' mode.value = ''
events.data = events.data.filter( removeTempEvents()
(ev) => ev.id !== 'new-event' && ev.id !== 'duplicate-event',
)
} }
// utils // utils
function getFromToTime(time) { function getFromToTime(time) {
let currentTime = dayjs().format('HH:mm') || '00:00' const pad = (v) => String(v).padStart(2, '0')
let h = currentTime.split(':')[0] let now = dayjs()
let m = parseInt(currentTime.split(':')[1]) let h = now.hour()
let m = Math.floor(now.minute() / 15) * 15
m = Math.floor(m / 15) * 15 let fromHour = h
m = m < 10 ? '0' + m : String(m) let fromMinute = m
if (time) {
let fromTime = `${h}:${m}` if (/am|pm/i.test(time)) {
let toTime = `${parseInt(h) + 1}:${m}` const raw = time.trim().replace(' ', '')
const ampm = raw.slice(-2).toLowerCase()
if ( let hour = parseInt(raw.slice(0, -2))
time?.toLowerCase().includes('am') || if (ampm === 'pm' && hour < 12) hour += 12
time?.toLowerCase().includes('pm') if (ampm === 'am' && hour === 12) hour = 0
) { fromHour = hour
// 12 hour format fromMinute = 0
time = time.trim().replace(' ', '') } else if (/^\d{1,2}:?\d{0,2}$/.test(time)) {
const ampm = time.slice(-2) const [hh, mm = '00'] = time.split(':')
time = time.slice(0, -2) fromHour = parseInt(hh)
let hour = time fromMinute = parseInt(mm) || 0
if (ampm === 'pm' && parseInt(hour) < 12) {
hour = parseInt(hour) + 12
} else if (ampm === 'am' && hour == 12) {
hour = 0
} }
fromTime = `${hour}:00`
toTime = `${parseInt(hour) + 1}:00`
} else {
// 24 hour format
let [hour, minute] = time ? time.split(':') : [h, m]
fromTime = `${hour}:${minute || '00'}`
toTime = `${parseInt(hour) + 1}:${minute || '00'}`
} }
const toHour = (fromHour + 1) % 24
return [fromTime, toTime] return [
`${pad(fromHour)}:${pad(fromMinute)}`,
`${pad(toHour)}:${pad(fromMinute)}`,
]
} }
async function ensureParticipantContacts(participants) { async function ensureParticipantContacts(participants) {