feat: add duplicate functionality to CalendarEventDetails and CalendarEventPanel components

This commit is contained in:
Shariq Ansari 2025-08-06 17:41:53 +05:30
parent 1cc972ea8b
commit 38b838ec97
4 changed files with 69 additions and 21 deletions

View File

@ -191,8 +191,10 @@ 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']
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']
LucideLetterText: typeof import('~icons/lucide/letter-text')['default']
LucidePlus: typeof import('~icons/lucide/plus')['default'] LucidePlus: typeof import('~icons/lucide/plus')['default']
LucideText: typeof import('~icons/lucide/text')['default'] LucideText: typeof import('~icons/lucide/text')['default']
LucideX: typeof import('~icons/lucide/x')['default'] LucideX: typeof import('~icons/lucide/x')['default']

View File

@ -4,24 +4,24 @@
class="flex items-center justify-between p-4.5 text-ink-gray-7 text-lg font-medium" class="flex items-center justify-between p-4.5 text-ink-gray-7 text-lg font-medium"
> >
<div>{{ __('Event details') }}</div> <div>{{ __('Event details') }}</div>
<div class="flex items-center gap-x-2"> <div class="flex items-center gap-x-1">
<Button variant="ghost" @click="editDetails"> <Button variant="ghost" @click="editDetails">
<template #icon> <template #icon>
<EditIcon class="size-4" /> <EditIcon class="size-4" />
</template> </template>
</Button> </Button>
<Button icon="trash-2" variant="ghost" @click="deleteEvent" />
<Dropdown <Dropdown
v-if="event.id" v-if="event.id"
:options="[ :options="[
{ {
label: __('Delete'), label: __('Duplicate'),
value: 'delete', icon: 'copy',
icon: 'trash-2', onClick: duplicateEvent,
onClick: deleteEvent,
}, },
]" ]"
> >
<Button variant="ghost" icon="more-horizontal" /> <Button variant="ghost" icon="more-vertical" />
</Dropdown> </Dropdown>
<Button <Button
icon="x" icon="x"
@ -98,6 +98,10 @@ function deleteEvent() {
emit('delete', props.event.id) emit('delete', props.event.id)
} }
function duplicateEvent() {
emit('duplicate', props.event)
}
function editDetails() { function editDetails() {
emit('edit', props.event) emit('edit', props.event)
} }

View File

@ -3,20 +3,32 @@
<div <div
class="flex items-center justify-between p-4.5 text-ink-gray-7 text-lg font-medium" class="flex items-center justify-between p-4.5 text-ink-gray-7 text-lg font-medium"
> >
<div>{{ __(title) }}</div> <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'"
@click="event.id && goToDetails()"
>
<LucideChevronLeft v-if="event.id" class="size-4" />
{{ __(title) }}
</div>
<div class="flex items-center gap-x-1">
<Button
v-if="event.id"
icon="trash-2"
variant="ghost"
@click="deleteEvent"
/>
<Dropdown <Dropdown
v-if="event.id" v-if="event.id"
:options="[ :options="[
{ {
label: __('Delete'), label: __('Duplicate'),
value: 'delete', icon: 'copy',
icon: 'trash-2', onClick: duplicateEvent,
onClick: deleteEvent,
}, },
]" ]"
> >
<Button variant="ghost" icon="more-horizontal" /> <Button variant="ghost" icon="more-vertical" />
</Dropdown> </Dropdown>
<Button <Button
icon="x" icon="x"
@ -177,7 +189,7 @@ const props = defineProps({
}, },
}) })
const emit = defineEmits(['save', 'delete']) const emit = defineEmits(['save', 'delete', 'goToDetails'])
const show = defineModel() const show = defineModel()
@ -198,11 +210,18 @@ watch(
if (newEvent && newEvent.id) { if (newEvent && newEvent.id) {
title.value = 'Editing event' title.value = 'Editing event'
} else { } else if (!newEvent.title) {
title.value = 'New event' title.value = 'New event'
} else {
title.value = 'Duplicate event'
} }
nextTick(() => (_event.value = { ...newEvent })) nextTick(() => {
_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 },
@ -257,10 +276,23 @@ function saveEvent() {
emit('save', _event.value) emit('save', _event.value)
} }
function duplicateEvent() {
props.event.id = ''
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() {
show.value = false
emit('goToDetails', _event.value)
}
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

@ -4,7 +4,7 @@
<ViewBreadcrumbs routeName="Calendar" /> <ViewBreadcrumbs routeName="Calendar" />
</template> </template>
<template #right-header> <template #right-header>
<Button variant="solid" :label="__('Create')" @click="showEventPanelArea"> <Button variant="solid" :label="__('Create')" @click="newEvent">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template> <template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button> </Button>
</template> </template>
@ -29,7 +29,7 @@
@delete="(eventID) => deleteEvent(eventID)" @delete="(eventID) => deleteEvent(eventID)"
:onClick="showDetails" :onClick="showDetails"
:onDblClick="editDetails" :onDblClick="editDetails"
:onCellDblClick="showEventPanelArea" :onCellDblClick="newEvent"
> >
<template <template
#header="{ #header="{
@ -88,6 +88,7 @@
:event="event" :event="event"
@save="saveEvent" @save="saveEvent"
@delete="deleteEvent" @delete="deleteEvent"
@goToDetails="showDetails"
/> />
<CalendarEventDetails <CalendarEventDetails
@ -96,6 +97,7 @@
:event="event" :event="event"
@edit="editDetails" @edit="editDetails"
@delete="deleteEvent" @delete="deleteEvent"
@duplicate="duplicateEvent"
/> />
</div> </div>
</template> </template>
@ -249,10 +251,11 @@ const showEventDetails = ref(false)
const event = ref({}) const event = ref({})
function showDetails(e) { function showDetails(e) {
let _e = e?.calendarEvent || e
showEventPanel.value = false showEventPanel.value = false
showEventDetails.value = true showEventDetails.value = true
event.value = { ...e.calendarEvent } event.value = { ..._e }
activeEvent.value = e.calendarEvent.id activeEvent.value = _e.id
} }
function editDetails(e) { function editDetails(e) {
@ -263,7 +266,7 @@ function editDetails(e) {
activeEvent.value = _e.id activeEvent.value = _e.id
} }
function showEventPanelArea(e) { function newEvent(e) {
let [fromTime, toTime] = getFromToTime(e.time) let [fromTime, toTime] = getFromToTime(e.time)
let fromDate = dayjs(e.date).format('YYYY-MM-DD') let fromDate = dayjs(e.date).format('YYYY-MM-DD')
@ -284,6 +287,13 @@ function showEventPanelArea(e) {
} }
} }
function duplicateEvent(e) {
showEventDetails.value = false
showEventPanel.value = true
e.id = ''
event.value = { ...e }
}
// utils // utils
function getFromToTime(time) { function getFromToTime(time) {
let currentTime = dayjs().format('HH:mm') || '00:00' let currentTime = dayjs().format('HH:mm') || '00:00'