crm/frontend/src/components/Modals/EventModal.vue
Shariq Ansari ea644c22f1 feat: added events tab in lead/deal page
create & update events from lead/deal page
link events with lead or deal
2025-09-03 13:04:36 +05:30

312 lines
8.6 KiB
Vue

<template>
<Dialog v-model="show" :options="{ size: 'xl' }">
<template #body-title>
<div class="flex items-center gap-3">
<h3 class="text-2xl font-semibold leading-6 text-ink-gray-9">
{{ editMode ? __('Edit an event') : __('Create an event') }}
</h3>
</div>
</template>
<template #body-content>
<div class="flex flex-col gap-4">
<div class="flex items-center">
<div class="text-base text-ink-gray-7 w-3/12">
{{ __('Title') }}
</div>
<TextInput
ref="title"
class="w-9/12"
size="md"
v-model="_event.title"
:placeholder="__('Call with John Doe')"
variant="outline"
required
/>
</div>
<div class="flex items-center">
<div class="text-base text-ink-gray-7 w-3/12">
{{ __('All day') }}
</div>
<Switch v-model="_event.isFullDay" />
</div>
<div class="border-t border-outline-gray-1" />
<div class="flex items-center">
<div class="text-base text-ink-gray-7 w-3/12">
{{ __('Date & Time') }}
</div>
<div class="flex gap-2 w-9/12">
<DatePicker
:class="[_event.isFullDay ? 'w-full' : 'w-[160px]']"
variant="outline"
:value="_event.fromDate"
:formatter="(date) => getFormat(date, 'MMM D, YYYY')"
:placeholder="__('May 1, 2025')"
@update:modelValue="(date) => updateDate(date, true)"
>
<template #suffix="{ togglePopover }">
<FeatherIcon
name="chevron-down"
class="h-4 w-4 cursor-pointer"
@click="togglePopover"
/>
</template>
</DatePicker>
<TimePicker
v-if="!_event.isFullDay"
variant="outline"
:value="_event.fromTime"
:placeholder="__('Start Time')"
@update:modelValue="(time) => updateTime(time, true)"
>
<template #suffix="{ togglePopover }">
<FeatherIcon
name="chevron-down"
class="h-4 w-4 cursor-pointer"
@click="togglePopover"
/>
</template>
</TimePicker>
<TimePicker
v-if="!_event.isFullDay"
variant="outline"
:value="_event.toTime"
:placeholder="__('End Time')"
@update:modelValue="(time) => updateTime(time)"
>
<template #suffix="{ togglePopover }">
<FeatherIcon
name="chevron-down"
class="h-4 w-4 cursor-pointer"
@click="togglePopover"
/>
</template>
</TimePicker>
</div>
</div>
<div class="flex">
<div class="mt-2 text-base text-ink-gray-7 w-3/12">
{{ __('Description') }}
</div>
<div class="w-9/12">
<TextEditor
editor-class="!prose-sm overflow-auto min-h-[80px] max-h-80 py-1.5 px-2 rounded border border-outline-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-3 hover:border-outline-gray-modals hover:shadow-sm focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3 text-ink-gray-8 transition-colors"
:bubbleMenu="true"
:content="_event.description"
@change="(val) => (_event.description = val)"
:placeholder="__('Add description.')"
/>
</div>
</div>
<ErrorMessage class="mt-4" v-if="error" :message="__(error)" />
</div>
</template>
<template #actions>
<div class="flex gap-2 justify-end">
<Button :label="__('Cancel')" @click="show = false" />
<Button
variant="solid"
:label="editMode ? __('Update') : __('Create')"
:loading="editMode ? events.setValue.loading : events.insert.loading"
@click="update"
/>
</div>
</template>
</Dialog>
</template>
<script setup>
import {
Switch,
TextEditor,
ErrorMessage,
Dialog,
DatePicker,
dayjs,
} from 'frappe-ui'
import { getFormat } from '@/utils'
import { onMounted, ref } from 'vue'
const props = defineProps({
event: {
type: Object,
default: () => ({}),
},
doctype: {
type: String,
default: '',
},
docname: {
type: String,
default: '',
},
})
const show = defineModel()
const events = defineModel('events')
const title = ref(null)
const error = ref(null)
const editMode = ref(false)
const _event = ref({
title: '',
description: '',
fromDate: '',
toDate: '',
fromTime: '',
toTime: '',
isFullDay: false,
eventType: 'Public',
color: 'green',
})
onMounted(() => {
if (props.event) {
let start = dayjs(props.event.starts_on)
let end = dayjs(props.event.ends_on)
if (props.event.name) {
editMode.value = true
} else {
start = dayjs()
end = dayjs().add(1, 'hour')
}
_event.value = {
id: props.event.name || '',
title: props.event.subject,
description: props.event.description,
fromDate: start.format('YYYY-MM-DD'),
toDate: end.format('YYYY-MM-DD'),
fromTime: start.format('HH:mm'),
toTime: end.format('HH:mm'),
isFullDay: props.event.all_day,
eventType: props.event.event_type,
color: props.event.color,
}
setTimeout(() => title.value?.el?.focus(), 100)
}
})
function updateDate(d) {
_event.value.fromDate = d
_event.value.toDate = d
}
function updateTime(t, fromTime = false) {
error.value = null
let oldTo = _event.value.toTime || _event.value.fromTime
if (fromTime) {
_event.value.fromTime = t
if (!_event.value.toTime) {
const hour = parseInt(t.split(':')[0])
const minute = parseInt(t.split(':')[1])
_event.value.toTime = `${hour + 1}:${minute}`
}
} else {
_event.value.toTime = t
}
if (_event.value.toTime && _event.value.fromTime) {
const diff = dayjs(_event.value.toDate + ' ' + _event.value.toTime).diff(
dayjs(_event.value.fromDate + ' ' + _event.value.fromTime),
'minute',
)
if (diff <= 0) {
_event.value.toTime = oldTo
error.value = __('End time should be after start time')
return
}
}
}
function update() {
error.value = null
if (!_event.value.title) {
error.value = __('Title is required')
title.value.el.focus()
return
}
_event.value.id ? updateEvent() : createEvent()
}
function createEvent() {
events.value.insert.submit(
{
subject: _event.value.title,
description: _event.value.description,
starts_on: _event.value.fromDate + ' ' + _event.value.fromTime,
ends_on: _event.value.toDate + ' ' + _event.value.toTime,
all_day: _event.value.isFullDay || false,
event_type: _event.value.eventType,
color: _event.value.color,
reference_doctype: props.doctype,
reference_docname: props.docname,
},
{
onSuccess: async () => {
await events.value.reload()
show.value = false
},
},
)
}
function updateEvent() {
if (!_event.value.id) {
error.value = __('Event ID is required')
return
}
events.value.setValue.submit(
{
name: _event.value.id,
subject: _event.value.title,
description: _event.value.description,
starts_on: _event.value.fromDate + ' ' + _event.value.fromTime,
ends_on: _event.value.toDate + ' ' + _event.value.toTime,
all_day: _event.value.isFullDay,
event_type: _event.value.eventType,
color: _event.value.color,
reference_doctype: props.doctype,
reference_docname: props.docname,
},
{
onSuccess: async () => {
await events.value.reload()
show.value = false
},
},
)
}
// function deleteEvent(eventID) {
// if (!eventID) return
// $dialog({
// title: __('Delete'),
// message: __('Are you sure you want to delete this event?'),
// actions: [
// {
// label: __('Delete'),
// variant: 'solid',
// theme: 'red',
// onClick: (close) => {
// events.delete.submit(eventID, {
// onSuccess: () => events.reload(),
// })
// showEventPanel.value = false
// event.value = {}
// activeEvent.value = ''
// mode.value = ''
// close()
// },
// },
// ],
// })
// }
</script>