easyappointments/assets/js/backend_calendar_table_view.js

1687 lines
65 KiB
JavaScript
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ----------------------------------------------------------------------------
* Easy!Appointments - Open Source Web Scheduler
*
* @package EasyAppointments
* @author A.Tselegidis <alextselegidis@gmail.com>
* @copyright Copyright (c) 2013 - 2016, Alex Tselegidis
* @license http://opensource.org/licenses/GPL-3.0 - GPLv3
* @link http://easyappointments.org
* @since v1.2.0
* ---------------------------------------------------------------------------- */
window.BackendCalendarTableView = window.BackendCalendarTableView || {};
/**
* Backend Calendar
*
* This module implements the table calendar view of backend.
*
* @module BackendCalendarTableView
*/
(function (exports) {
'use strict';
var $filterProvider;
var $filterService;
var lastFocusedEventData;
/**
* Bind page event handlers.
*/
function bindEventHandlers() {
var $calendarToolbar = $('#calendar-toolbar');
var $calendar = $('#calendar');
$calendar.on('click', '.calendar-header .btn.previous', function () {
var dayInterval = $('#select-filter-item').val();
var currentDate = $('.select-date').datepicker('getDate');
var startDate = moment(currentDate).subtract(1, 'days');
var endDate = startDate.clone().add(dayInterval - 1, 'days');
$('.select-date').datepicker('setDate', startDate.toDate());
createView(startDate.toDate(), endDate.toDate());
});
$calendar.on('click', '.calendar-header .btn.next', function () {
var dayInterval = $('#select-filter-item').val();
var currentDate = $('.select-date').datepicker('getDate');
var startDate = moment(currentDate).add(1, 'days');
var endDate = startDate.clone().add(dayInterval - 1, 'days');
$('.select-date').datepicker('setDate', startDate.toDate());
createView(startDate.toDate(), endDate.toDate());
});
$calendarToolbar.on('change', '#select-filter-item', function () {
var dayInterval = $('#select-filter-item').val();
var currentDate = $('.select-date').datepicker('getDate');
var startDate = moment(currentDate);
var endDate = startDate.clone().add(dayInterval - 1, 'days');
createView(startDate.toDate(), endDate.toDate());
});
$calendarToolbar.on('click', '#reload-appointments', function () {
// Remove all the events from the tables.
$('.calendar-view .event').remove();
// Fetch the events and place them in the existing HTML format.
var dayInterval = $('#select-filter-item').val();
var currentDate = $('.select-date').datepicker('getDate');
var startDateMoment = moment(currentDate);
var startDate = startDateMoment.toDate();
var endDateMoment = startDateMoment.clone().add(dayInterval - 1, 'days');
var endDate = endDateMoment.toDate();
getCalendarEvents(startDate, endDate)
.done(function () {
var currentDate = startDate;
while (currentDate <= endDate) {
$('.calendar-view .date-column').each(function (index, dateColumn) {
var $dateColumn = $(dateColumn);
var date = new Date($dateColumn.data('date'));
if (currentDate.getTime() !== date.getTime()) {
return true;
}
$dateColumn.find('.date-column-title').text(GeneralFunctions.formatDate(date, GlobalVariables.dateFormat));
$dateColumn.find('.provider-column').each(function (index, providerColumn) {
var $providerColumn = $(providerColumn);
var provider = $providerColumn.data('provider');
$providerColumn.find('.calendar-wrapper').fullCalendar('removeEvents');
createNonWorkingHours($providerColumn.find('.calendar-wrapper'), JSON.parse($providerColumn.data('provider').settings.working_plan));
// Add the appointments to the column.
createAppointments($providerColumn, response.appointments);
// Add the unavailabilities to the column.
createUnavailabilities($providerColumn, response.unavailabilities);
// Add the provider breaks to the column.
var workingPlan = JSON.parse(provider.settings.working_plan);
var day = date.toString('dddd').toLowerCase();
if (workingPlan[day]) {
var breaks = workingPlan[day].breaks;
createBreaks($providerColumn, breaks);
}
});
});
currentDate.add({days: 1});
}
// setCalendarViewSize();
Backend.placeFooterToBottom();
})
.fail(GeneralFunctions.ajaxFailureHandler);
});
/**
* Event: On Window Resize
*/
$(window).on('resize', function () {
setCalendarViewSize();
});
/**
* Event: Popover Close Button "Click"
*
* Hides the open popover element.
*/
$calendar.on('click', '.close-popover', function () {
$(this).parents('.popover').popover('dispose');
});
/**
* Event: Popover Edit Button "Click"
*
* Enables the edit dialog of the selected table event.
*/
$calendar.on('click', '.edit-popover', function () {
$(this).parents('.popover').popover('dispose');
var $dialog;
if (lastFocusedEventData.data.is_unavailable === '0') {
var appointment = lastFocusedEventData.data;
$dialog = $('#manage-appointment');
BackendCalendarAppointmentsModal.resetAppointmentDialog();
// Apply appointment data and show modal dialog.
$dialog.find('.modal-header h3').text(EALang.edit_appointment_title);
$dialog.find('#appointment-id').val(appointment.id);
$dialog.find('#select-service').val(appointment.id_services).trigger('change');
$dialog.find('#select-provider').val(appointment.id_users_provider);
// Set the start and end datetime of the appointment.
var startDatetime = Date.parseExact(appointment.start_datetime, 'yyyy-MM-dd HH:mm:ss');
$dialog.find('#start-datetime').datetimepicker('setDate', startDatetime);
var endDatetime = Date.parseExact(appointment.end_datetime, 'yyyy-MM-dd HH:mm:ss');
$dialog.find('#end-datetime').datetimepicker('setDate', endDatetime);
var customer = appointment.customer;
$dialog.find('#customer-id').val(appointment.id_users_customer);
$dialog.find('#first-name').val(customer.first_name);
$dialog.find('#last-name').val(customer.last_name);
$dialog.find('#email').val(customer.email);
$dialog.find('#phone-number').val(customer.phone_number);
$dialog.find('#address').val(customer.address);
$dialog.find('#city').val(customer.city);
$dialog.find('#zip-code').val(customer.zip_code);
$dialog.find('#appointment-location').val(appointment.location);
$dialog.find('#appointment-notes').val(appointment.notes);
$dialog.find('#customer-notes').val(customer.notes);
} else {
var unavailable = lastFocusedEventData.data;
// Replace string date values with actual date objects.
unavailable.start_datetime = lastFocusedEventData.start.format('YYYY-MM-DD HH:mm:ss');
var startDatetime = Date.parseExact(unavailable.start_datetime, 'yyyy-MM-dd HH:mm:ss');
unavailable.end_datetime = lastFocusedEventData.end.format('YYYY-MM-DD HH:mm:ss');
var endDatetime = Date.parseExact(unavailable.end_datetime, 'yyyy-MM-dd HH:mm:ss');
$dialog = $('#manage-unavailable');
BackendCalendarUnavailabilitiesModal.resetUnavailableDialog();
// Apply unavailable data to dialog.
$dialog.find('.modal-header h3').text('Edit Unavailable Period');
$dialog.find('#unavailable-start').datetimepicker('setDate', startDatetime);
$dialog.find('#unavailable-id').val(unavailable.id);
$dialog.find('#unavailable-provider').val(unavailable.id_users_provider);
$dialog.find('#unavailable-end').datetimepicker('setDate', endDatetime);
$dialog.find('#unavailable-notes').val(unavailable.notes);
}
// :: DISPLAY EDIT DIALOG
$dialog.modal('show');
});
/**
* Event: Popover Delete Button "Click"
*
* Displays a prompt on whether the user wants the appointment to be deleted. If he confirms the
* deletion then an ajax call is made to the server and deletes the appointment from the database.
*/
$calendar.on('click', '.delete-popover', function () {
$(this).parents('.popover').popover('dispose'); // Hide the popover.
var url;
var data;
// If id_role parameter exists the popover is an custom availability period.
if (lastFocusedEventData.data.hasOwnProperty('id_roles')) {
// Do not display confirmation prompt.
url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_delete_custom_availability_period';
data = {
csrfToken: GlobalVariables.csrfToken,
custom_availability_period: lastFocusedEventData.start.format('YYYY-MM-DD'),
provider_id: lastFocusedEventData.data.id
};
$.post(url, data)
.done(function () {
$('#message-box').dialog('close');
var customAvailabilityPeriods = jQuery.parseJSON(lastFocusedEventData.data.settings.custom_availability_periods);
delete customAvailabilityPeriods[lastFocusedEventData.start.format('YYYY-MM-DD')];
lastFocusedEventData.data.settings.custom_availability_periods = JSON.stringify(customAvailabilityPeriods);
// Refresh calendar event items.
$('#select-filter-item').trigger('change');
})
.fail(GeneralFunctions.ajaxFailureHandler);
} else if (lastFocusedEventData.data.is_unavailable === '0') {
var buttons = [
{
text: EALang.cancel,
click: function () {
$('#message-box').dialog('close');
}
},
{
text: 'OK',
click: function () {
url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_delete_appointment';
data = {
csrfToken: GlobalVariables.csrfToken,
appointment_id: lastFocusedEventData.data.id,
delete_reason: $('#delete-reason').val()
};
$.post(url, data)
.done(function () {
$('#message-box').dialog('close');
// Refresh calendar event items.
$('#select-filter-item').trigger('change');
})
.fail(GeneralFunctions.ajaxFailureHandler);
}
}
];
GeneralFunctions.displayMessageBox(EALang.delete_appointment_title,
EALang.write_appointment_removal_reason, buttons);
$('<textarea/>', {
'class': 'form-control w-100',
'id': 'delete-reason',
'rows': '3'
})
.appendTo('#message-box');
} else {
// Do not display confirmation prompt.
url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_delete_unavailable';
data = {
csrfToken: GlobalVariables.csrfToken,
unavailable_id: lastFocusedEventData.data.id
};
$.post(url, data)
.done(function () {
$('#message-box').dialog('close');
// Refresh calendar event items.
$('#select-filter-item').trigger('change');
})
.fail(GeneralFunctions.ajaxFailureHandler);
}
});
}
/**
* Create table view header container.
*
* The header contains the date navigation elements (buttons and datepicker).
*/
function createHeader() {
var $calendarFilter = $('#calendar-filter');
$calendarFilter
.find('select')
.empty()
.append(new Option('1 ' + EALang.day, 1))
.append(new Option('3 ' + EALang.days, 3));
var $calendarHeader = $('<div/>', {
'class': 'calendar-header'
})
.appendTo('#calendar');
$('<button/>', {
'class': 'btn btn-xs btn-outline-secondary previous mr-2',
'html': [
$('<span/>', {
'class': 'fas fa-chevron-left'
})
]
})
.appendTo($calendarHeader);
$('<input/>', {
'type': 'text',
'class': 'form-control d-inline-block select-date mr-2',
'value': GeneralFunctions.formatDate(new Date(), GlobalVariables.dateFormat, false)
})
.appendTo($calendarHeader);
$('<button/>', {
'class': 'btn btn-xs btn-outline-secondary next',
'html': [
$('<span/>', {
'class': 'fas fa-chevron-right'
})
]
})
.appendTo($calendarHeader);
var dateFormat;
switch (GlobalVariables.dateFormat) {
case 'DMY':
dateFormat = 'dd/mm/yy';
break;
case 'MDY':
dateFormat = 'mm/dd/yy';
break;
case 'YMD':
dateFormat = 'yy/mm/dd';
break;
default:
throw new Error('Invalid date format setting provided: ' + GlobalVariables.dateFormat);
}
$calendarHeader.find('.select-date').datepicker({
defaultDate: new Date(),
dateFormat: dateFormat,
onSelect: function (dateText, instance) {
var startDate = new Date(instance.currentYear, instance.currentMonth, instance.currentDay);
var endDate = new Date(startDate.getTime()).add({days: parseInt($('#select-filter-item').val()) - 1});
createView(startDate, endDate);
}
});
var providers = GlobalVariables.availableProviders.filter(function (provider) {
return GlobalVariables.user.role_slug === Backend.DB_SLUG_ADMIN
|| (GlobalVariables.user.role_slug === Backend.DB_SLUG_SECRETARY
&& GlobalVariables.secretaryProviders.indexOf(provider.id) !== -1)
|| (GlobalVariables.user.role_slug === Backend.DB_SLUG_PROVIDER
&& Number(provider.id) === Number(GlobalVariables.user.id));
});
// Create providers and service filters.
if (GlobalVariables.user.role_slug !== Backend.DB_SLUG_PROVIDER) {
$('<label/>', {
'text': EALang.provider
})
.appendTo($calendarHeader);
$filterProvider = $('<select/>', {
'id': 'filter-provider',
'multiple': 'multiple',
'on': {
'change': function () {
var startDate = new Date($('.calendar-view .date-column:first').data('date'));
var endDate = new Date(startDate.getTime()).add({days: parseInt($('#select-date').val()) - 1});
createView(startDate, endDate);
}
}
})
.appendTo($calendarHeader);
providers.forEach(function (provider) {
$filterProvider.append(new Option(provider.first_name + ' ' + provider.last_name, provider.id));
});
$filterProvider.select2();
}
var services = GlobalVariables.availableServices.filter(function (service) {
var provider = providers.find(function (provider) {
return provider.services.indexOf(service.id) !== -1;
});
return GlobalVariables.user.role_slug === Backend.DB_SLUG_ADMIN || provider;
});
$('<label/>', {
'text': EALang.service
})
.appendTo($calendarHeader);
$filterService = $('<select/>', {
'id': 'filter-service',
'multiple': 'multiple',
'on': {
'change': function () {
var startDate = new Date($('.calendar-view .date-column:first').data('date'));
var endDate = new Date(startDate.getTime()).add({days: parseInt($('#select-date').val()) - 1});
createView(startDate, endDate);
}
}
})
.appendTo($calendarHeader);
services.forEach(function (service) {
$filterService.append(new Option(service.name, service.id));
});
$filterService.select2();
}
/**
* Create table schedule view.
*
* This method will destroy any previous instances and create a new view for displaying the appointments in
* a table format.
*
* @param {Date} startDate Start date to be displayed.
* @param {Date} endDate End date to be displayed.
*/
function createView(startDate, endDate) {
// Disable date navigation.
$('#calendar .calendar-header .btn')
.addClass('disabled')
.prop('disabled', true);
// Remember provider calendar view mode.
var providerView = {};
$('.provider-column').each(function (index, providerColumn) {
var $providerColumn = $(providerColumn);
var providerId = $providerColumn.data('provider').id;
providerView[providerId] = $providerColumn.find('.calendar-wrapper').fullCalendar('getView').name;
});
$('#calendar .calendar-view').remove();
Backend.placeFooterToBottom();
var $calendarView = $('<div/>', {
'class': 'calendar-view'
})
.appendTo('#calendar');
$calendarView.data({
startDate: startDate.toString('yyyy-MM-dd'),
endDate: endDate.toString('yyyy-MM-dd')
});
var $wrapper = $('<div/>').appendTo($calendarView);
getCalendarEvents(startDate, endDate)
.done(function (response) {
var currentDate = startDate;
while (currentDate <= endDate) {
createDateColumn($wrapper, currentDate, response);
currentDate.add({days: 1});
}
setCalendarViewSize();
Backend.placeFooterToBottom();
// Activate calendar navigation.
$('#calendar .calendar-header .btn').removeClass('disabled').prop('disabled', false);
// Apply provider calendar view mode.
$('.provider-column').each(function (index, providerColumn) {
var $providerColumn = $(providerColumn);
var providerId = $providerColumn.data('provider').id;
$providerColumn.find('.calendar-wrapper')
.fullCalendar('changeView', providerView[providerId] || 'agendaDay');
});
})
.fail(GeneralFunctions.ajaxFailureHandler);
}
/**
* Create Date Column Container
*
* This element will contain the provider columns.
*
* @param {jQuery} $wrapper The wrapper div element of the table view.
* @param {Date} date Selected date for the column.
* @param {Object[]} events Events to be displayed on this date.
*/
function createDateColumn($wrapper, date, events) {
var $dateColumn = $('<div/>', {
'class': 'date-column'
})
.appendTo($wrapper);
$dateColumn.data('date', date.getTime());
$('<h5/>', {
'class': 'date-column-title',
'text': GeneralFunctions.formatDate(date, GlobalVariables.dateFormat)
})
.appendTo($dateColumn);
var filterProviderIds = $filterProvider.val();
var filterServiceIds = $filterService.val();
var providers = GlobalVariables.availableProviders.filter(function (provider) {
var servedServiceIds = provider.services.filter(function (serviceId) {
var matches = filterServiceIds.filter(function (filterServiceId) {
return Number(serviceId) === Number(filterServiceId);
});
return matches.length;
});
return (!filterProviderIds.length && !filterServiceIds.length) || servedServiceIds.length
|| filterProviderIds.indexOf(provider.id) !== -1;
});
if (GlobalVariables.user.role_slug === 'provider') {
GlobalVariables.availableProviders.forEach(function (provider) {
if (Number(provider.id) === Number(GlobalVariables.user.id)) {
providers = [provider];
}
});
}
if (GlobalVariables.user.role_slug === 'secretary') {
providers = [];
GlobalVariables.availableProviders.forEach(function (provider) {
if (GlobalVariables.secretaryProviders.indexOf(provider.id) > -1) {
providers.push(provider)
}
});
}
providers.forEach(function (provider) {
createProviderColumn($dateColumn, date, provider, events);
});
}
/**
* Create Provider Column Container
*
* @param {jQuery} $dateColumn Element to container the provider's column.
* @param {Date} date Selected date for the column.
* @param {Object} provider Contains the provider data.
* @param {Object[]} events Events to be displayed on this date.
*/
function createProviderColumn($dateColumn, date, provider, events) {
if (provider.services.length === 0) {
return;
}
var $providerColumn = $('<div/>', {
'class': 'provider-column'
})
.appendTo($dateColumn);
$providerColumn.data('provider', provider);
// Create calendar.
createCalendar($providerColumn, date, provider);
// Create non working hours.
createNonWorkingHours($providerColumn.find('.calendar-wrapper'), JSON.parse(provider.settings.working_plan))
// Add the appointments to the column.
createAppointments($providerColumn, events.appointments);
// Add the unavailabilities to the column.
createUnavailabilities($providerColumn, events.unavailabilities);
Backend.placeFooterToBottom();
}
/**
* Get Calendar Component Height
*
* This method calculates the proper calendar height, in order to be displayed correctly, even when the
* browser window is resizing.
*
* @return {Number} Returns the calendar element height in pixels.
*/
function getCalendarHeight() {
var result = window.innerHeight - $('#footer').outerHeight() - $('#header').outerHeight()
- 60; // 60 for fine tuning
return result > 500 ? result : 500; // Minimum height is 500px
}
function createCalendar($providerColumn, goToDate, provider) {
var $wrapper = $('<div/>', {
'class': 'calendar-wrapper'
})
.appendTo($providerColumn);
var columnFormat = '';
switch (GlobalVariables.dateFormat) {
case 'DMY':
columnFormat = 'ddd D/M';
break;
case 'MDY':
case 'YMD':
columnFormat = 'ddd M/D';
break;
default:
throw new Error('Invalid date format setting provided!', GlobalVariables.dateFormat);
}
// Time formats
var timeFormat = '';
var slotTimeFormat = '';
switch (GlobalVariables.timeFormat) {
case 'military':
timeFormat = 'H:mm';
slotTimeFormat = 'H(:mm)';
break;
case 'regular':
timeFormat = 'h:mm a';
slotTimeFormat = 'h(:mm) a';
break;
default:
throw new Error('Invalid time format setting provided!' + GlobalVariables.timeFormat);
}
var firstWeekday = GlobalVariables.firstWeekday;
var firstWeekdayNumber = GeneralFunctions.getWeekDayId(firstWeekday);
$wrapper.fullCalendar({
defaultView: 'agendaDay',
height: getCalendarHeight(),
editable: true,
firstDay: firstWeekdayNumber,
timeFormat: timeFormat,
slotLabelFormat: slotTimeFormat,
allDaySlot: true,
columnFormat: columnFormat,
titleFormat: 'MMMM YYYY',
header: {
left: 'listDay,agendaDay',
center: '',
right: ''
},
// Selectable
selectable: true,
selectHelper: true,
select: function (start, end, jsEvent) {
if (!start.hasTime() || !end.hasTime()) {
return;
}
$('#insert-appointment').trigger('click');
// Preselect service & provider.
var $providerColumn = $(jsEvent.target).parents('.provider-column');
var providerId = $providerColumn.data('provider').id;
var provider = GlobalVariables.availableProviders.find(function (provider) {
return Number(provider.id) === Number(providerId);
});
var service = GlobalVariables.availableServices.find(function (service) {
return provider.services.indexOf(service.id) !== -1
});
$('#select-service').val(service.id).trigger('change');
$('#select-provider').val(provider.id).trigger('change');
// Preselect time
$('#start-datetime').datepicker('setDate', new Date(start.format('YYY/MM/DD HH:mm:ss')));
$('#end-datetime').datepicker('setDate', new Date(end.format('YYYY/MM/DD HH:mm:ss')));
return false;
},
// Translations
monthNames: [EALang.january, EALang.february, EALang.march, EALang.april,
EALang.may, EALang.june, EALang.july, EALang.august,
EALang.september, EALang.october, EALang.november,
EALang.december],
monthNamesShort: [EALang.january.substr(0, 3), EALang.february.substr(0, 3),
EALang.march.substr(0, 3), EALang.april.substr(0, 3),
EALang.may.substr(0, 3), EALang.june.substr(0, 3),
EALang.july.substr(0, 3), EALang.august.substr(0, 3),
EALang.september.substr(0, 3), EALang.october.substr(0, 3),
EALang.november.substr(0, 3), EALang.december.substr(0, 3)],
dayNames: [EALang.sunday, EALang.monday, EALang.tuesday, EALang.wednesday,
EALang.thursday, EALang.friday, EALang.saturday],
dayNamesShort: [EALang.sunday.substr(0, 3), EALang.monday.substr(0, 3),
EALang.tuesday.substr(0, 3), EALang.wednesday.substr(0, 3),
EALang.thursday.substr(0, 3), EALang.friday.substr(0, 3),
EALang.saturday.substr(0, 3)],
dayNamesMin: [EALang.sunday.substr(0, 2), EALang.monday.substr(0, 2),
EALang.tuesday.substr(0, 2), EALang.wednesday.substr(0, 2),
EALang.thursday.substr(0, 2), EALang.friday.substr(0, 2),
EALang.saturday.substr(0, 2)],
buttonText: {
today: EALang.today,
day: EALang.day,
week: EALang.week,
month: EALang.month,
agendaDay: EALang.calendar,
listDay: EALang.list,
},
// Calendar events need to be declared on initialization.
eventClick: onEventClick,
eventResize: onEventResize,
eventDrop: onEventDrop,
dayClick: onDayClick,
viewRender: onViewRender
});
$wrapper.fullCalendar('gotoDate', moment(goToDate));
$('<h6/>', {
'text': provider.first_name + ' ' + provider.last_name
})
.prependTo($providerColumn);
}
/**
* Calendar Day "Click" Callback
*
* When the user clicks on a day square on the calendar, then he will automatically be transferred to that
* day view calendar.
*/
function onDayClick(date) {
$('#insert-appointment').trigger('click');
var $dialog = $('#manage-appointment');
var providerId = $(this).closest('.provider-column').data('provider').id;
var provider = GlobalVariables.availableProviders.find(function (provider) {
return Number(provider.id) === Number(providerId);
});
if (!provider) {
return;
}
var service = GlobalVariables.availableServices.find(function (service) {
return provider.services.indexOf(service.id) !== -1;
});
if (provider.services.length) {
$dialog.find('#select-service').val([provider.services[0]]).trigger('change');
$dialog.find('#select-provider').val(provider.id);
$dialog.find('#start-datetime').val(GeneralFunctions.formatDate(date, GlobalVariables.dateFormat, true));
$dialog.find('#start-datetime').datepicker('setDate', date);
$dialog.find('#end-datetime').val(GeneralFunctions.formatDate(date.addMinutes(service.duration),
GlobalVariables.dateFormat, true));
$dialog.find('#end-datetime').datepicker('setDate', date);
}
}
function onViewRender(view, element) {
$(element).fullCalendar('option', 'height', getCalendarHeight());
}
function createNonWorkingHours($calendar, workingPlan) {
var view = $calendar.fullCalendar('getView');
var start = view.start.clone();
var end = view.end.clone();
var selDayName = start.toDate().toString('dddd').toLowerCase();
if (workingPlan[selDayName] === null) {
var nonWorkingDay = {
title: EALang.not_working,
start: start,
end: end,
allDay: false,
color: '#BEBEBE',
editable: false,
className: 'fc-unavailable'
};
$calendar.fullCalendar('renderEvent', nonWorkingDay, true);
return;
}
var workDateStart = moment(start.toDate().toString('yyyy-MM-dd') + ' ' + workingPlan[selDayName].start);
if (start < workDateStart) {
unavailablePeriod = {
title: EALang.not_working,
start: start,
end: workDateStart,
allDay: false,
color: '#BEBEBE',
editable: false,
className: 'fc-unavailable'
};
$calendar.fullCalendar('renderEvent', unavailablePeriod, false);
}
// Add unavailable period after work ends.
var workDateEnd = moment(start.toDate().toString('yyyy-MM-dd') + ' ' + workingPlan[selDayName].end);
if (end > workDateEnd) {
var unavailablePeriod = {
title: EALang.not_working,
start: workDateEnd,
end: end,
allDay: false,
color: '#BEBEBE',
editable: false,
className: 'fc-unavailable'
};
$calendar.fullCalendar('renderEvent', unavailablePeriod, false);
}
// Add unavailable periods for breaks.
var breakStart;
var breakEnd;
workingPlan[selDayName].breaks.forEach(function (currentBreak) {
breakStart = moment(start.toDate().toString('yyyy-MM-dd') + ' ' + currentBreak.start);
breakEnd = moment(start.toDate().toString('yyyy-MM-dd') + ' ' + currentBreak.end);
var unavailablePeriod = {
title: EALang.break,
start: breakStart,
end: breakEnd,
allDay: false,
color: '#BEBEBE',
editable: false,
className: 'fc-unavailable fc-break'
};
$calendar.fullCalendar('renderEvent', unavailablePeriod, false);
});
}
/**
* Create Appointment Events
*
* This method will add the appointment events on the table view.
*
* @param {jQuery} $providerColumn The provider column container.
* @param {Object[]} appointments Contains the appointment events data.
*/
function createAppointments($providerColumn, appointments) {
if (appointments.length === 0) {
return;
}
var filterServiceIds = $filterService.val();
appointments = appointments.filter(function (appointment) {
return !filterServiceIds.length || filterServiceIds.indexOf(appointment.id_services) !== -1;
});
var calendarEvents = [];
for (var index in appointments) {
var appointment = appointments[index];
if (appointment.id_users_provider !== $providerColumn.data('provider').id) {
continue;
}
calendarEvents.push({
id: appointment.id,
title: appointment.service.name + ' - '
+ appointment.customer.first_name + ' '
+ appointment.customer.last_name,
start: moment(appointment.start_datetime),
end: moment(appointment.end_datetime),
allDay: false,
data: appointment // Store appointment data for later use.
});
}
$providerColumn.find('.calendar-wrapper').fullCalendar('addEventSource', calendarEvents);
}
/**
* Create Unavailabilities Events
*
* This method will add the unavailability events on the table view.
*
* @param {jQuery} $providerColumn The provider column container.
* @param {Object[]} unavailabilities Contains the unavailability events data.
*/
function createUnavailabilities($providerColumn, unavailabilities) {
if (unavailabilities.length === 0) {
return;
}
for (var index in unavailabilities) {
var unavailability = unavailabilities[index];
if (unavailability.id_users_provider !== $providerColumn.data('provider').id) {
continue;
}
var event = {
title: EALang.unavailable,
start: moment(unavailability.start_datetime),
end: moment(unavailability.end_datetime),
allDay: false,
color: '#879DB4',
editable: true,
className: 'fc-unavailable fc-custom',
data: unavailability
};
$providerColumn.find('.calendar-wrapper').fullCalendar('renderEvent', event, false);
}
}
/**
* Create break events in the table view.
*
* @param {jQuery} $providerColumn The provider column container.
* @param {Object[]} breaks Contains the break events data.
*/
function createBreaks($providerColumn, breaks) {
if (breaks.length === 0) {
return;
}
var currentDate = new Date($providerColumn.parents('.date-column').data('date'));
var $tbody = $providerColumn.find('table tbody');
for (var index in breaks) {
var entry = breaks[index];
var startHour = entry.start.split(':');
var eventDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate(), startHour[0], startHour[1]);
var endHour = entry.end.split(':');
var endDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate(), endHour[0], endHour[1]);
var eventDuration = Math.round((endDate - eventDate) / 60000);
var $event = $('<div/>', {
'class': 'event unavailability break'
});
$event.html(
EALang.break +
' <span class="hour">' + eventDate.toString('HH:mm') + '</span> (' + eventDuration + '\')');
$event.data(entry);
$tbody.find('tr').each(function (index, tr) {
var $td = $(tr).find('td:first');
var cellDate = new Date(currentDate.getTime()).set({
hour: parseInt($td.text().split(':')[0]),
minute: parseInt($td.text().split(':')[1])
});
if (eventDate < cellDate) {
// Remove the hour from the event if it is the same as the row.
if (eventDate.toString('HH:mm') === $(tr).prev().find('td').eq(0).text()) {
$event.find('.hour').remove();
}
$(tr).prev().find('td:gt(0)').each(function (index, td) {
$event.clone().appendTo($(td));
});
return false;
}
});
}
}
/**
* Get the event notes for the popup widget.
*
* @param {Event} event
*/
function getEventNotes(event) {
if (!event.data || !event.data.notes) {
return '-';
}
var notes = event.data.notes;
return notes.length > 100 ? notes.substring(0, 100) + '...' : notes;
}
/**
* Calendar Event "Click" Callback
*
* When the user clicks on an appointment object on the calendar, then a data preview popover is display
* above the calendar item.
*/
function onEventClick(event, jsEvent) {
$('.popover').popover('dispose'); // Close all open popovers.
var $html;
var displayEdit;
var displayDelete;
// Depending where the user clicked the event (title or empty space) we
// need to use different selectors to reach the parent element.
var $parent = $(jsEvent.target.offsetParent);
var $altParent = $(jsEvent.target).parents().eq(1);
if ($(this).hasClass('fc-unavailable') || $parent.hasClass('fc-unavailable') || $altParent.hasClass('fc-unavailable')) {
displayEdit = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
&& GlobalVariables.user.privileges.appointments.edit === true)
? 'mr-2' : 'd-none';
displayDelete = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
&& GlobalVariables.user.privileges.appointments.delete === true)
? 'mr-2' : 'd-none'; // Same value at the time.
$html = $('<div/>', {
'html': [
$('<strong/>', {
'text': EALang.start
}),
$('<span/>', {
'text': GeneralFunctions.formatDate(event.start.format('YYYY-MM-DD HH:mm:ss'), GlobalVariables.dateFormat, true)
}),
$('<br/>'),
$('<strong/>', {
'text': EALang.end
}),
$('<span/>', {
'text': GeneralFunctions.formatDate(event.end.format('YYYY-MM-DD HH:mm:ss'), GlobalVariables.dateFormat, true)
}),
$('<br/>'),
$('<strong/>', {
'text': EALang.notes
}),
$('<span/>', {
'text': getEventNotes(event)
}),
$('<br/>'),
$('<hr/>'),
$('<div/>', {
'class': 'd-flex justify-content-between',
'html': [
$('<button/>', {
'class': 'edit-popover btn btn-primary ' + displayEdit,
'html': [
$('<i/>', {
'class': 'far fa-edit mr-2'
}),
$('<span/>', {
'text': EALang.edit
})
]
}),
$('<button/>', {
'class': 'delete-popover btn btn-danger ' + displayDelete,
'html': [
$('<i/>', {
'class': 'far fa-trash-alt mr-2'
}),
$('<span/>', {
'text': EALang.delete
})
]
}),
$('<button/>', {
'class': 'close-popover btn btn-outline-secondary',
'html': [
$('<i/>', {
'class': 'fas fa-ban mr-2'
}),
$('<span/>', {
'text': EALang.close
})
]
})
]
})
]
});
} else if ($(this).hasClass('fc-custom-availability-period') || $parent.hasClass('fc-custom-availability-period') || $altParent.hasClass('fc-custom-availability-period')) {
displayEdit = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
&& GlobalVariables.user.privileges.appointments.edit === true)
? 'mr-2' : 'd-none'; // Same value at the time.
displayDelete = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
&& GlobalVariables.user.privileges.appointments.delete === true)
? 'mr-2' : 'd-none'; // Same value at the time.
$html = $('<div/>', {
'html': [
$('<strong/>', {
'text': EALang.provider
}),
$('<span/>', {
'text': event.data ? event.data.first_name + ' ' + event.data.last_name : '-'
}),
$('<br/>'),
$('<strong/>', {
'text': EALang.start
}),
$('<span/>', {
'text': GeneralFunctions.formatDate(event.start.format('YYYY-MM-DD HH:mm:ss'), GlobalVariables.dateFormat, true)
}),
$('<br/>'),
$('<strong/>', {
'text': EALang.end
}),
$('<span/>', {
'text': GeneralFunctions.formatDate(event.end.format('YYYY-MM-DD HH:mm:ss'), GlobalVariables.dateFormat, true)
}),
$('<br/>'),
$('<strong/>', {
'text': EALang.timezone
}),
$('<span/>', {
'text': GlobalVariables.timezones[event.data.provider.timezone]
}),
$('<br/>'),
$('<hr/>'),
$('<div/>', {
'class': 'd-flex justify-content-center',
'html': [
$('<button/>', {
'class': 'edit-popover btn btn-danger ' + displayEdit,
'html': [
$('<i/>', {
'class': 'far fa-edit mr-2'
}),
$('<span/>', {
'text': EALang.edit
})
]
}),
$('<button/>', {
'class': 'delete-popover btn btn-danger ' + displayDelete,
'html': [
$('<i/>', {
'class': 'far fa-trash-alt mr-2'
}),
$('<span/>', {
'text': EALang.delete
})
]
}),
$('<button/>', {
'class': 'close-popover btn btn-outline-secondary',
'html': [
$('<i/>', {
'class': 'fas fa-ban mr-2'
}),
$('<span/>', {
'text': EALang.close
})
]
})
]
})
]
});
} else {
displayEdit = (GlobalVariables.user.privileges.appointments.edit === true)
? 'mr-2' : 'd-none';
displayDelete = (GlobalVariables.user.privileges.appointments.delete === true)
? 'mr-2' : 'd-none';
$html = $('<div/>', {
'html': [
$('<strong/>', {
'text': EALang.start
}),
$('<span/>', {
'text': GeneralFunctions.formatDate(event.start.format('YYYY-MM-DD HH:mm:ss'), GlobalVariables.dateFormat, true)
}),
$('<br/>'),
$('<strong/>', {
'text': EALang.end
}),
$('<span/>', {
'text': GeneralFunctions.formatDate(event.end.format('YYYY-MM-DD HH:mm:ss'), GlobalVariables.dateFormat, true)
}),
$('<br/>'),
$('<strong/>', {
'text': EALang.timezone
}),
$('<span/>', {
'text': GlobalVariables.timezones[event.data.provider.timezone]
}),
$('<br/>'),
$('<strong/>', {
'text': EALang.service
}),
$('<span/>', {
'text': event.data.service.name
}),
$('<br/>'),
$('<strong/>', {
'text': EALang.provider
}),
GeneralFunctions.renderMapIcon(event.data.provider),
$('<span/>', {
'text': event.data.provider.first_name + ' ' + event.data.provider.last_name
}),
$('<br/>'),
$('<strong/>', {
'text': EALang.customer
}),
GeneralFunctions.renderMapIcon(event.data.customer),
$('<span/>', {
'text': event.data.customer.first_name + ' ' + event.data.customer.last_name
}),
$('<br/>'),
$('<strong/>', {
'text': EALang.email
}),
GeneralFunctions.renderMailIcon(event.data.customer.email),
$('<span/>', {
'text': event.data.customer.email
}),
$('<br/>'),
$('<strong/>', {
'text': EALang.phone
}),
GeneralFunctions.renderPhoneIcon(event.data.customer.phone_number),
$('<span/>', {
'text': event.data.customer.phone_number
}),
$('<br/>'),
$('<strong/>', {
'text': EALang.notes
}),
$('<span/>', {
'text': getEventNotes(event)
}),
$('<br/>'),
$('<hr/>'),
$('<div/>', {
'class': 'd-flex justify-content-center',
'html': [
$('<button/>', {
'class': 'edit-popover btn btn-primary ' + displayEdit,
'html': [
$('<i/>', {
'class': 'far fa-edit mr-2'
}),
$('<span/>', {
'text': EALang.edit
})
]
}),
$('<button/>', {
'class': 'delete-popover btn btn-danger ' + displayDelete,
'html': [
$('<i/>', {
'class': 'far fa-trash-alt mr-2'
}),
$('<span/>', {
'text': EALang.delete
})
]
}),
$('<button/>', {
'class': 'close-popover btn btn-outline-secondary',
'html': [
$('<i/>', {
'class': 'fas fa-ban mr-2'
}),
$('<span/>', {
'text': EALang.close
})
]
})
]
})
]
});
}
$(jsEvent.target).popover({
placement: 'top',
title: event.title,
content: $html,
html: true,
container: '#calendar',
trigger: 'manual'
});
lastFocusedEventData = event;
$(jsEvent.target).popover('toggle');
// Fix popover position.
if ($('.popover').length > 0 && $('.popover').position().top < 200) {
$('.popover').css('top', '200px');
}
}
/**
* Calendar Event "Resize" Callback
*
* The user can change the duration of an event by resizing an appointment object on the calendar. This
* change needs to be stored to the database too and this is done via an ajax call.
*
* @see updateAppointmentData()
*/
function onEventResize(event, delta, revertFunc) {
if (GlobalVariables.user.privileges.appointments.edit === false) {
revertFunc();
Backend.displayNotification(EALang.no_privileges_edit_appointments);
return;
}
var $calendar = $('#calendar');
if ($('#notification').is(':visible')) {
$('#notification').hide('bind');
}
var successCallback;
if (event.data.is_unavailable === '0') {
// Prepare appointment data.
event.data.end_datetime = Date.parseExact(
event.data.end_datetime, 'yyyy-MM-dd HH:mm:ss')
.add({days: delta.days(), hours: delta.hours(), minutes: delta.minutes()})
.toString('yyyy-MM-dd HH:mm:ss');
var appointment = GeneralFunctions.clone(event.data);
// Must delete the following because only appointment data should be provided to the AJAX call.
delete appointment.customer;
delete appointment.provider;
delete appointment.service;
// Success callback
successCallback = function () {
// Display success notification to user.
var undoFunction = function () {
appointment.end_datetime = event.data.end_datetime = Date.parseExact(
appointment.end_datetime, 'yyyy-MM-dd HH:mm:ss')
.add({days: -delta.days(), hours: -delta.hours(), minutes: -delta.minutes()})
.toString('yyyy-MM-dd HH:mm:ss');
var url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_save_appointment';
var data = {
csrfToken: GlobalVariables.csrfToken,
appointment_data: JSON.stringify(appointment)
};
$.post(url, data)
.done(function () {
$('#notification').hide('blind');
revertFunc();
})
.fail(GeneralFunctions.ajaxFailureHandler);
};
Backend.displayNotification(EALang.appointment_updated, [
{
'label': EALang.undo,
'function': undoFunction
}
]);
$('#footer').css('position', 'static'); // Footer position fix.
// Update the event data for later use.
$calendar.fullCalendar('updateEvent', event);
};
// Update appointment data.
BackendCalendarApi.saveAppointment(appointment, null, successCallback);
} else {
// Update unavailable time period.
var unavailable = {
id: event.data.id,
start_datetime: event.start.format('YYYY-MM-DD HH:mm:ss'),
end_datetime: event.end.format('YYYY-MM-DD HH:mm:ss'),
id_users_provider: event.data.id_users_provider
};
event.data.end_datetime = unavailable.end_datetime;
// Define success callback function.
successCallback = function () {
// Display success notification to user.
var undoFunction = function () {
unavailable.end_datetime = event.data.end_datetime = Date.parseExact(
unavailable.end_datetime, 'yyyy-MM-dd HH:mm:ss')
.add({minutes: -delta.minutes()})
.toString('yyyy-MM-dd HH:mm:ss');
var url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_save_unavailable';
var data = {
csrfToken: GlobalVariables.csrfToken,
unavailable: JSON.stringify(unavailable)
};
$.post(url, data)
.done(function () {
$('#notification').hide('blind');
revertFunc();
})
.fail(GeneralFunctions.ajaxFailureHandler);
};
Backend.displayNotification(EALang.unavailable_updated, [
{
'label': EALang.undo,
'function': undoFunction
}
]);
$('#footer').css('position', 'static'); // Footer position fix.
// Update the event data for later use.
$calendar.fullCalendar('updateEvent', event);
};
BackendCalendarApi.saveUnavailable(unavailable, successCallback);
}
}
/**
* Calendar Event "Drop" Callback
*
* This event handler is triggered whenever the user drags and drops an event into a different position
* on the calendar. We need to update the database with this change. This is done via an ajax call.
*/
function onEventDrop(event, delta, revertFunc) {
if (GlobalVariables.user.privileges.appointments.edit === false) {
revertFunc();
Backend.displayNotification(EALang.no_privileges_edit_appointments);
return;
}
if ($('#notification').is(':visible')) {
$('#notification').hide('bind');
}
var successCallback;
if (event.data.is_unavailable === '0') {
// Prepare appointment data.
var appointment = GeneralFunctions.clone(event.data);
// Must delete the following because only appointment data should be provided to the ajax call.
delete appointment.customer;
delete appointment.provider;
delete appointment.service;
appointment.start_datetime = Date.parseExact(
appointment.start_datetime, 'yyyy-MM-dd HH:mm:ss')
.add({days: delta.days(), hours: delta.hours(), minutes: delta.minutes()})
.toString('yyyy-MM-dd HH:mm:ss');
appointment.end_datetime = Date.parseExact(
appointment.end_datetime, 'yyyy-MM-dd HH:mm:ss')
.add({days: delta.days(), hours: delta.hours(), minutes: delta.minutes()})
.toString('yyyy-MM-dd HH:mm:ss');
event.data.start_datetime = appointment.start_datetime;
event.data.end_datetime = appointment.end_datetime;
// Define success callback function.
successCallback = function () {
// Define the undo function, if the user needs to reset the last change.
var undoFunction = function () {
appointment.start_datetime = Date.parseExact(
appointment.start_datetime, 'yyyy-MM-dd HH:mm:ss')
.add({days: -delta.days(), hours: -delta.hours(), minutes: -delta.minutes()})
.toString('yyyy-MM-dd HH:mm:ss');
appointment.end_datetime = Date.parseExact(
appointment.end_datetime, 'yyyy-MM-dd HH:mm:ss')
.add({days: -delta.days(), hours: -delta.hours(), minutes: -delta.minutes()})
.toString('yyyy-MM-dd HH:mm:ss');
event.data.start_datetime = appointment.start_datetime;
event.data.end_datetime = appointment.end_datetime;
var url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_save_appointment';
var data = {
csrfToken: GlobalVariables.csrfToken,
appointment_data: JSON.stringify(appointment)
};
$.post(url, data)
.done(function () {
$('#notification').hide('blind');
revertFunc();
})
.fail(GeneralFunctions.ajaxFailureHandler);
};
Backend.displayNotification(EALang.appointment_updated, [
{
'label': EALang.undo,
'function': undoFunction
}
]);
$('#footer').css('position', 'static'); // Footer position fix.
};
// Update appointment data.
BackendCalendarApi.saveAppointment(appointment, null, successCallback);
} else {
// Update unavailable time period.
var unavailable = {
id: event.data.id,
start_datetime: event.start.format('YYYY-MM-DD HH:mm:ss'),
end_datetime: event.end.format('YYYY-MM-DD HH:mm:ss'),
id_users_provider: event.data.id_users_provider
};
successCallback = function () {
var undoFunction = function () {
unavailable.start_datetime = Date.parseExact(
unavailable.start_datetime, 'yyyy-MM-dd HH:mm:ss')
.add({days: -delta.days(), minutes: -delta.minutes()})
.toString('yyyy-MM-dd HH:mm:ss');
unavailable.end_datetime = Date.parseExact(
unavailable.end_datetime, 'yyyy-MM-dd HH:mm:ss')
.add({days: -delta.days(), minutes: -delta.minutes()})
.toString('yyyy-MM-dd HH:mm:ss');
event.data.start_datetime = unavailable.start_datetime;
event.data.end_datetime = unavailable.end_datetime;
var url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_save_unavailable';
var data = {
csrfToken: GlobalVariables.csrfToken,
unavailable: JSON.stringify(unavailable)
};
$.post(url, data)
.done(function () {
$('#notification').hide('blind');
revertFunc();
})
.fail(GeneralFunctions.ajaxFailureHandler);
};
Backend.displayNotification(EALang.unavailable_updated, [
{
label: EALang.undo,
function: undoFunction
}
]);
$('#footer').css('position', 'static'); // Footer position fix.
};
BackendCalendarApi.saveUnavailable(unavailable, successCallback);
}
}
/**
* Set Table Calendar View
*
* This method will set the optimal size in the calendar view elements in order to fit in the page without
* using scrollbars.
*/
function setCalendarViewSize() {
var height = window.innerHeight - $('#header').outerHeight() - $('#footer').outerHeight()
- $('#calendar-toolbar').outerHeight() - $('.calendar-header').outerHeight() - 50;
if (height < 500) {
height = 500;
}
var $dateColumn = $('.date-column');
var $calendarViewDiv = $('.calendar-view > div');
$calendarViewDiv.css('min-width', '1000%');
var width = 0;
$dateColumn.each(function (index, dateColumn) {
width += $(dateColumn).outerWidth();
});
$calendarViewDiv.css('min-width', width + 200);
var dateColumnHeight = $dateColumn.outerHeight();
$('.calendar-view .not-working').outerHeight((dateColumnHeight > height ? dateColumnHeight : height) - 70);
}
/**
* Get the calendar events.
*
* @param {Date} startDate The start date of the selected period.
* @param {Date} endDate The end date of the selected period.
*
* @return {jQuery.jqXHR}
*/
function getCalendarEvents(startDate, endDate) {
var url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_get_calendar_events';
var data = {
csrfToken: GlobalVariables.csrfToken,
startDate: startDate.toString('yyyy-MM-dd'),
endDate: endDate.toString('yyyy-MM-dd')
};
return $.ajax({
url: url,
data: data,
method: 'POST',
beforeSend: function () {
// $('#loading').css('visibility', 'hidden');
},
complete: function () {
// $('#loading').css('visibility', '');
}
});
}
/**
* Initialize Page
*/
exports.initialize = function () {
createHeader();
var startDate = moment().toDate();
var endDate = moment().add(Number($('#select-filter-item').val()) - 1, 'days').toDate();
createView(startDate, endDate);
bindEventHandlers();
// Hide Google Calendar Sync buttons cause they can not be used within this view.
$('#enable-sync, #google-sync').hide();
};
})(window.BackendCalendarTableView);