refactor: remove CalendarEventDetails component and update CalendarEventPanel for improved event handling

This commit is contained in:
Shariq Ansari 2025-08-07 15:59:37 +05:30
parent 682e445288
commit 10e3adfd18
4 changed files with 152 additions and 192 deletions

View File

@ -46,7 +46,6 @@ declare module 'vue' {
BrandLogo: typeof import('./src/components/BrandLogo.vue')['default'] BrandLogo: typeof import('./src/components/BrandLogo.vue')['default']
BrandSettings: typeof import('./src/components/Settings/General/BrandSettings.vue')['default'] BrandSettings: typeof import('./src/components/Settings/General/BrandSettings.vue')['default']
BulkDeleteLinkedDocModal: typeof import('./src/components/BulkDeleteLinkedDocModal.vue')['default'] BulkDeleteLinkedDocModal: typeof import('./src/components/BulkDeleteLinkedDocModal.vue')['default']
CalendarEventDetails: typeof import('./src/components/Calendar/CalendarEventDetails.vue')['default']
CalendarEventPanel: typeof import('./src/components/Calendar/CalendarEventPanel.vue')['default'] CalendarEventPanel: typeof import('./src/components/Calendar/CalendarEventPanel.vue')['default']
CalendarIcon: typeof import('./src/components/Icons/CalendarIcon.vue')['default'] CalendarIcon: typeof import('./src/components/Icons/CalendarIcon.vue')['default']
CalendarModal: typeof import('./src/components/Modals/CalendarModal.vue')['default'] CalendarModal: typeof import('./src/components/Modals/CalendarModal.vue')['default']
@ -191,6 +190,7 @@ declare module 'vue' {
ListRows: typeof import('./src/components/ListViews/ListRows.vue')['default'] ListRows: typeof import('./src/components/ListViews/ListRows.vue')['default']
LoadingIndicator: typeof import('./src/components/Icons/LoadingIndicator.vue')['default'] LoadingIndicator: typeof import('./src/components/Icons/LoadingIndicator.vue')['default']
LostReasonModal: typeof import('./src/components/Modals/LostReasonModal.vue')['default'] LostReasonModal: typeof import('./src/components/Modals/LostReasonModal.vue')['default']
LucideCalendar: typeof import('~icons/lucide/calendar')['default']
LucideChevronLeft: typeof import('~icons/lucide/chevron-left')['default'] LucideChevronLeft: typeof import('~icons/lucide/chevron-left')['default']
LucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] LucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
LucideEarth: typeof import('~icons/lucide/earth')['default'] LucideEarth: typeof import('~icons/lucide/earth')['default']

View File

@ -1,108 +0,0 @@
<template>
<div v-if="show" class="w-[352px] border-l">
<div
class="flex items-center justify-between p-4.5 text-ink-gray-7 text-lg font-medium"
>
<div>{{ __('Event details') }}</div>
<div class="flex items-center gap-x-1">
<Button variant="ghost" @click="editDetails">
<template #icon>
<EditIcon class="size-4" />
</template>
</Button>
<Button icon="trash-2" variant="ghost" @click="deleteEvent" />
<Dropdown
v-if="event.id"
:options="[
{
label: __('Duplicate'),
icon: 'copy',
onClick: duplicateEvent,
},
]"
>
<Button variant="ghost" icon="more-vertical" />
</Dropdown>
<Button
icon="x"
variant="ghost"
@click="
() => {
show = false
activeEvent = ''
}
"
/>
</div>
</div>
<div class="text-base">
<div class="flex items-start gap-2 px-4.5 py-3 pb-0">
<div
class="mx-0.5 my-[5px] size-2.5 rounded-full cursor-pointer"
:style="{
backgroundColor: event.color || '#30A66D',
}"
/>
<div class="flex flex-col gap-[3px]">
<div class="text-ink-gray-8 font-semibold text-xl">
{{ event.title || __('(No title)') }}
</div>
<div class="text-ink-gray-6 text-p-base">{{ formattedDateTime }}</div>
</div>
</div>
<div
v-if="event.description && event.description !== '<p></p>'"
class="mx-4.5 my-2.5 border-t border-outline-gray-1"
/>
<div v-if="event.description && event.description !== '<p></p>'">
<div class="flex gap-2 items-center text-ink-gray-7 px-4.5 py-1">
<DescriptionIcon class="size-4" />
{{ __('Description') }}
</div>
<div
class="px-4.5 py-2 text-ink-gray-7 text-p-base"
v-html="event.description"
/>
</div>
</div>
</div>
</template>
<script setup>
import DescriptionIcon from '@/components/Icons/DescriptionIcon.vue'
import EditIcon from '@/components/Icons/EditIcon.vue'
import { Dropdown, CalendarActiveEvent as activeEvent, dayjs } from 'frappe-ui'
import { computed } from 'vue'
const props = defineProps({
event: {
type: Object,
required: true,
},
})
const show = defineModel()
const emit = defineEmits(['edit', 'delete'])
const formattedDateTime = computed(() => {
if (props.event.isFullDay) {
return `${__('All day')} - ${dayjs(props.event.fromDateTime).format('ddd, D MMM YYYY')}`
}
const start = dayjs(props.event.fromDateTime)
const end = dayjs(props.event.toDateTime)
return `${start.format('h:mm a')} - ${end.format('h:mm a')} ${start.format('ddd, D MMM YYYY')}`
})
function deleteEvent() {
emit('delete', props.event.id)
}
function duplicateEvent() {
emit('duplicate', props.event)
}
function editDetails() {
emit('edit', props.event)
}
</script>

View File

@ -5,21 +5,26 @@
> >
<div <div
class="flex items-center gap-x-2" class="flex items-center gap-x-2"
:class="event.id && 'cursor-pointer hover:text-ink-gray-8'" :class="mode == 'edit' && 'cursor-pointer hover:text-ink-gray-8'"
@click="event.id && goToDetails()" @click="mode == 'edit' && details()"
> >
<LucideChevronLeft v-if="event.id" class="size-4" /> <LucideChevronLeft v-if="mode == 'edit'" class="size-4" />
{{ __(title) }} {{ __(title) }}
</div> </div>
<div class="flex items-center gap-x-1"> <div class="flex items-center gap-x-1">
<Button v-if="mode == 'details'" variant="ghost" @click="editDetails">
<template #icon>
<EditIcon class="size-4" />
</template>
</Button>
<Button <Button
v-if="event.id" v-if="mode === 'edit' || mode === 'details'"
icon="trash-2" icon="trash-2"
variant="ghost" variant="ghost"
@click="deleteEvent" @click="deleteEvent"
/> />
<Dropdown <Dropdown
v-if="event.id" v-if="mode === 'edit' || mode === 'details'"
:options="[ :options="[
{ {
label: __('Duplicate'), label: __('Duplicate'),
@ -30,19 +35,10 @@
> >
<Button variant="ghost" icon="more-vertical" /> <Button variant="ghost" icon="more-vertical" />
</Dropdown> </Dropdown>
<Button <Button icon="x" variant="ghost" @click="close" />
icon="x"
variant="ghost"
@click="
() => {
show = false
activeEvent = ''
}
"
/>
</div> </div>
</div> </div>
<div class="text-base"> <div v-if="mode !== 'details'" class="text-base">
<div> <div>
<div class="px-4.5 py-3"> <div class="px-4.5 py-3">
<TextInput <TextInput
@ -149,13 +145,14 @@
/> />
</div> </div>
<div class="my-3"> <div class="my-3">
<Button <Button variant="solid" class="w-full" @click="saveEvent">
variant="solid" {{
class="w-full" mode === 'edit'
:disabled="!dirty" ? __('Save')
@click="saveEvent" : mode === 'duplicate'
> ? __('Duplicate event')
{{ _event.id ? __('Save') : __('Create event') }} : __('Create event')
}}
</Button> </Button>
</div> </div>
@ -163,10 +160,42 @@
</div> </div>
</div> </div>
</div> </div>
<div v-else class="text-base">
<div class="flex items-start gap-2 px-4.5 py-3 pb-0">
<div
class="mx-0.5 my-[5px] size-2.5 rounded-full cursor-pointer"
:style="{
backgroundColor: event.color || '#30A66D',
}"
/>
<div class="flex flex-col gap-[3px]">
<div class="text-ink-gray-8 font-semibold text-xl">
{{ event.title || __('(No title)') }}
</div>
<div class="text-ink-gray-6 text-p-base">{{ formattedDateTime }}</div>
</div>
</div>
<div
v-if="event.description && event.description !== '<p></p>'"
class="mx-4.5 my-2.5 border-t border-outline-gray-1"
/>
<div v-if="event.description && event.description !== '<p></p>'">
<div class="flex gap-2 items-center text-ink-gray-7 px-4.5 py-1">
<DescriptionIcon class="size-4" />
{{ __('Description') }}
</div>
<div
class="px-4.5 py-2 text-ink-gray-7 text-p-base"
v-html="event.description"
/>
</div>
</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import EditIcon from '@/components/Icons/EditIcon.vue'
import DescriptionIcon from '@/components/Icons/DescriptionIcon.vue'
import TimePicker from './TimePicker.vue' import TimePicker from './TimePicker.vue'
import { getFormat } from '@/utils' import { getFormat } from '@/utils'
import { import {
@ -187,9 +216,13 @@ const props = defineProps({
type: Object, type: Object,
default: null, default: null,
}, },
mode: {
type: String,
default: 'details',
},
}) })
const emit = defineEmits(['save', 'delete', 'goToDetails']) const emit = defineEmits(['save', 'delete', 'details', 'close', 'edit'])
const show = defineModel() const show = defineModel()
@ -199,29 +232,22 @@ const _event = ref({})
const eventTitle = ref(null) const eventTitle = ref(null)
const error = ref(null) const error = ref(null)
const dirty = computed(() => {
return JSON.stringify(_event.value) !== JSON.stringify(props.event)
})
watch( watch(
() => props.event, () => props.event,
(newEvent) => { (newEvent) => {
error.value = null error.value = null
if (newEvent && newEvent.id) { if (props.mode === 'details') {
title.value = 'Event details'
} else if (props.mode === 'edit') {
title.value = 'Editing event' title.value = 'Editing event'
} else if (!newEvent.title) { } else if (props.mode === 'create') {
title.value = 'New event' title.value = 'New event'
} else { } else {
title.value = 'Duplicate event' title.value = 'Duplicate event'
} }
nextTick(() => { nextTick(() => (_event.value = { ...newEvent }))
_event.value = { ...newEvent }
if (title.value == 'Duplicate event') {
_event.value.title = `${_event.value.title} (Copy)`
}
})
setTimeout(() => eventTitle.value?.el?.focus(), 100) setTimeout(() => eventTitle.value?.el?.focus(), 100)
}, },
{ immediate: true }, { immediate: true },
@ -276,23 +302,38 @@ function saveEvent() {
emit('save', _event.value) emit('save', _event.value)
} }
function editDetails() {
emit('edit', _event.value)
}
function duplicateEvent() { function duplicateEvent() {
props.event.id = '' emit('duplicate', _event.value)
title.value = 'Duplicate event'
_event.value = { ...props.event }
_event.value.title = `${_event.value.title} (Copy)`
nextTick(() => eventTitle.value?.el?.focus())
} }
function deleteEvent() { function deleteEvent() {
emit('delete', _event.value.id) emit('delete', _event.value.id)
} }
function goToDetails() { function details() {
show.value = false emit('details', _event.value)
emit('goToDetails', _event.value)
} }
function close() {
show.value = false
activeEvent.value = ''
emit('close', _event.value)
}
const formattedDateTime = computed(() => {
if (props.event.isFullDay) {
return `${__('All day')} - ${dayjs(props.event.fromDateTime).format('ddd, D MMM YYYY')}`
}
const start = dayjs(props.event.fromDateTime)
const end = dayjs(props.event.toDateTime)
return `${start.format('h:mm a')} - ${end.format('h:mm a')} ${start.format('ddd, D MMM YYYY')}`
})
const colors = Object.keys(colorMap).map((color) => ({ const colors = Object.keys(colorMap).map((color) => ({
label: color.charAt(0).toUpperCase() + color.slice(1), label: color.charAt(0).toUpperCase() + color.slice(1),
value: colorMap[color].color, value: colorMap[color].color,

View File

@ -86,18 +86,13 @@
v-if="showEventPanel" v-if="showEventPanel"
v-model="showEventPanel" v-model="showEventPanel"
:event="event" :event="event"
:mode="mode"
@save="saveEvent" @save="saveEvent"
@delete="deleteEvent"
@goToDetails="showDetails"
/>
<CalendarEventDetails
v-if="showEventDetails"
v-model="showEventDetails"
:event="event"
@edit="editDetails" @edit="editDetails"
@delete="deleteEvent" @delete="deleteEvent"
@duplicate="duplicateEvent" @duplicate="duplicateEvent"
@details="showDetails"
@close="close"
/> />
</div> </div>
</template> </template>
@ -182,9 +177,9 @@ function createEvent(_event) {
onSuccess: (e) => { onSuccess: (e) => {
_event.id = e.name _event.id = e.name
event.value = _event event.value = _event
showEventPanel.value = false showEventPanel.value = true
showEventDetails.value = true
activeEvent.value = e.name activeEvent.value = e.name
mode.value = 'details'
}, },
}, },
) )
@ -193,16 +188,18 @@ function createEvent(_event) {
function updateEvent(_event) { function updateEvent(_event) {
if (!_event.id) return if (!_event.id) return
events.setValue.submit({ if (mode.value === 'edit' || mode.value === 'details') {
name: _event.id, events.setValue.submit({
subject: _event.title, name: _event.id,
description: _event.description, subject: _event.title,
starts_on: _event.fromDateTime, description: _event.description,
ends_on: _event.toDateTime, starts_on: _event.fromDateTime,
all_day: _event.isFullDay, ends_on: _event.toDateTime,
event_type: _event.eventType, all_day: _event.isFullDay,
color: _event.color, event_type: _event.eventType,
}) color: _event.color,
})
}
event.value = _event event.value = _event
} }
@ -223,9 +220,9 @@ function deleteEvent(eventID) {
onClick: (close) => { onClick: (close) => {
events.delete.submit(eventID) events.delete.submit(eventID)
showEventPanel.value = false showEventPanel.value = false
showEventDetails.value = false
event.value = {} event.value = {}
activeEvent.value = '' activeEvent.value = ''
mode.value = ''
close() close()
}, },
}, },
@ -235,56 +232,86 @@ function deleteEvent(eventID) {
onMounted(() => { onMounted(() => {
activeEvent.value = '' activeEvent.value = ''
mode.value = ''
showEventPanel.value = false showEventPanel.value = false
showEventDetails.value = false
}) })
const showEventPanel = ref(false) const showEventPanel = ref(false)
const showEventDetails = ref(false)
const event = ref({}) const event = ref({})
const mode = ref('')
function showDetails(e) { function showDetails(e) {
let _e = e?.calendarEvent || e let _e = e?.calendarEvent || e
showEventPanel.value = false showEventPanel.value = true
showEventDetails.value = true
event.value = { ..._e } event.value = { ..._e }
activeEvent.value = _e.id activeEvent.value = _e.id
mode.value = 'details'
} }
function editDetails(e) { function editDetails(e) {
let _e = e?.calendarEvent || e let _e = e?.calendarEvent || e
showEventDetails.value = false
showEventPanel.value = true showEventPanel.value = true
event.value = { ..._e } event.value = { ..._e }
activeEvent.value = _e.id activeEvent.value = _e.id
mode.value = 'edit'
} }
function newEvent(e) { function newEvent(e, duplicate = false) {
let [fromTime, toTime] = getFromToTime(e.time) let fromTime = e.fromTime
let toTime = e.toTime
let fromDate = e.fromDate
let fromDate = dayjs(e.date).format('YYYY-MM-DD') if (!duplicate) {
let t = getFromToTime(e.time)
fromTime = t[0]
toTime = t[1]
fromDate = dayjs(e.date).format('YYYY-MM-DD')
}
activeEvent.value = ''
showEventDetails.value = false
showEventPanel.value = true showEventPanel.value = true
event.value = { event.value = {
title: '', title: duplicate ? `${e.title} (Copy)` : '',
description: '', description: e.description || '',
date: fromDate, date: fromDate,
fromDate: fromDate, fromDate: fromDate,
toDate: fromDate, toDate: fromDate,
fromTime, fromTime,
toTime, toTime,
isFullDay: false, isFullDay: e.isFullDay,
eventType: 'Public', eventType: e.eventType || 'Public',
color: e.color || 'green',
} }
events.data.push({
id: duplicate ? 'duplicate-event' : 'new-event',
title: duplicate ? `${e.title} (Copy)` : '',
description: e.description || '',
status: 'Open',
eventType: e.eventType || 'Public',
fromDate: fromDate + ' ' + fromTime,
toDate: fromDate + ' ' + toTime,
color: e.color || 'green',
isFullDay: e.isFullDay,
})
activeEvent.value = duplicate ? 'duplicate-event' : 'new-event'
mode.value = duplicate ? 'duplicate' : 'create'
} }
function duplicateEvent(e) { function duplicateEvent(e) {
showEventDetails.value = false newEvent(e, true)
showEventPanel.value = true }
e.id = ''
event.value = { ...e } function close() {
showEventPanel.value = false
event.value = {}
activeEvent.value = ''
mode.value = ''
events.data = events.data.filter(
(ev) => ev.id !== 'new-event' && ev.id !== 'duplicate-event',
)
} }
// utils // utils