fullcalendar adds 1 more day to my date selection

123 Views Asked by At

I'm stuck on a calendar problem. I use FullCalendar to select a range of dates. Example when I select from 11/20/23 to 11/22/23.

Fullcalendar adds 1 more day (see screen).

fullcalendar

this is the result when I selected 20 to 22.

I don't understand where the problem comes from.

Here is the code I already made.

var CalendarProduct = function () {
    var calendarUnvailable;
    var data = {
        id: '',
        eventName: '',
        startDate: '',
        endDate: '',
        allDay: false
    };

    // Add event variables
    var eventName;
    var eventDescription;
    var eventLocation;
    var startDatepicker;
    var startFlatpickr;
    var endDatepicker;
    var endFlatpickr;
    var startTimepicker;
    var startTimeFlatpickr;
    var endTimepicker
    var endTimeFlatpickr;
    var modal;
    var modalTitle;
    var form;
    var validator;
    var addButton;
    var submitButton;
    var cancelButton;
    var closeButton;

    var viewEventName;
    var viewAllDay;
    var viewEventDescription;
    var viewEventLocation;
    var viewStartDate;
    var viewEndDate;
    var viewModal;
    var viewEditButton;
    var viewDeleteButton;

    var initCalendarApp = function () {
        moment.locale('fr');
        let calendarElt = document.getElementById('date_unvailable');
        var todayDate = moment().startOf('day');
        var TODAY = todayDate.format('YYYY-MM-DD');

        calendarUnvailable = new FullCalendar.Calendar(calendarElt, {
            plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
            events: JSON.parse(unvailable),
            initialView: 'dayGridWeek',
            headerToolbar: {
                left: 'prev,next today',
                center: 'title',
                right: 'dayGridMonth,timeGridWeek,timeGridDay'
            },
            locales: [frLocale],
            locale: 'fr',
            initialDate: TODAY,
            themeSystem: 'bootstrap',
            navLinks: true,
            selectable: true,
            selectMirror: true,
            dayHeaderContent: (args) => {
                return moment(args.date).format('dddd')
            },
            eventResize: function (info) {
                console.log(info);
            },

            // Select dates action --- more info: https://fullcalendar.io/docs/select-callback
            select: function (arg) {
                arg.allDay = false;
                formatArgs(arg);
                handleNewEvent();
            },
            eventDrop: function (info) {
                $.post(calendarUnvailableEditRoute, {
                    'datas': {
                        title: info.event.title,
                        id: info.event.id,
                        start: info.event.start.toISOString(),
                        end: info.event.end.toISOString()
                    }
                })
                    .done(function () {
                        Swal.fire(
                            "Enregistré !",
                            "Date déplacée",
                            "success"
                        )
                    })
                    .fail(function () {
                        Swal.fire(
                            "Internal Error",
                            "Votre date n'a pas été déplacée, contacter un administrateur",
                            "error"
                        )
                    })
            },
            eventClick: function (arg) {
                formatArgs({
                    id: arg.event.id,
                    title: arg.event.title,
                    startStr: arg.event.start,
                    endStr: arg.event.end,
                    allDay: arg.event.allDay
                });

                handleViewEvent();
            },

            editable: true,
            dayMaxEvents: true
        });
        calendarUnvailable.render();
    }

    const handleNewEvent = () => {
        // Update modal title
        modalTitle.innerText = "Ajouter une indisponibilité";

        modal.show();

        // Select datepicker wrapper elements
        const datepickerWrappers = form.querySelectorAll('[data-kt-calendar="datepicker"]');

        // Handle all day toggle
        const allDayToggle = form.querySelector('#kt_calendar_datepicker_allday');
        allDayToggle.addEventListener('click', e => {
            if (e.target.checked) {
                datepickerWrappers.forEach(dw => {
                    dw.classList.add('d-none');
                });
            } else {
                endFlatpickr.setDate(data.endDate, true, 'Y-m-d H:i');
                datepickerWrappers.forEach(dw => {
                    dw.classList.remove('d-none');
                });
            }
        });

        populateForm(data);

        // Handle submit form
        submitButton.addEventListener('click', function (e) {
            // Prevent default button action
            e.preventDefault();

            // Validate form before submit
            if (validator) {
                validator.validate().then(function (status) {
                    if (status == 'Valid') {
                        // Show loading indication
                        submitButton.setAttribute('data-kt-indicator', 'on');

                        // Disable submit button whilst loading
                        submitButton.disabled = true;

                        // Simulate form submission
                        setTimeout(function () {
                            // Simulate form submission
                            submitButton.removeAttribute('data-kt-indicator');

                            // Show popup confirmation
                            Swal.fire({
                                text: "Nouvelle disponibilité ajouté avec succès !",
                                icon: "success",
                                buttonsStyling: false,
                                confirmButtonText: "D'accord, j'ai compris !",
                                customClass: {
                                    confirmButton: "btn btn-primary"
                                }
                            }).then(function (result) {
                                if (result.isConfirmed) {
                                    modal.hide();

                                    // Enable submit button after loading
                                    submitButton.disabled = false;

                                    // Detect if is all day event
                                    let allDayEvent = false;
                                    if (allDayToggle.checked) {
                                        allDayEvent = true;
                                    }
                                    if (startTimeFlatpickr.selectedDates.length === 0) {
                                        allDayEvent = true;
                                    }
                                    // Merge date & time
                                    var startDateTime = moment(startFlatpickr.selectedDates[0]).format();
                                    var endDateTime = moment(endFlatpickr.selectedDates[endFlatpickr.selectedDates.length - 1]).format();
                                    if (!allDayEvent) {
                                        const startDate = moment(startFlatpickr.selectedDates[0]).format('YYYY-MM-DD');
                                        const endDate = startDate;
                                        const startTime = moment(startTimeFlatpickr.selectedDates[0]).format('HH:mm:ss');
                                        const endTime = moment(endTimeFlatpickr.selectedDates[0]).format('HH:mm:ss');

                                        startDateTime = startDate + 'T' + startTime;
                                        endDateTime = endDate + 'T' + endTime;
                                    }
                                    // Add new event to calendar
                                    calendarUnvailable.addEvent({
                                        id: uid(),
                                        title: eventName.value,
                                        start: startDateTime,
                                        end: endDateTime,
                                        className: 'fc-event-success',
                                        allDay: allDayEvent
                                    });
                                    calendarUnvailable.render();

                                    // Save in database
                                    $.post(calendarUnvailableAddRoute, {
                                        'datas': {
                                            title: eventName.value,
                                            start: startDateTime,
                                            end: endDateTime,
                                            allDay: allDayEvent
                                        }
                                    })
                                        .fail(function () {
                                            Swal.fire(
                                                "Internal Error",
                                                "Votre disponibilité n'a pas été enregistré, contacter un administrateur",
                                                "error"
                                            )
                                        })
                                    // Reset form for demo purposes only
                                    form.reset();
                                }
                            });

                            //form.submit(); // Submit form
                        }, 2000);
                    } else {
                        // Show popup warning
                        Swal.fire({
                            text: "Désolé, il semble qu'il y ait des erreurs détectées, veuillez réessayer.",
                            icon: "error",
                            buttonsStyling: false,
                            confirmButtonText: "D'accord, j'ai compris !",
                            customClass: {
                                confirmButton: "btn btn-primary"
                            }
                        });
                    }
                });
            }
        });
    }

    const initDatepickers = () => {
        startFlatpickr = flatpickr(startDatepicker, {
            enableTime: false,
            dateFormat: "d/m/Y",
            locale: French,
            static: true
        });

        endFlatpickr = flatpickr(endDatepicker, {
            enableTime: false,
            dateFormat: "d/m/Y",
            locale: French,
            static: true
        });

        startTimeFlatpickr = flatpickr(startTimepicker, {
            enableTime: true,
            noCalendar: true,
            dateFormat: "H:i",
            locale: French,
            static: true
        });

        endTimeFlatpickr = flatpickr(endTimepicker, {
            enableTime: true,
            noCalendar: true,
            dateFormat: "H:i",
            locale: French,
            static: true
        });
    }

    // Init validator
    const initValidator = () => {
        // Init form validation rules. For more info check the FormValidation plugin's official documentation:https://formvalidation.io/
        validator = FormValidation.formValidation(
            form,
            {
                fields: {
                    'calendar_event_name': {
                        validators: {
                            notEmpty: {
                                message: 'Le titre est requis'
                            }
                        }
                    },
                    'calendar_event_start_date': {
                        validators: {
                            notEmpty: {
                                message: 'La date de début est requise'
                            }
                        }
                    },
                    'calendar_event_end_date': {
                        validators: {
                            notEmpty: {
                                message: 'La date de fin est requise'
                            }
                        }
                    }
                },
                plugins: {
                    trigger: new FormValidation.plugins.Trigger(),
                    bootstrap: new FormValidation.plugins.Bootstrap5({
                        rowSelector: '.fv-row',
                        eleInvalidClass: '',
                        eleValidClass: ''
                    })
                },
            }
        );
    }

    // Handle add button
    const handleAddButton = () => {
        addButton.addEventListener('click', e => {
            // Reset form data
            data = {
                id: '',
                startDate: new Date(),
                endDate: new Date(),
                allDay: false
            };
            handleNewEvent();
        });
    }

    const resetFormValidator = (element) => {
        // Target modal hidden event --- For more info: https://getbootstrap.com/docs/5.0/components/modal/#events
        element.addEventListener('hidden.bs.modal', e => {
            if (validator) {
                // Reset form validator. For more info: https://formvalidation.io/guide/api/reset-form
                validator.resetForm(true);
            }
        });
    }

    // Populate form
    const populateForm = () => {
        eventName.value = data.title ? data.title : '';
        startFlatpickr.setDate(data.startDate, true, 'Y-m-d');

        // Handle null end dates
        const endDate = data.endDate ? data.endDate : moment(data.startDate).format();
        endFlatpickr.setDate(endDate, true, 'Y-m-d');

        const allDayToggle = form.querySelector('#kt_calendar_datepicker_allday');
        const datepickerWrappers = form.querySelectorAll('[data-kt-calendar="datepicker"]');
        if (data.allDay) {
            allDayToggle.checked = true;
            datepickerWrappers.forEach(dw => {
                dw.classList.add('d-none');
            });
        } else {
            startTimeFlatpickr.setDate(data.startDate, true, 'Y-m-d H:i');
            endTimeFlatpickr.setDate(data.endDate, true, 'Y-m-d H:i');
            endFlatpickr.setDate(data.endDate, true, 'Y-m-d');
            allDayToggle.checked = false;
            datepickerWrappers.forEach(dw => {
                dw.classList.remove('d-none');
            });
        }
    }

    const formatArgs = (res) => {
        console.log(res);
        data.id = res.id;
        data.title = res.title;
        data.startDate = res.startStr;
        data.endDate = res.endStr;
        data.allDay = res.allDay;
    }

    const uid = () => {
        return Date.now().toString() + Math.floor(Math.random() * 1000).toString();
    }

    return {
        init: function () {
            const element = document.getElementById('kt_modal_add_event');
            if (element != undefined) {
                form = element.querySelector('#kt_modal_add_event_form');
                eventName = form.querySelector('[name="calendar_event_name"]');
                eventDescription = form.querySelector('[name="calendar_event_description"]');
                eventLocation = form.querySelector('[name="calendar_event_location"]');
                startDatepicker = form.querySelector('#kt_calendar_datepicker_start_date');
                endDatepicker = form.querySelector('#kt_calendar_datepicker_end_date');
                startTimepicker = form.querySelector('#kt_calendar_datepicker_start_time');
                endTimepicker = form.querySelector('#kt_calendar_datepicker_end_time');
                addButton = document.querySelector('[data-kt-calendar="add"]');
                submitButton = form.querySelector('#kt_modal_add_event_submit');
                cancelButton = form.querySelector('#kt_modal_add_event_cancel');
                closeButton = element.querySelector('#kt_modal_add_event_close');
                modalTitle = form.querySelector('[data-kt-calendar="title"]');
                modal = new bootstrap.Modal(element);

                const viewElement = document.getElementById('kt_modal_view_event');
                viewModal = new bootstrap.Modal(viewElement);
                viewEventName = viewElement.querySelector('[data-kt-calendar="event_name"]');
                viewAllDay = viewElement.querySelector('[data-kt-calendar="all_day"]');
                viewEventDescription = viewElement.querySelector('[data-kt-calendar="event_description"]');
                viewEventLocation = viewElement.querySelector('[data-kt-calendar="event_location"]');
                viewStartDate = viewElement.querySelector('[data-kt-calendar="event_start_date"]');
                viewEndDate = viewElement.querySelector('[data-kt-calendar="event_end_date"]');
                viewEditButton = viewElement.querySelector('#kt_modal_view_event_edit');
                viewDeleteButton = viewElement.querySelector('#kt_modal_view_event_delete');

                initCalendarApp();
                initValidator();
                initDatepickers();
                handleAddButton();
                resetFormValidator(element);
            }
        }
    }
}();

Exemple:

enter image description here

Do you have any idea where this could come from? Thanks for your help.

1

There are 1 best solutions below

2
On

As per the documentation of the "select" callback, fullcalendar treats end dates as exclusive. The end date you see is the first moment after the event finishes.

Therefore if you select the entire day of the 22nd, the first moment after the end of the 22nd is midnight on the 23rd.

For ease of reference, here is the relevant quote from the documentation of the "select" callback:

end: Date. A date indicating the end of the selection.

In line with the discussion about the Event object, it is important to stress that the end date property is exclusive. For example, if the selection is all-day and the last day is a Thursday, end will be Friday.