crm/frontend/src/pages/Calendar.vue

338 lines
7.9 KiB
Vue

<template>
<LayoutHeader>
<template #left-header>
<ViewBreadcrumbs routeName="Calendar" />
</template>
<template #right-header>
<Button variant="solid" :label="__('Create')" @click="newEvent">
<template #prefix><FeatherIcon name="plus" class="h-4" /></template>
</Button>
</template>
</LayoutHeader>
<div class="flex h-screen overflow-hidden">
<Calendar
v-if="events.data?.length"
class="flex-1 overflow-hidden"
ref="calendar"
:config="{
defaultMode: 'Week',
isEditMode: true,
eventIcons: {},
allowCustomClickEvents: true,
redundantCellHeight: 100,
enableShortcuts: false,
noBorder: true,
}"
:events="events.data"
@create="(event) => createEvent(event)"
@update="(event) => updateEvent(event)"
@delete="(eventID) => deleteEvent(eventID)"
:onClick="showDetails"
:onDblClick="editDetails"
:onCellDblClick="newEvent"
>
<template
#header="{
currentMonthYear,
enabledModes,
activeView,
decrement,
increment,
updateActiveView,
}"
>
<div class="my-4 mx-5 flex justify-between">
<!-- left side -->
<!-- Year, Month -->
<span class="text-lg font-medium text-ink-gray-8">
{{ currentMonthYear }}
</span>
<!-- right side -->
<!-- actions buttons for calendar -->
<div class="flex gap-x-1">
<!-- Increment and Decrement Button-->
<Button
@click="decrement()"
variant="ghost"
class="h-4 w-4"
icon="chevron-left"
/>
<Button
@click="increment()"
variant="ghost"
class="h-4 w-4"
icon="chevron-right"
/>
<!-- View change button, default is months or can be set via props! -->
<TabButtons
:buttons="enabledModes"
class="ml-2"
:modelValue="activeView"
@update:modelValue="updateActiveView($event)"
/>
</div>
</div>
</template>
<template #daily-header="{ parseDateWithDay, currentDate }">
<p class="ml-4 pb-2 text-base text-ink-gray-6">
{{ parseDateWithDay(currentDate) }}
</p>
</template>
</Calendar>
<CalendarEventPanel
v-if="showEventPanel"
v-model="showEventPanel"
:event="event"
@save="saveEvent"
@delete="deleteEvent"
@goToDetails="showDetails"
/>
<CalendarEventDetails
v-if="showEventDetails"
v-model="showEventDetails"
:event="event"
@edit="editDetails"
@delete="deleteEvent"
@duplicate="duplicateEvent"
/>
</div>
</template>
<script setup>
import CalendarEventPanel from '@/components/Calendar/CalendarEventPanel.vue'
import ViewBreadcrumbs from '@/components/ViewBreadcrumbs.vue'
import LayoutHeader from '@/components/LayoutHeader.vue'
import { sessionStore } from '@/stores/session'
import { globalStore } from '@/stores/global'
import {
Calendar,
createListResource,
TabButtons,
dayjs,
CalendarActiveEvent as activeEvent,
} from 'frappe-ui'
import { onMounted, ref } from 'vue'
const { user } = sessionStore()
const { $dialog } = globalStore()
const calendar = ref(null)
const events = createListResource({
doctype: 'Event',
cache: ['calendar', user],
fields: [
'name',
'status',
'subject',
'description',
'starts_on',
'ends_on',
'all_day',
'event_type',
'color',
],
filters: { status: 'Open', owner: user },
auto: true,
transform: (data) => {
return data.map((event) => ({
id: event.name,
title: event.subject,
description: event.description,
status: event.status,
fromDate: event.starts_on,
toDate: event.ends_on,
isFullDay: event.all_day,
eventType: event.event_type,
color: event.color,
}))
},
insert: {
onSuccess: () => events.reload(),
},
delete: {
onSuccess: () => events.reload(),
},
setValue: {
onSuccess: () => events.reload(),
},
})
function saveEvent(_event) {
_event.id ? updateEvent(_event) : createEvent(_event)
}
function createEvent(_event) {
if (!_event.title) return
events.insert.submit(
{
subject: _event.title,
description: _event.description,
starts_on: _event.fromDateTime,
ends_on: _event.toDateTime,
all_day: _event.isFullDay,
event_type: _event.eventType,
color: _event.color,
},
{
onSuccess: (e) => {
_event.id = e.name
event.value = _event
showEventPanel.value = false
showEventDetails.value = true
activeEvent.value = e.name
},
},
)
}
function updateEvent(_event) {
if (!_event.id) return
events.setValue.submit({
name: _event.id,
subject: _event.title,
description: _event.description,
starts_on: _event.fromDateTime,
ends_on: _event.toDateTime,
all_day: _event.isFullDay,
event_type: _event.eventType,
color: _event.color,
})
showEventPanel.value = false
if (showEventPanel.value) {
showEventPanel.value = true
activeEvent.value = _event.id
}
event.value = _event
}
function deleteEvent(eventID) {
if (!eventID) return
$dialog({
title: __('Delete'),
message: __('Are you sure you want to delete this event?'),
variant: 'solid',
theme: 'red',
actions: [
{
label: __('Delete'),
variant: 'solid',
theme: 'red',
onClick: (close) => {
events.delete.submit(eventID)
showEventPanel.value = false
showEventDetails.value = false
event.value = {}
activeEvent.value = ''
close()
},
},
],
})
}
onMounted(() => {
activeEvent.value = ''
showEventPanel.value = false
showEventDetails.value = false
})
const showEventPanel = ref(false)
const showEventDetails = ref(false)
const event = ref({})
function showDetails(e) {
let _e = e?.calendarEvent || e
showEventPanel.value = false
showEventDetails.value = true
event.value = { ..._e }
activeEvent.value = _e.id
}
function editDetails(e) {
let _e = e?.calendarEvent || e
showEventDetails.value = false
showEventPanel.value = true
event.value = { ..._e }
activeEvent.value = _e.id
}
function newEvent(e) {
let [fromTime, toTime] = getFromToTime(e.time)
let fromDate = dayjs(e.date).format('YYYY-MM-DD')
activeEvent.value = ''
showEventDetails.value = false
showEventPanel.value = true
event.value = {
title: '',
description: '',
date: fromDate,
fromDate: fromDate,
toDate: fromDate,
fromTime,
toTime,
isFullDay: false,
eventType: 'Public',
}
}
function duplicateEvent(e) {
showEventDetails.value = false
showEventPanel.value = true
e.id = ''
event.value = { ...e }
}
// utils
function getFromToTime(time) {
let currentTime = dayjs().format('HH:mm') || '00:00'
let h = currentTime.split(':')[0]
let m = parseInt(currentTime.split(':')[1])
m = Math.floor(m / 15) * 15
m = m < 10 ? '0' + m : String(m)
let fromTime = `${h}:${m}`
let toTime = `${parseInt(h) + 1}:${m}`
if (
time?.toLowerCase().includes('am') ||
time?.toLowerCase().includes('pm')
) {
// 12 hour format
time = time.trim().replace(' ', '')
const ampm = time.slice(-2)
time = time.slice(0, -2)
let hour = time
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'}`
}
return [fromTime, toTime]
}
</script>