import dayGridPlugin from '@fullcalendar/daygrid' import interactionPlugin from '@fullcalendar/interaction' import listPlugin from '@fullcalendar/list' import timeGridPlugin from '@fullcalendar/timegrid' import { useConfigStore } from '@core/stores/config' import { useCalendarStore } from '@/views/apps/calendar/useCalendarStore' export const blankEvent = { title: '', start: '', end: '', allDay: false, url: '', extendedProps: { /* â„šī¸ We have to use undefined here because if we have blank string as value then select placeholder will be active (moved to top). Hence, we need to set it to undefined or null */ calendar: undefined, guests: [], location: '', description: '', }, } export const useCalendar = (event, isEventHandlerSidebarActive, isLeftSidebarOpen) => { const configStore = useConfigStore() // 👉 Store const store = useCalendarStore() // 👉 Calendar template ref const refCalendar = ref() // 👉 Calendar colors const calendarsColor = { Business: 'primary', Holiday: 'success', Personal: 'error', Family: 'warning', ETC: 'info', } // â„šī¸ Extract event data from event API const extractEventDataFromEventApi = eventApi => { const { id, title, start, end, url, extendedProps: { calendar, guests, location, description }, allDay } = eventApi return { id, title, start, end, url, extendedProps: { calendar, guests, location, description, }, allDay, } } if (typeof process !== 'undefined' && process.server) store.fetchEvents() // 👉 Fetch events const fetchEvents = (info, successCallback) => { // If there's no info => Don't make useless API call if (!info) return store.fetchEvents() .then(r => { successCallback(r.map(e => ({ ...e, // Convert string representation of date to Date object start: new Date(e.start), end: new Date(e.end), }))) }) .catch(e => { console.error('Error occurred while fetching calendar events', e) }) } // 👉 Calendar API const calendarApi = ref(null) // 👉 Update event in calendar [UI] const updateEventInCalendar = (updatedEventData, propsToUpdate, extendedPropsToUpdate) => { const existingEvent = calendarApi.value?.getEventById(String(updatedEventData.id)) if (!existingEvent) { console.warn('Can\'t found event in calendar to update') return } // ---Set event properties except date related // Docs: https://fullcalendar.io/docs/Event-setProp // dateRelatedProps => ['start', 'end', 'allDay'] for (let index = 0; index < propsToUpdate.length; index++) { const propName = propsToUpdate[index] existingEvent.setProp(propName, updatedEventData[propName]) } // --- Set date related props // ? Docs: https://fullcalendar.io/docs/Event-setDates existingEvent.setDates(updatedEventData.start, updatedEventData.end, { allDay: updatedEventData.allDay }) // --- Set event's extendedProps // ? Docs: https://fullcalendar.io/docs/Event-setExtendedProp for (let index = 0; index < extendedPropsToUpdate.length; index++) { const propName = extendedPropsToUpdate[index] existingEvent.setExtendedProp(propName, updatedEventData.extendedProps[propName]) } } // 👉 Remove event in calendar [UI] const removeEventInCalendar = eventId => { const _event = calendarApi.value?.getEventById(eventId) if (_event) _event.remove() } // 👉 refetch events const refetchEvents = () => { calendarApi.value?.refetchEvents() } watch(() => store.selectedCalendars, refetchEvents) // 👉 Add event const addEvent = _event => { store.addEvent(_event) .then(() => { refetchEvents() }) } // 👉 Update event const updateEvent = _event => { store.updateEvent(_event) .then(r => { const propsToUpdate = ['id', 'title', 'url'] const extendedPropsToUpdate = ['calendar', 'guests', 'location', 'description'] updateEventInCalendar(r, propsToUpdate, extendedPropsToUpdate) }) refetchEvents() } // 👉 Remove event const removeEvent = eventId => { store.removeEvent(eventId).then(() => { removeEventInCalendar(eventId) }) } // 👉 Calendar options const calendarOptions = { plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin, listPlugin], initialView: 'dayGridMonth', headerToolbar: { start: 'drawerToggler,prev,next title', end: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth', }, events: fetchEvents, // ❗ We need this to be true because when its false and event is allDay event and end date is same as start data then Full calendar will set end to null forceEventDuration: true, /* Enable dragging and resizing event Docs: https://fullcalendar.io/docs/editable */ editable: true, /* Enable resizing event from start Docs: https://fullcalendar.io/docs/eventResizableFromStart */ eventResizableFromStart: true, /* Automatically scroll the scroll-containers during event drag-and-drop and date selecting Docs: https://fullcalendar.io/docs/dragScroll */ dragScroll: true, /* Max number of events within a given day Docs: https://fullcalendar.io/docs/dayMaxEvents */ dayMaxEvents: 2, /* Determines if day names and week names are clickable Docs: https://fullcalendar.io/docs/navLinks */ navLinks: true, eventClassNames({ event: calendarEvent }) { const colorName = calendarsColor[calendarEvent._def.extendedProps.calendar] return [ // Background Color `bg-light-${colorName} text-${colorName}`, ] }, eventClick({ event: clickedEvent, jsEvent }) { // Prevent the default action jsEvent.preventDefault() if (clickedEvent.url) { // Open the URL in a new tab window.open(clickedEvent.url, '_blank') } // * Only grab required field otherwise it goes in infinity loop // ! Always grab all fields rendered by form (even if it get `undefined`) otherwise due to Vue3/Composition API you might get: "object is not extensible" event.value = extractEventDataFromEventApi(clickedEvent) isEventHandlerSidebarActive.value = true }, // customButtons dateClick(info) { event.value = { ...event.value, start: info.date } isEventHandlerSidebarActive.value = true }, /* Handle event drop (Also include dragged event) Docs: https://fullcalendar.io/docs/eventDrop We can use `eventDragStop` but it doesn't return updated event so we have to use `eventDrop` which returns updated event */ eventDrop({ event: droppedEvent }) { updateEvent(extractEventDataFromEventApi(droppedEvent)) }, /* Handle event resize Docs: https://fullcalendar.io/docs/eventResize */ eventResize({ event: resizedEvent }) { if (resizedEvent.start && resizedEvent.end) updateEvent(extractEventDataFromEventApi(resizedEvent)) }, customButtons: { drawerToggler: { text: 'calendarDrawerToggler', click() { isLeftSidebarOpen.value = true }, }, }, } // 👉 onMounted onMounted(() => { calendarApi.value = refCalendar.value.getApi() }) // 👉 Jump to date on sidebar(inline) calendar change const jumpToDate = currentDate => { calendarApi.value?.gotoDate(new Date(currentDate)) } watch(() => configStore.isAppRTL, val => { calendarApi.value?.setOption('direction', val ? 'rtl' : 'ltr') }, { immediate: true }) return { refCalendar, calendarOptions, refetchEvents, fetchEvents, addEvent, updateEvent, removeEvent, jumpToDate, } }