feat: add CalendarEventDetails component and integrate it into Calendar for event management

This commit is contained in:
Shariq Ansari 2025-08-05 17:37:32 +05:30
parent 55c61cbd80
commit 1c432d8610
5 changed files with 151 additions and 8 deletions

View File

@ -46,6 +46,7 @@ declare module 'vue' {
BrandLogo: typeof import('./src/components/BrandLogo.vue')['default']
BrandSettings: typeof import('./src/components/Settings/General/BrandSettings.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']
CalendarIcon: typeof import('./src/components/Icons/CalendarIcon.vue')['default']
CalendarModal: typeof import('./src/components/Modals/CalendarModal.vue')['default']
@ -90,6 +91,7 @@ declare module 'vue' {
DealsListView: typeof import('./src/components/ListViews/DealsListView.vue')['default']
DeclinedCallIcon: typeof import('./src/components/Icons/DeclinedCallIcon.vue')['default']
DeleteLinkedDocModal: typeof import('./src/components/DeleteLinkedDocModal.vue')['default']
DescriptionIcon: typeof import('./src/components/Icons/DescriptionIcon.vue')['default']
DesendingIcon: typeof import('./src/components/Icons/DesendingIcon.vue')['default']
DesktopLayout: typeof import('./src/components/Layouts/DesktopLayout.vue')['default']
DetailsIcon: typeof import('./src/components/Icons/DetailsIcon.vue')['default']

View File

@ -0,0 +1,104 @@
<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-2">
<Button variant="ghost" @click="editDetails">
<template #icon>
<EditIcon class="size-4" />
</template>
</Button>
<Dropdown
v-if="event.id"
:options="[
{
label: __('Delete'),
value: 'delete',
icon: 'trash-2',
onClick: deleteEvent,
},
]"
>
<Button variant="ghost" icon="more-horizontal" />
</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 class="mx-4.5 my-2.5 border-t border-outline-gray-1" />
<div>
<div class="flex gap-2 items-center text-ink-gray-7 px-4.5 py-2">
<DescriptionIcon class="size-4" />
{{ __('Description') }}
</div>
<div class="px-4.5 py-2 text-ink-gray-7 text-p-base">
<div
v-if="event.description && event.description !== '<p></p>'"
v-html="event.description"
/>
<div class="text-ink-gray-5" v-else>{{ __('(No description)') }}</div>
</div>
</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 editDetails() {
emit('edit', props.event)
}
</script>

View File

@ -42,7 +42,7 @@
<template #prefix>
<Dropdown class="ml-1" :options="colors">
<div
class="ml-0.5 size-2 rounded-full cursor-pointer"
class="ml-0.5 size-2.5 rounded-full cursor-pointer"
:style="{
backgroundColor: _event.color || '#30A66D',
}"
@ -125,7 +125,7 @@
</TimePicker>
</div>
</div>
<div class="mx-4.5 my-2.5 border-t" />
<div class="mx-4.5 my-2.5 border-t border-outline-gray-1" />
<div class="px-4.5 py-3">
<div class="flex items-center gap-x-2 border rounded py-1">
<TextEditor
@ -197,7 +197,7 @@ watch(
error.value = null
if (newEvent && newEvent.id) {
title.value = 'Event details'
title.value = 'Editing event'
} else {
title.value = 'New event'
}
@ -265,7 +265,7 @@ const colors = Object.keys(colorMap).map((color) => ({
label: color.charAt(0).toUpperCase() + color.slice(1),
value: colorMap[color].color,
icon: h('div', {
class: '!size-2 rounded-full',
class: '!size-2.5 rounded-full',
style: { backgroundColor: colorMap[color].color },
}),
onClick: () => {

View File

@ -0,0 +1,14 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.66699 12.7109C7.89499 12.7574 8.06641 12.9594 8.06641 13.2012C8.06625 13.4428 7.89489 13.6449 7.66699 13.6914L7.56641 13.7012H1.5C1.22406 13.7011 1.00018 13.4771 1 13.2012C1 12.9251 1.22395 12.7013 1.5 12.7012H7.56641L7.66699 12.7109ZM14.6006 9.24414C14.8284 9.29081 15 9.4928 15 9.73438C14.9999 9.97585 14.8283 10.178 14.6006 10.2246L14.5 10.2344H1.5C1.22403 10.2343 1.00013 10.0103 1 9.73438C1 9.4583 1.22395 9.23448 1.5 9.23438H14.5L14.6006 9.24414ZM3.56934 2.45996C3.78682 2.52747 3.96526 2.69406 4.04297 2.91699L5.2168 6.29199C5.31837 6.58437 5.10155 6.88965 4.79199 6.88965H4.77441C4.57659 6.88946 4.40241 6.75957 4.34473 6.57031L4.11133 5.80664H2.53613L2.30273 6.57129C2.24483 6.7604 2.06986 6.88965 1.87207 6.88965H1.85742C1.54804 6.88951 1.33114 6.5843 1.43262 6.29199L2.59961 2.93066C2.70432 2.62909 2.98841 2.42694 3.30762 2.42676H3.56934V2.45996ZM14.6006 5.77734C14.8283 5.82404 15 6.02602 15 6.26758C14.9999 6.50907 14.8283 6.71112 14.6006 6.75781L14.5 6.76758H7.56641C7.29038 6.7675 7.06648 6.5436 7.06641 6.26758C7.06641 5.99149 7.29033 5.76766 7.56641 5.76758H14.5L14.6006 5.77734ZM2.75 5.1084H3.89844L3.35254 3.31738H3.29688L2.75 5.1084ZM14.6006 2.31055C14.8283 2.35723 14.9999 2.55931 15 2.80078C15 3.04233 14.8283 3.24432 14.6006 3.29102L14.5 3.30078H7.56641C7.29033 3.3007 7.06641 3.07687 7.06641 2.80078C7.06651 2.52478 7.2904 2.30086 7.56641 2.30078H14.5L14.6006 2.31055Z"
fill="currentColor"
/>
</svg>
</template>

View File

@ -89,6 +89,14 @@
@save="saveEvent"
@delete="deleteEvent"
/>
<CalendarEventDetails
v-if="showEventDetails"
v-model="showEventDetails"
:event="event"
@edit="editDetails"
@delete="deleteEvent"
/>
</div>
</template>
<script setup>
@ -181,6 +189,8 @@ function createEvent(_event) {
onSuccess: (e) => {
_event.id = e.name
event.value = _event
showEventPanel.value = false
showEventDetails.value = true
activeEvent.value = e.name
},
},
@ -201,7 +211,10 @@ function updateEvent(_event) {
color: _event.color,
})
showEventPanel.value = false
showEventDetails.value = true
event.value = _event
activeEvent.value = _event.id
}
function deleteEvent(eventID) {
@ -220,6 +233,8 @@ function deleteEvent(eventID) {
onClick: (close) => {
events.delete.submit(eventID)
showEventPanel.value = false
showEventDetails.value = false
event.value = {}
activeEvent.value = ''
close()
},
@ -229,15 +244,22 @@ function deleteEvent(eventID) {
}
const showEventPanel = ref(false)
const showEventDetails = ref(false)
const event = ref({})
function showDetails(e) {}
function showDetails(e) {
showEventPanel.value = false
showEventDetails.value = true
event.value = { ...e.calendarEvent }
activeEvent.value = e.calendarEvent.id
}
function editDetails(e) {
let _e = e?.calendarEvent || e
showEventDetails.value = false
showEventPanel.value = true
event.value = { ...e.calendarEvent }
activeEvent.value = e.calendarEvent.id
event.value = { ..._e }
activeEvent.value = _e.id
}
function showEventPanelArea(e) {
@ -246,6 +268,7 @@ function showEventPanelArea(e) {
let fromDate = dayjs(e.date).format('YYYY-MM-DD')
activeEvent.value = ''
showEventDetails.value = false
showEventPanel.value = true
event.value = {
title: '',