mirror of
https://github.com/alextselegidis/easyappointments.git
synced 2024-11-26 01:43:06 +03:00
Ολοκλήρωση του πρώτου μέρους δυνατοτήτων της σελίδας Calendar του backend. Σχεδίαση και προετοιμασία του τρόπου με τον οποίο θα εκτελείται η διαδικασία OAuth, έτσι ώστε να συχρονίζονται τα πλάνα των πάροχων με το Google Calendar.
This commit is contained in:
parent
d2eb0b6400
commit
fc53817e81
16 changed files with 2797 additions and 157 deletions
|
@ -1,9 +1,15 @@
|
||||||
VERSION 0.2
|
VERSION 0.3
|
||||||
===========
|
===========
|
||||||
- Use the PHPMailer class for sending HTML emails.
|
Main
|
||||||
- Display error message to users.
|
|
||||||
- Includes complete Google Sync protocol document.
|
- First backend-calendar page implementation (not complete, admin's perspective).
|
||||||
- Customers can book appointments only for the available hours.
|
- Javascript Google API usage from the customer's perspective.
|
||||||
- Generation of code documentation.
|
- Backend google calendar authentication Process.
|
||||||
- Customers can edit the application through unique links (from their emails).
|
- Sync every appointment change made from E!A to Google Calendar.
|
||||||
- Minor Fixes
|
- Display user friendly error messages.
|
||||||
|
|
||||||
|
|
||||||
|
Minor
|
||||||
|
|
||||||
|
- Added sync exception to Google Sync library.
|
||||||
|
|
36
install.sh
36
install.sh
|
@ -1,36 +0,0 @@
|
||||||
#! /bin/bash
|
|
||||||
|
|
||||||
clear
|
|
||||||
|
|
||||||
echo "========================================="
|
|
||||||
echo " "
|
|
||||||
echo "Easy!Appointments Installation Script "
|
|
||||||
echo " "
|
|
||||||
echo "========================================="
|
|
||||||
|
|
||||||
printf "Before you continue ensure that your MAMP
|
|
||||||
\nserver is running and you already know the
|
|
||||||
\nfolder path in which the application is
|
|
||||||
\ngoing to be installed.\n\n"
|
|
||||||
|
|
||||||
read -p "Press Enter to Continue" -n 1
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]
|
|
||||||
then
|
|
||||||
# Copy Application Files
|
|
||||||
printf "\n\nEnter destination directory:"
|
|
||||||
read DEST
|
|
||||||
cp -r "easy_appointments" $DEST
|
|
||||||
|
|
||||||
# Install MySQL Database
|
|
||||||
printf "\n>>>Installing database ..."
|
|
||||||
/Applications/MAMP/Library/bin/mysql -uroot -p
|
|
||||||
/Applications/MAMP/Library/bin/mysql "CREATE DATABASE IF NOT EXISTS easy_appointments"
|
|
||||||
/Applications/MAMP/Library/bin/mysql "easy_appointments" < "easy_appointments.sql"
|
|
||||||
|
|
||||||
printf "\n\n>>>Review your configuration.php file before trying to use the application."
|
|
||||||
|
|
||||||
# End of install operation
|
|
||||||
printf "\n\n======================================"
|
|
||||||
printf "\nInstallation completed successfully!"
|
|
||||||
read
|
|
||||||
fi
|
|
|
@ -28,19 +28,19 @@ class Backend extends CI_Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function customers() {
|
public function customers() {
|
||||||
|
echo '<h1>Not implemented yet.</h1>';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function services() {
|
public function services() {
|
||||||
|
echo '<h1>Not implemented yet.</h1>';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function providers() {
|
public function providers() {
|
||||||
|
echo '<h1>Not implemented yet.</h1>';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function settings() {
|
public function settings() {
|
||||||
|
echo '<h1>Not implemented yet.</h1>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,8 +50,8 @@ class Backend extends CI_Controller {
|
||||||
* period and record type (provider or service).
|
* period and record type (provider or service).
|
||||||
*
|
*
|
||||||
* @param {numeric} $_POST['record_id'] Selected record id.
|
* @param {numeric} $_POST['record_id'] Selected record id.
|
||||||
* @param {string} $_POST['filter_type'] Could be either FILTER_TYPE_PROVIDER or
|
* @param {string} $_POST['filter_type'] Could be either FILTER_TYPE_PROVIDER
|
||||||
* FILTER_TYPE_SERVICE.
|
* or FILTER_TYPE_SERVICE.
|
||||||
* @param {string} $_POST['start_date'] The user selected start date.
|
* @param {string} $_POST['start_date'] The user selected start date.
|
||||||
* @param {string} $_POST['end_date'] The user selected end date.
|
* @param {string} $_POST['end_date'] The user selected end date.
|
||||||
*/
|
*/
|
||||||
|
@ -86,6 +86,40 @@ class Backend extends CI_Controller {
|
||||||
|
|
||||||
echo json_encode($appointments);
|
echo json_encode($appointments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [AJAX] Save appointment changes that are made from the backend calendar
|
||||||
|
* page.
|
||||||
|
*
|
||||||
|
* @param array $_POST['appointment_data'] (OPTIONAL) Array with the
|
||||||
|
* appointment data.
|
||||||
|
* @param array $_POST['customer_data'] (OPTIONAL) Array with the customer
|
||||||
|
* data.
|
||||||
|
*/
|
||||||
|
public function ajax_save_appointment_changes() {
|
||||||
|
try {
|
||||||
|
if (isset($_POST['appointment_data'])) {
|
||||||
|
$appointment_data = json_decode(stripcslashes($_POST['appointment_data']), true);
|
||||||
|
$this->load->model('Appointments_Model');
|
||||||
|
$this->Appointments_Model->add($appointment_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_POST['customer_data'])) {
|
||||||
|
$customer_data = json_decode(stripcslashes($_POST['customer_data']), true);
|
||||||
|
$this->load->model('Customers_Model');
|
||||||
|
$this->Customers_Model->add($customer_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode('SUCCESS');
|
||||||
|
|
||||||
|
} catch(Exception $exc) {
|
||||||
|
$js_error = array(
|
||||||
|
'error' => $exc->getMessage()
|
||||||
|
);
|
||||||
|
|
||||||
|
echo json_encode($js_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* End of file backend.php */
|
/* End of file backend.php */
|
||||||
|
|
40
src/application/controllers/google.php
Normal file
40
src/application/controllers/google.php
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
|
||||||
|
|
||||||
|
class Google extends CI_Controller {
|
||||||
|
/**
|
||||||
|
* Authorize Google Calendar API usage for a specific provider.
|
||||||
|
*
|
||||||
|
* Since it is required to follow the web application flow, in order to retrieve
|
||||||
|
* a refresh token from the Google API service, this method is going to authorize
|
||||||
|
* the given provider.
|
||||||
|
*
|
||||||
|
* @param int $provider_id The provider id, for whom the sync authorization is
|
||||||
|
* made.
|
||||||
|
*/
|
||||||
|
public function oauth($provider_id) {
|
||||||
|
$this->load->library('Google_Sync');
|
||||||
|
|
||||||
|
// @task Create auth link and redirect browser window.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback method for the Google Calendar API authorization process.
|
||||||
|
*
|
||||||
|
* Once the user grants consent with his Google Calendar data usage, the Google
|
||||||
|
* OAuth service will redirect him back in this page. Here we are going to store
|
||||||
|
* the refresh token, because this is what will be used to generate access tokens
|
||||||
|
* in the future.
|
||||||
|
*
|
||||||
|
* <strong>IMPORTANT!</strong> Because it is necessary to authorize the application
|
||||||
|
* using the web server flow (see official documentation of OAuth), every
|
||||||
|
* Easy!Appointments installation should use its own calendar api key. So in every
|
||||||
|
* api console account, the "http://path-to-e!a/google/oauth_callback" should be
|
||||||
|
* included in the allowed redirect urls.
|
||||||
|
*/
|
||||||
|
public function oauth_callback() {
|
||||||
|
// @task Store refresh token.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* End of file google.php */
|
||||||
|
/* Location: ./application/controllers/google.php */
|
|
@ -4,6 +4,9 @@
|
||||||
<script type="text/javascript"
|
<script type="text/javascript"
|
||||||
src="<?php echo $base_url; ?>assets/js/libs/jquery/fullcalendar.min.js"></script>
|
src="<?php echo $base_url; ?>assets/js/libs/jquery/fullcalendar.min.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript"
|
||||||
|
src="<?php echo $base_url; ?>assets/js/libs/jquery/jquery-ui-timepicker-addon.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript"
|
<script type="text/javascript"
|
||||||
src="<?php echo $base_url; ?>assets/js/backend_calendar.js"></script>
|
src="<?php echo $base_url; ?>assets/js/backend_calendar.js"></script>
|
||||||
|
|
||||||
|
@ -39,9 +42,15 @@
|
||||||
Enable Sync
|
Enable Sync
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button id="insert-new-appointment" class="btn"
|
||||||
|
title="Create a new appointment and store it into the database.">
|
||||||
|
<i class="icon-plus"></i>
|
||||||
|
New Appointment
|
||||||
|
</button>
|
||||||
|
|
||||||
<button id="insert-unavailable-period" class="btn"
|
<button id="insert-unavailable-period" class="btn"
|
||||||
title="During unavailalbe period the provider won't accept new appointments.">
|
title="During unavailalbe period the provider won't accept new appointments.">
|
||||||
<i class="icon-plus"></i>
|
<i class="icon-ban-circle"></i>
|
||||||
Unavailable
|
Unavailable
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -62,6 +71,8 @@
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Appointment Details</legend>
|
<legend>Appointment Details</legend>
|
||||||
|
|
||||||
|
<input id="appointment-id" type="hidden" />
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="select-service" class="control-label">Service</label>
|
<label for="select-service" class="control-label">Service</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
@ -77,9 +88,16 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="select-date" class="control-label"></label>
|
<label for="start-datetime" class="control-label">Start Date/Time</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<span id="select-date"></span>
|
<input type="text" id="start-datetime" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="end-datetime" class="control-label">End Date/Time</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="end-datetime" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
@ -87,6 +105,8 @@
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Customer Details</legend>
|
<legend>Customer Details</legend>
|
||||||
|
|
||||||
|
<input id="customer-id" type="hidden" />
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="first-name" class="control-label">First Name</label>
|
<label for="first-name" class="control-label">First Name</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
|
7
src/application/views/backend/customers.php
Normal file
7
src/application/views/backend/customers.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* To change this template, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
?>
|
|
@ -30,6 +30,10 @@
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
type="text/css"
|
type="text/css"
|
||||||
href="<?php echo $base_url; ?>assets/css/backend.css">
|
href="<?php echo $base_url; ?>assets/css/backend.css">
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href="<?php echo $base_url; ?>assets/css/general.css">
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
|
@ -104,3 +108,4 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="notification" style="display: none;"></div>
|
7
src/application/views/backend/providers.php
Normal file
7
src/application/views/backend/providers.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* To change this template, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
?>
|
7
src/application/views/backend/services.php
Normal file
7
src/application/views/backend/services.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* To change this template, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
?>
|
7
src/application/views/backend/settings.php
Normal file
7
src/application/views/backend/settings.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* To change this template, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
?>
|
|
@ -30,6 +30,8 @@ root {
|
||||||
}
|
}
|
||||||
#footer #footer-content { padding: 15px; }
|
#footer #footer-content { padding: 15px; }
|
||||||
|
|
||||||
|
#notification strong { margin-right: 15px; }
|
||||||
|
|
||||||
/* BACKEND CALENDAR PAGE
|
/* BACKEND CALENDAR PAGE
|
||||||
-------------------------------------------------------------------- */
|
-------------------------------------------------------------------- */
|
||||||
#calendar-page #calendar-toolbar { margin: 15px 10px 20px 10px; padding-bottom: 10px;
|
#calendar-page #calendar-toolbar { margin: 15px 10px 20px 10px; padding-bottom: 10px;
|
||||||
|
@ -38,7 +40,7 @@ root {
|
||||||
#calendar-page #calendar-filter label { display: inline-block; margin-right: 7px;
|
#calendar-page #calendar-filter label { display: inline-block; margin-right: 7px;
|
||||||
font-weight: bold; font-size: 18px; }
|
font-weight: bold; font-size: 18px; }
|
||||||
#calendar-page #calendar-filter select { margin-top: 5px; }
|
#calendar-page #calendar-filter select { margin-top: 5px; }
|
||||||
#calendar-page #calendar-actions { display: inline-block; float: right; }
|
#calendar-page #calendar-actions { display: inline-block; float: right; margin-top: 4px; }
|
||||||
#calendar-page #calendar { margin: 12px; }
|
#calendar-page #calendar { margin: 12px; }
|
||||||
|
|
||||||
/* BACKEND CUSTOMERS PAGE
|
/* BACKEND CUSTOMERS PAGE
|
||||||
|
|
12
src/assets/css/general.css
Normal file
12
src/assets/css/general.css
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/* JQUERY UI DATETIME PICKER ADDON
|
||||||
|
------------------------------------------------------------------------- */
|
||||||
|
.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
|
||||||
|
.ui-timepicker-div dl { text-align: left; }
|
||||||
|
.ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; }
|
||||||
|
.ui-timepicker-div dl dd { margin: 0 10px 10px 65px; }
|
||||||
|
.ui-timepicker-div td { font-size: 90%; }
|
||||||
|
.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
.ui-timepicker-rtl{ direction: rtl; }
|
||||||
|
.ui-timepicker-rtl dl { text-align: right; }
|
||||||
|
.ui-timepicker-rtl dl dd { margin: 0 65px 10px 10px; }
|
|
@ -32,5 +32,43 @@ var Backend = {
|
||||||
'position' : 'static'
|
'position' : 'static'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display backend notifications to user.
|
||||||
|
*
|
||||||
|
* Using this method you can display notifications to the use with custom
|
||||||
|
* messages. If the 'actions' array is provided then an action link will
|
||||||
|
* be displayed too.
|
||||||
|
*
|
||||||
|
* @param {string} message Notification message
|
||||||
|
* @param {array} actions An array with custom actions that will be available
|
||||||
|
* to the user. Every array item is an object that contains the 'label' and
|
||||||
|
* 'function' key values.
|
||||||
|
*/
|
||||||
|
displayNotification: function(message, actions) {
|
||||||
|
if (message === undefined) {
|
||||||
|
message = 'NO MESSAGE PROVIDED FOR THIS NOTIFICATION';
|
||||||
|
}
|
||||||
|
|
||||||
|
var notificationHtml =
|
||||||
|
'<div class="notification alert">' +
|
||||||
|
'<strong>' + message + '</strong>';
|
||||||
|
|
||||||
|
$.each(actions, function(index, action) {
|
||||||
|
var actionId = action['label'].toLowerCase().replace(' ', '-');
|
||||||
|
notificationHtml += '<button id="' + actionId + '" class="btn">'
|
||||||
|
+ action['label'] + '</button>';
|
||||||
|
|
||||||
|
$(document).off('click', '#' + actionId);
|
||||||
|
$(document).on('click', '#' + actionId, action['function']);
|
||||||
|
});
|
||||||
|
|
||||||
|
notificationHtml += '</div>';
|
||||||
|
|
||||||
|
var leftValue = window.innerWidth / 2 - $('body .notification').width() - 200;
|
||||||
|
|
||||||
|
$('#notification').html(notificationHtml);
|
||||||
|
$('#notification').show('blind');
|
||||||
}
|
}
|
||||||
}
|
};
|
|
@ -4,10 +4,12 @@
|
||||||
* @namespace BackendCalendar
|
* @namespace BackendCalendar
|
||||||
*/
|
*/
|
||||||
var BackendCalendar = {
|
var BackendCalendar = {
|
||||||
|
// :: NAMESPACE CONSTANTS
|
||||||
FILTER_TYPE_PROVIDER : 'provider',
|
FILTER_TYPE_PROVIDER : 'provider',
|
||||||
FILTER_TYPE_SERVICE : 'service',
|
FILTER_TYPE_SERVICE : 'service',
|
||||||
|
|
||||||
lastFocusedEvent : undefined, // Contain event data for later use.
|
// :: NAMESPACE VALIABLES
|
||||||
|
lastFocusedEventData : undefined, // Contain event data for later use.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function makes the necessary initialization for the default backend
|
* This function makes the necessary initialization for the default backend
|
||||||
|
@ -17,7 +19,7 @@ var BackendCalendar = {
|
||||||
* @param {bool} defaultEventHandlers (OPTIONAL = TRUE) Determines whether the
|
* @param {bool} defaultEventHandlers (OPTIONAL = TRUE) Determines whether the
|
||||||
* default event handlers will be set for the current page.
|
* default event handlers will be set for the current page.
|
||||||
*/
|
*/
|
||||||
initialize: function(defaultEventHandlers) {
|
initialize : function(defaultEventHandlers) {
|
||||||
if (defaultEventHandlers === undefined) defaultEventHandlers = true;
|
if (defaultEventHandlers === undefined) defaultEventHandlers = true;
|
||||||
|
|
||||||
// :: INITIALIZE THE DOM ELEMENTS OF THE PAGE
|
// :: INITIALIZE THE DOM ELEMENTS OF THE PAGE
|
||||||
|
@ -25,6 +27,7 @@ var BackendCalendar = {
|
||||||
defaultView : 'agendaWeek',
|
defaultView : 'agendaWeek',
|
||||||
height : BackendCalendar.getCalendarHeight(),
|
height : BackendCalendar.getCalendarHeight(),
|
||||||
editable : true,
|
editable : true,
|
||||||
|
slotMinutes : 15,
|
||||||
columnFormat : {
|
columnFormat : {
|
||||||
month : 'ddd',
|
month : 'ddd',
|
||||||
week : 'ddd d/M',
|
week : 'ddd d/M',
|
||||||
|
@ -40,84 +43,20 @@ var BackendCalendar = {
|
||||||
center : 'title',
|
center : 'title',
|
||||||
right : 'agendaDay,agendaWeek,month'
|
right : 'agendaDay,agendaWeek,month'
|
||||||
},
|
},
|
||||||
windowResize : function(view) {
|
|
||||||
$('#calendar').fullCalendar('option', 'height',
|
|
||||||
BackendCalendar.getCalendarHeight());
|
|
||||||
},
|
|
||||||
dayClick : function(date, allDay, jsEvent, view) {
|
|
||||||
if (allDay) {
|
|
||||||
// Switch to day view
|
|
||||||
$('#calendar').fullCalendar('gotoDate', date);
|
|
||||||
$('#calendar').fullCalendar('changeView', 'agendaDay');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
eventClick : function(event, jsEvent, view) {
|
|
||||||
// Display a popover with the event details.
|
|
||||||
var html =
|
|
||||||
'<style type="text/css">'
|
|
||||||
+ '.popover-content strong {min-width: 80px; display:inline-block;}'
|
|
||||||
+ '.popover-content button {margin-right: 10px;}'
|
|
||||||
+ '</style>' +
|
|
||||||
'<strong>Start</strong> '
|
|
||||||
+ event.start.toString('dd-MM-yyyy HH:mm')
|
|
||||||
+ '<br>' +
|
|
||||||
'<strong>End</strong> '
|
|
||||||
+ event.end.toString('dd-MM-yyyy HH:mm')
|
|
||||||
+ '<br>' +
|
|
||||||
'<strong>Service</strong> '
|
|
||||||
+ event.title
|
|
||||||
+ '<br>' +
|
|
||||||
'<strong>Provider</strong> '
|
|
||||||
+ event.data['provider']['first_name'] + ' '
|
|
||||||
+ event.data['provider']['last_name']
|
|
||||||
+ '<br>' +
|
|
||||||
'<strong>Customer</strong> '
|
|
||||||
+ event.data['customer']['first_name'] + ' '
|
|
||||||
+ event.data['customer']['last_name']
|
|
||||||
+ '<hr>' +
|
|
||||||
'<center>' +
|
|
||||||
'<button class="edit-popover btn btn-primary">Edit</button>' +
|
|
||||||
'<button class="close-popover btn" data-po=' + jsEvent.target + '>Close</button>' +
|
|
||||||
'</center>';
|
|
||||||
|
|
||||||
$(jsEvent.target).popover({
|
// Calendar events need to be declared on initialization.
|
||||||
placement : 'top',
|
windowResize : BackendCalendar.calendarWindowResize,
|
||||||
title : event.title,
|
viewDisplay : BackendCalendar.calendarViewDisplay,
|
||||||
content : html,
|
dayClick : BackendCalendar.calendarDayClick,
|
||||||
html : true,
|
eventClick : BackendCalendar.calendarEventClick,
|
||||||
container : 'body',
|
eventResize : BackendCalendar.calendarEventResize,
|
||||||
trigger : 'manual'
|
eventDrop : BackendCalendar.calendarEventDrop
|
||||||
});
|
|
||||||
|
|
||||||
$(jsEvent.target).popover('show');
|
|
||||||
|
|
||||||
BackendCalendar.lastFocusedEvent = event;
|
|
||||||
},
|
|
||||||
eventResize : function(event, dayDelta, minuteDelta, revertFunc, jsEvent, ui, view) {
|
|
||||||
// @task Display confirmation modal.
|
|
||||||
|
|
||||||
},
|
|
||||||
eventDrop : function(event, dayDelta, minuteDelta, allDay, revertFunc, jsEvent, ui, view) {
|
|
||||||
// @task Display confirmation modal.
|
|
||||||
|
|
||||||
},
|
|
||||||
viewDisplay : function(view) {
|
|
||||||
// Place the footer into correct position because the calendar
|
|
||||||
// height might change.
|
|
||||||
BackendCalendar.refreshCalendarAppointments(
|
|
||||||
$('#calendar'),
|
|
||||||
$('#select-filter-item').val(),
|
|
||||||
$('#select-filter-item option:selected').attr('type'),
|
|
||||||
$('#calendar').fullCalendar('getView').visStart,
|
|
||||||
$('#calendar').fullCalendar('getView').visEnd);
|
|
||||||
$(window).trigger('resize');
|
|
||||||
|
|
||||||
$('.fv-events').each(function(index, eventHandle) {
|
|
||||||
$(eventHandle).popover();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Trigger once to set the proper footer position after calendar
|
||||||
|
// initialization.
|
||||||
|
BackendCalendar.calendarWindowResize();
|
||||||
|
|
||||||
// :: FILL THE SELECT ELEMENTS OF THE PAGE
|
// :: FILL THE SELECT ELEMENTS OF THE PAGE
|
||||||
var optgroupHtml = '<optgroup label="Providers">';
|
var optgroupHtml = '<optgroup label="Providers">';
|
||||||
$.each(GlobalVariables.availableProviders, function(index, provider) {
|
$.each(GlobalVariables.availableProviders, function(index, provider) {
|
||||||
|
@ -131,8 +70,8 @@ var BackendCalendar = {
|
||||||
optgroupHtml = '<optgroup label="Services">';
|
optgroupHtml = '<optgroup label="Services">';
|
||||||
$.each(GlobalVariables.availableServices, function(index, service) {
|
$.each(GlobalVariables.availableServices, function(index, service) {
|
||||||
optgroupHtml += '<option value="' + service['id'] + '" ' +
|
optgroupHtml += '<option value="' + service['id'] + '" ' +
|
||||||
'type="' + BackendCalendar.FILTER_TYPE_SERVICE + '">'
|
'type="' + BackendCalendar.FILTER_TYPE_SERVICE + '">' +
|
||||||
+ service['name'] + '</option>';
|
service['name'] + '</option>';
|
||||||
});
|
});
|
||||||
optgroupHtml += '</optgroup>';
|
optgroupHtml += '</optgroup>';
|
||||||
$('#select-filter-item').append(optgroupHtml)
|
$('#select-filter-item').append(optgroupHtml)
|
||||||
|
@ -149,9 +88,9 @@ var BackendCalendar = {
|
||||||
* page. If you do not need the default handlers then initialize the page
|
* page. If you do not need the default handlers then initialize the page
|
||||||
* by setting the "defaultEventHandlers" argument to "false".
|
* by setting the "defaultEventHandlers" argument to "false".
|
||||||
*/
|
*/
|
||||||
bindEventHandlers: function() {
|
bindEventHandlers : function() {
|
||||||
/**
|
/**
|
||||||
* Event: Calendar filter item "Changed"
|
* Event: Calendar Filter Item "Change"
|
||||||
*
|
*
|
||||||
* Load the appointments that correspond to the select filter item and
|
* Load the appointments that correspond to the select filter item and
|
||||||
* display them on the calendar.
|
* display them on the calendar.
|
||||||
|
@ -164,11 +103,20 @@ var BackendCalendar = {
|
||||||
$('#calendar').fullCalendar('getView').visStart,
|
$('#calendar').fullCalendar('getView').visStart,
|
||||||
$('#calendar').fullCalendar('getView').visEnd);
|
$('#calendar').fullCalendar('getView').visEnd);
|
||||||
|
|
||||||
// @task If current value is service, then the sync buttons must be disabled.
|
// If current value is service, then the sync buttons must be
|
||||||
|
// disabled.
|
||||||
|
if ($('#select-filter-item option:selected').attr('type')
|
||||||
|
=== BackendCalendar.FILTER_TYPE_SERVICE) {
|
||||||
|
$('#google-sync, #enable-sync, #insert-unavailable-period')
|
||||||
|
.prop('disabled', true);
|
||||||
|
} else {
|
||||||
|
$('#google-sync, #enable-sync, #insert-unavailable-period')
|
||||||
|
.prop('disabled', false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event: Popover close button "Clicked"
|
* Event: Popover Close Button "Click"
|
||||||
*
|
*
|
||||||
* Hides the open popover element.
|
* Hides the open popover element.
|
||||||
*/
|
*/
|
||||||
|
@ -177,18 +125,19 @@ var BackendCalendar = {
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event: Popover edit button "Clicked"
|
* Event: Popover Edit Button "Click"
|
||||||
*
|
*
|
||||||
* Enables the edit dialog of the selected calendar event.
|
* Enables the edit dialog of the selected calendar event.
|
||||||
*/
|
*/
|
||||||
$(document).on('click', '.edit-popover', function() {
|
$(document).on('click', '.edit-popover', function() {
|
||||||
$(this).parents().eq(2).remove(); // Hide the popover
|
$(this).parents().eq(2).remove(); // Hide the popover
|
||||||
|
|
||||||
var appointmentData = BackendCalendar.lastFocusedEvent.data;
|
var appointmentData = BackendCalendar.lastFocusedEventData.data;
|
||||||
var modalHandle = $('#manage-appointment');
|
var modalHandle = $('#manage-appointment');
|
||||||
|
|
||||||
// :: APPLY APPOINTMENT DATA AND SHOW TO MODAL DIALOG
|
// :: APPLY APPOINTMENT DATA AND SHOW TO MODAL DIALOG
|
||||||
modalHandle.find('input, textarea').val('');
|
modalHandle.find('input, textarea').val('');
|
||||||
|
modalHandle.find('#appointment-id').val(appointmentData['id']);
|
||||||
|
|
||||||
// Fill the services listbox and select the appointment service.
|
// Fill the services listbox and select the appointment service.
|
||||||
$.each(GlobalVariables.availableServices, function(index, service) {
|
$.each(GlobalVariables.availableServices, function(index, service) {
|
||||||
|
@ -220,7 +169,25 @@ var BackendCalendar = {
|
||||||
|
|
||||||
modalHandle.find('#select-provider').val(appointmentData['id_users_provider']);
|
modalHandle.find('#select-provider').val(appointmentData['id_users_provider']);
|
||||||
|
|
||||||
|
// Set the start and end datetime of the appointment.\
|
||||||
|
var startDatetime = Date.parseExact(appointmentData['start_datetime'],
|
||||||
|
'yyyy-MM-dd HH:mm:ss').toString('dd/MM/yyyy HH:mm');
|
||||||
|
modalHandle.find('#start-datetime').datetimepicker({
|
||||||
|
dateFormat : 'dd/mm/yy',
|
||||||
|
defaultValue: startDatetime
|
||||||
|
});
|
||||||
|
modalHandle.find('#start-datetime').val(startDatetime);
|
||||||
|
|
||||||
|
var endDatetime = Date.parseExact(appointmentData['end_datetime'],
|
||||||
|
'yyyy-MM-dd HH:mm:ss').toString('dd/MM/yyyy HH:mm');
|
||||||
|
modalHandle.find('#end-datetime').datetimepicker({
|
||||||
|
dateFormat : 'dd/mm/yy',
|
||||||
|
defaultValue: endDatetime
|
||||||
|
});
|
||||||
|
modalHandle.find('#end-datetime').val(endDatetime);
|
||||||
|
|
||||||
var customerData = appointmentData['customer'];
|
var customerData = appointmentData['customer'];
|
||||||
|
modalHandle.find('#customer-id').val(appointmentData['id_users_customer']);
|
||||||
modalHandle.find('#first-name').val(customerData['first_name']);
|
modalHandle.find('#first-name').val(customerData['first_name']);
|
||||||
modalHandle.find('#last-name').val(customerData['last_name']);
|
modalHandle.find('#last-name').val(customerData['last_name']);
|
||||||
modalHandle.find('#email').val(customerData['email']);
|
modalHandle.find('#email').val(customerData['email']);
|
||||||
|
@ -228,16 +195,14 @@ var BackendCalendar = {
|
||||||
modalHandle.find('#address').val(customerData['address']);
|
modalHandle.find('#address').val(customerData['address']);
|
||||||
modalHandle.find('#city').val(customerData['city']);
|
modalHandle.find('#city').val(customerData['city']);
|
||||||
modalHandle.find('#zip-code').val(customerData['zip_code']);
|
modalHandle.find('#zip-code').val(customerData['zip_code']);
|
||||||
modalHandle.find('#notes').val(customerData['notes']);
|
modalHandle.find('#notes').val(appointmentData['notes']);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// :: DISPLAY THE MANAGE APPOINTMENTS MODAL DIALOG
|
// :: DISPLAY THE MANAGE APPOINTMENTS MODAL DIALOG
|
||||||
$('#manage-appointment').modal('show');
|
$('#manage-appointment').modal('show');
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event: Manage Appointments Dialog Cancel Button "Clicked"
|
* Event: Manage Appointments Dialog Cancel Button "Click"
|
||||||
*
|
*
|
||||||
* Closes the dialog without making any actions.
|
* Closes the dialog without making any actions.
|
||||||
*/
|
*/
|
||||||
|
@ -246,16 +211,108 @@ var BackendCalendar = {
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event: Manage Appointments Dialog Save Button "Clicked"
|
* Event: Manage Appointments Dialog Save Button "Click"
|
||||||
*
|
*
|
||||||
* Stores the appointment changes.
|
* Stores the appointment changes.
|
||||||
*/
|
*/
|
||||||
$('#manage-appointment #save-button').click(function() {
|
$('#manage-appointment #save-button').click(function() {
|
||||||
// :: PREPARE APPOINTMENT DATA FOR AJAX CALL
|
// :: PREPARE APPOINTMENT DATA FOR AJAX CALL
|
||||||
var appointmentData = {};
|
var modalHandle = $('#manage-appointment');
|
||||||
|
|
||||||
|
// Id must exist on the object in order for the model to update
|
||||||
|
// the record and not to perform an insert operation.
|
||||||
|
|
||||||
|
var startDatetime = Date.parseExact(modalHandle.find('#start-datetime').val(),
|
||||||
|
'dd/MM/yyyy HH:mm').toString('yyyy-MM-dd HH:mm:ss');
|
||||||
|
var endDatetime = Date.parseExact(modalHandle.find('#end-datetime').val(),
|
||||||
|
'dd/MM/yyyy HH:mm').toString('yyyy-MM-dd HH:mm:ss');
|
||||||
|
|
||||||
|
var appointmentData = {
|
||||||
|
'id' : modalHandle.find('#appointment-id').val(),
|
||||||
|
'id_services' : modalHandle.find('#select-service').val(),
|
||||||
|
'id_users_provider' : modalHandle.find('#select-provider').val(),
|
||||||
|
'id_users_customer' : modalHandle.find('#customer-id').val(),
|
||||||
|
'start_datetime' : startDatetime,
|
||||||
|
'end_datetime' : endDatetime,
|
||||||
|
'notes' : modalHandle.find('#notes').val()
|
||||||
|
};
|
||||||
|
|
||||||
|
var customerData = {
|
||||||
|
'id' : modalHandle.find('#customer-id').val(),
|
||||||
|
'first_name' : modalHandle.find('#first-name').val(),
|
||||||
|
'last_name' : modalHandle.find('#last-name').val(),
|
||||||
|
'email' : modalHandle.find('#email').val(),
|
||||||
|
'phone_number' : modalHandle.find('#phone-number').val(),
|
||||||
|
'address' : modalHandle.find('#address').val(),
|
||||||
|
'city' : modalHandle.find('#city').val(),
|
||||||
|
'zip_code' : modalHandle.find('#zip-code').val()
|
||||||
|
};
|
||||||
|
|
||||||
|
// :: DEFINE SUCCESS EVENT CALLBACK
|
||||||
|
var successCallback = function(response) {
|
||||||
|
if (response.error) {
|
||||||
|
// There was something wrong within the ajax function.
|
||||||
|
modalHandle.find('.modal-header').append(
|
||||||
|
'<br><div class="alert alert-error">' +
|
||||||
|
response.error +
|
||||||
|
'</div>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display success message to the user.
|
||||||
|
modalHandle.find('.modal-header').append(
|
||||||
|
'<br><div class="alert alert-success">' +
|
||||||
|
'Appointment saved successfully!' +
|
||||||
|
'</div>');
|
||||||
|
|
||||||
|
// Close the modal dialog and refresh the calendar appointments
|
||||||
|
// after one second.
|
||||||
|
setTimeout(function() {
|
||||||
|
modalHandle.find('.alert').remove();
|
||||||
|
modalHandle.modal('hide');
|
||||||
|
$('#select-filter-item').trigger('change');
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
// :: DEFINE ERROR EVENT CALLBACK
|
||||||
|
var errorCallback = function() {
|
||||||
|
// Display error message to the user.
|
||||||
|
modalHandle.find('.modal-header').append(
|
||||||
|
'<br><div class="alert alert-error">' +
|
||||||
|
'A server communication error occured, please try again.' +
|
||||||
|
'</div>');
|
||||||
|
};
|
||||||
|
|
||||||
// :: CALL THE UPDATE APPOINTMENT METHOD
|
// :: CALL THE UPDATE APPOINTMENT METHOD
|
||||||
BackendCalendar.updateAppointment(appointmentData);
|
BackendCalendar.updateAppointmentData(appointmentData, customerData,
|
||||||
|
successCallback, errorCallback);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event: Enable Synchronization Button "Click"
|
||||||
|
*
|
||||||
|
* When the user clicks on the "Enable Sync" button, a popup should appear
|
||||||
|
* that is going to follow the web server authorization flow of OAuth.
|
||||||
|
*
|
||||||
|
* @task Check whether the selected provider has already enabled the sync
|
||||||
|
* or not.
|
||||||
|
*/
|
||||||
|
$('#enable-sync').click(function() {
|
||||||
|
var authUrl = GlobalVariables.baseUrl + 'google/oauth/'
|
||||||
|
+ $('#select-filter-item').val();
|
||||||
|
var redirectUrl = GlobalVariables.baseUrl + 'google/oauth_callback';
|
||||||
|
|
||||||
|
var windowHandle = window.open(authUrl, 'Authorize Easy!Appointments',
|
||||||
|
'width=800, height=600');
|
||||||
|
|
||||||
|
var authInterval = window.setInterval(function() {
|
||||||
|
if (windowHandle.document.URL.indexOf(redirectUrl) !== -1) {
|
||||||
|
// The user has granted access to his data.
|
||||||
|
windowHandle.close();
|
||||||
|
window.clearInterval(authInterval);
|
||||||
|
$('#enable-sync').addClass('btn-success');
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -265,7 +322,7 @@ var BackendCalendar = {
|
||||||
*
|
*
|
||||||
* @return {int} Returns the calendar element height in pixels.
|
* @return {int} Returns the calendar element height in pixels.
|
||||||
*/
|
*/
|
||||||
getCalendarHeight: function () {
|
getCalendarHeight : function () {
|
||||||
var result = window.innerHeight - $('#footer').height() - $('#header').height()
|
var result = window.innerHeight - $('#footer').height() - $('#header').height()
|
||||||
- $('#calendar-toolbar').height() - 80; // 80 for fine tuning
|
- $('#calendar-toolbar').height() - 80; // 80 for fine tuning
|
||||||
return (result > 500) ? result : 500; // Minimum height is 500px
|
return (result > 500) ? result : 500; // Minimum height is 500px
|
||||||
|
@ -282,9 +339,9 @@ var BackendCalendar = {
|
||||||
* @param {date} startDate Visible start date of the calendar.
|
* @param {date} startDate Visible start date of the calendar.
|
||||||
* @param {type} endDate Visible end date of the calendar.
|
* @param {type} endDate Visible end date of the calendar.
|
||||||
*/
|
*/
|
||||||
refreshCalendarAppointments: function(calendarHandle, recordId, filterType,
|
refreshCalendarAppointments : function(calendarHandle, recordId, filterType,
|
||||||
startDate, endDate) {
|
startDate, endDate) {
|
||||||
var ajaxUrl = GlobalVariables.baseUrl + 'backend/ajax_get_calendar_appointments';
|
var postUrl = GlobalVariables.baseUrl + 'backend/ajax_get_calendar_appointments';
|
||||||
|
|
||||||
var postData = {
|
var postData = {
|
||||||
record_id : recordId,
|
record_id : recordId,
|
||||||
|
@ -293,7 +350,7 @@ var BackendCalendar = {
|
||||||
filter_type : filterType
|
filter_type : filterType
|
||||||
};
|
};
|
||||||
|
|
||||||
$.post(ajaxUrl, postData, function(response) {
|
$.post(postUrl, postData, function(response) {
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
//console.log('Refresh Calendar Appointments Response :', response);
|
//console.log('Refresh Calendar Appointments Response :', response);
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
|
@ -327,8 +384,295 @@ var BackendCalendar = {
|
||||||
* @param {object} appointmentData Contain the new appointment data. The
|
* @param {object} appointmentData Contain the new appointment data. The
|
||||||
* id of the appointment MUST be already included. The rest values must
|
* id of the appointment MUST be already included. The rest values must
|
||||||
* follow the database structure.
|
* follow the database structure.
|
||||||
|
* @param {object} customerData (OPTIONAL) contains the customer data.
|
||||||
|
* @param {function} successCallback (OPTIONAL) If defined, this function is
|
||||||
|
* going to be executed on post success.
|
||||||
|
* @param {function} errorCallback (OPTIONAL) If defined, this function is
|
||||||
|
* going to be executed on post failure.
|
||||||
*/
|
*/
|
||||||
updateAppointment: function(appointmentData) {
|
updateAppointmentData : function(appointmentData, customerData,
|
||||||
// @task Save the appointment changes (ajax call).
|
successCallback, errorCallback) {
|
||||||
|
// :: MAKE AN AJAX CALL TO SERVER - STORE APPOINTMENT DATA
|
||||||
|
var postUrl = GlobalVariables.baseUrl + 'backend/ajax_save_appointment_changes';
|
||||||
|
|
||||||
|
var postData = {};
|
||||||
|
postData['appointment_data'] = JSON.stringify(appointmentData);
|
||||||
|
|
||||||
|
if (customerData !== undefined) {
|
||||||
|
postData['customer_data'] = JSON.stringify(customerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type : 'POST',
|
||||||
|
url : postUrl,
|
||||||
|
data : postData,
|
||||||
|
dataType : 'json',
|
||||||
|
success : function(response) {
|
||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
console.log('Update Appointment Data Response:', response);
|
||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
if (successCallback !== undefined) {
|
||||||
|
successCallback(response);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error : function(jqXHR, textStatus, errorThrown) {
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
//console.log('Update Appointment Data Error:', jqXHR, textStatus,
|
||||||
|
// errorThrown);
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
if (errorCallback !== undefined) {
|
||||||
|
errorCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()
|
||||||
|
*/
|
||||||
|
calendarEventResize : function(event, dayDelta, minuteDelta, revertFunc,
|
||||||
|
jsEvent, ui, view) {
|
||||||
|
|
||||||
|
// :: PREPARE THE APPOINTMENT DATA
|
||||||
|
var appointmentData = GeneralFunctions.clone(event.data);
|
||||||
|
|
||||||
|
// Must delete the following because only appointment data should be
|
||||||
|
// provided to the ajax call.
|
||||||
|
delete appointmentData['customer'];
|
||||||
|
delete appointmentData['provider'];
|
||||||
|
delete appointmentData['service'];
|
||||||
|
|
||||||
|
appointmentData['end_datetime'] = Date.parseExact(
|
||||||
|
appointmentData['end_datetime'], 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
.add({ minutes: minuteDelta })
|
||||||
|
.toString('yyyy-MM-dd HH:mm:ss');
|
||||||
|
|
||||||
|
// :: DEFINE THE SUCCESS CALLBACK FUNCTION
|
||||||
|
var successCallback = function(response) {
|
||||||
|
if (response.error) {
|
||||||
|
// Display error message to the user.
|
||||||
|
Backend.displayNotification(reponse.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display success notification to user.
|
||||||
|
var undoFunction = function() {
|
||||||
|
appointmentData['end_datetime'] = Date.parseExact(
|
||||||
|
appointmentData['end_datetime'], 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
.add({ minutes: -minuteDelta })
|
||||||
|
.toString('yyyy-MM-dd HH:mm:ss');
|
||||||
|
|
||||||
|
var postUrl = GlobalVariables.baseUrl
|
||||||
|
+ 'backend/ajax_save_appointment_changes';
|
||||||
|
var postData = {
|
||||||
|
'appointment_data' : JSON.stringify(appointmentData)
|
||||||
|
};
|
||||||
|
|
||||||
|
$.post(postUrl, postData, function(response) {
|
||||||
|
$('#notification').hide('blind');
|
||||||
|
revertFunc();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Backend.displayNotification('Appointment updated successfully!', [
|
||||||
|
{
|
||||||
|
'label' : 'Undo',
|
||||||
|
'function' : undoFunction
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
$('#footer').css('position', 'static'); // Footer position fix.
|
||||||
|
};
|
||||||
|
|
||||||
|
// :: UPDATE APPOINTMENT DATA VIA AJAX CALL
|
||||||
|
BackendCalendar.updateAppointmentData(appointmentData, undefined,
|
||||||
|
successCallback, undefined);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calendar Window "Resize" Callback
|
||||||
|
*
|
||||||
|
* The calendar element needs to be resized too in order to fit into the
|
||||||
|
* window. Nevertheless, if the window becomes very small the the calendar
|
||||||
|
* won't shrink anymore.
|
||||||
|
*
|
||||||
|
* @see getCalendarHeight()
|
||||||
|
*/
|
||||||
|
calendarWindowResize : function(view) {
|
||||||
|
$('#calendar').fullCalendar('option', 'height',
|
||||||
|
BackendCalendar.getCalendarHeight());
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calendar Day "Click" Callback
|
||||||
|
*
|
||||||
|
* When the user clicks on a day square on the calendar, then he will
|
||||||
|
* automatically be transfered to that day view calendar.
|
||||||
|
*/
|
||||||
|
calendarDayClick : function(date, allDay, jsEvent, view) {
|
||||||
|
if (allDay) {
|
||||||
|
// Switch to day view
|
||||||
|
$('#calendar').fullCalendar('gotoDate', date);
|
||||||
|
$('#calendar').fullCalendar('changeView', 'agendaDay');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
calendarEventClick : function(event, jsEvent, view) {
|
||||||
|
// Display a popover with the event details.
|
||||||
|
var html =
|
||||||
|
'<style type="text/css">'
|
||||||
|
+ '.popover-content strong {min-width: 80px; display:inline-block;}'
|
||||||
|
+ '.popover-content button {margin-right: 10px;}'
|
||||||
|
+ '</style>' +
|
||||||
|
'<strong>Start</strong> '
|
||||||
|
+ event.start.toString('dd/MM/yyyy HH:mm')
|
||||||
|
+ '<br>' +
|
||||||
|
'<strong>End</strong> '
|
||||||
|
+ event.end.toString('dd/MM/yyyy HH:mm')
|
||||||
|
+ '<br>' +
|
||||||
|
'<strong>Service</strong> '
|
||||||
|
+ event.title
|
||||||
|
+ '<br>' +
|
||||||
|
'<strong>Provider</strong> '
|
||||||
|
+ event.data['provider']['first_name'] + ' '
|
||||||
|
+ event.data['provider']['last_name']
|
||||||
|
+ '<br>' +
|
||||||
|
'<strong>Customer</strong> '
|
||||||
|
+ event.data['customer']['first_name'] + ' '
|
||||||
|
+ event.data['customer']['last_name']
|
||||||
|
+ '<hr>' +
|
||||||
|
'<center>' +
|
||||||
|
'<button class="edit-popover btn btn-primary">Edit</button>' +
|
||||||
|
'<button class="close-popover btn" data-po=' + jsEvent.target + '>Close</button>' +
|
||||||
|
'</center>';
|
||||||
|
|
||||||
|
$(jsEvent.target).popover({
|
||||||
|
placement : 'top',
|
||||||
|
title : event.title,
|
||||||
|
content : html,
|
||||||
|
html : true,
|
||||||
|
container : 'body',
|
||||||
|
trigger : 'manual'
|
||||||
|
});
|
||||||
|
|
||||||
|
BackendCalendar.lastFocusedEventData = event;
|
||||||
|
|
||||||
|
$(jsEvent.target).popover('show');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
calendarEventDrop : function(event, dayDelta, minuteDelta, allDay,
|
||||||
|
revertFunc, jsEvent, ui, view) {
|
||||||
|
// :: PREPARE THE APPOINTMENT DATA
|
||||||
|
var appointmentData = GeneralFunctions.clone(event.data);
|
||||||
|
|
||||||
|
// Must delete the following because only appointment data should be
|
||||||
|
// provided to the ajax call.
|
||||||
|
delete appointmentData['customer'];
|
||||||
|
delete appointmentData['provider'];
|
||||||
|
delete appointmentData['service'];
|
||||||
|
|
||||||
|
appointmentData['start_datetime'] = Date.parseExact(
|
||||||
|
appointmentData['start_datetime'], 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
.add({ days: dayDelta, minutes: minuteDelta })
|
||||||
|
.toString('yyyy-MM-dd HH:mm:ss');
|
||||||
|
|
||||||
|
appointmentData['end_datetime'] = Date.parseExact(
|
||||||
|
appointmentData['end_datetime'], 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
.add({ days: dayDelta, minutes: minuteDelta })
|
||||||
|
.toString('yyyy-MM-dd HH:mm:ss');
|
||||||
|
|
||||||
|
event.data['start_datetime'] = appointmentData['start_datetime'];
|
||||||
|
event.data['end_datetime'] = appointmentData['end_datetime'];
|
||||||
|
|
||||||
|
// :: DEFINE THE SUCCESS CALLBACK FUNCTION
|
||||||
|
var successCallback = function(response) {
|
||||||
|
if (response.error) {
|
||||||
|
// Display error message to the user.
|
||||||
|
Backend.displayNotification(reponse.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display success notification to user.
|
||||||
|
var undoFunction = function() {
|
||||||
|
appointmentData['start_datetime'] = Date.parseExact(
|
||||||
|
appointmentData['start_datetime'], 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
.add({ days: -dayDelta, minutes: -minuteDelta })
|
||||||
|
.toString('yyyy-MM-dd HH:mm:ss');
|
||||||
|
|
||||||
|
appointmentData['end_datetime'] = Date.parseExact(
|
||||||
|
appointmentData['end_datetime'], 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
.add({ days: -dayDelta, minutes: -minuteDelta })
|
||||||
|
.toString('yyyy-MM-dd HH:mm:ss');
|
||||||
|
|
||||||
|
event.data['start_datetime'] = appointmentData['start_datetime'];
|
||||||
|
event.data['end_datetime'] = appointmentData['end_datetime'];
|
||||||
|
|
||||||
|
var postUrl = GlobalVariables.baseUrl
|
||||||
|
+ 'backend/ajax_save_appointment_changes';
|
||||||
|
var postData = {
|
||||||
|
'appointment_data' : JSON.stringify(appointmentData)
|
||||||
|
};
|
||||||
|
|
||||||
|
$.post(postUrl, postData, function(response) {
|
||||||
|
$('#notification').hide('blind');
|
||||||
|
revertFunc();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Backend.displayNotification('Appointment updated successfully!', [
|
||||||
|
{
|
||||||
|
'label' : 'Undo',
|
||||||
|
'function' : undoFunction
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
$('#footer').css('position', 'static'); // Footer position fix.
|
||||||
|
};
|
||||||
|
|
||||||
|
// :: UPDATE APPOINTMENT DATA VIA AJAX CALL
|
||||||
|
BackendCalendar.updateAppointmentData(appointmentData, undefined,
|
||||||
|
successCallback, undefined);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calendar "View Display" Callback
|
||||||
|
*
|
||||||
|
* Whenever the calendar changes or refreshes its view certain actions
|
||||||
|
* need to be made, in order to display proper information to the user.
|
||||||
|
*/
|
||||||
|
calendarViewDisplay : function(view) {
|
||||||
|
// Place the footer into correct position because the calendar
|
||||||
|
// height might change.
|
||||||
|
BackendCalendar.refreshCalendarAppointments(
|
||||||
|
$('#calendar'),
|
||||||
|
$('#select-filter-item').val(),
|
||||||
|
$('#select-filter-item option:selected').attr('type'),
|
||||||
|
$('#calendar').fullCalendar('getView').visStart,
|
||||||
|
$('#calendar').fullCalendar('getView').visEnd);
|
||||||
|
$(window).trigger('resize');
|
||||||
|
|
||||||
|
$('.fv-events').each(function(index, eventHandle) {
|
||||||
|
$(eventHandle).popover();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -119,5 +119,49 @@ var GeneralFunctions = {
|
||||||
+ pad(dt.getUTCHours())+':'
|
+ pad(dt.getUTCHours())+':'
|
||||||
+ pad(dt.getUTCMinutes())+':'
|
+ pad(dt.getUTCMinutes())+':'
|
||||||
+ pad(dt.getUTCSeconds())+'Z'
|
+ pad(dt.getUTCSeconds())+'Z'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method creates and returns an exact copy of the provided object.
|
||||||
|
* It is very usefull whenever changes need to be made to an object without
|
||||||
|
* modyfing the original data.
|
||||||
|
*
|
||||||
|
* @link http://stackoverflow.com/questions/728360/most-elegant-way-to-clone-a-javascript-object
|
||||||
|
*
|
||||||
|
* @param {object} originalObject Object to be copied.
|
||||||
|
* @returns {object} Returns an exact copy of the provided element.
|
||||||
|
*/
|
||||||
|
clone: function(originalObject) {
|
||||||
|
// Handle the 3 simple types, and null or undefined
|
||||||
|
if (null == originalObject || "object" != typeof originalObject)
|
||||||
|
return originalObject;
|
||||||
|
|
||||||
|
// Handle Date
|
||||||
|
if (originalObject instanceof Date) {
|
||||||
|
var copy = new Date();
|
||||||
|
copy.setTime(originalObject.getTime());
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Array
|
||||||
|
if (originalObject instanceof Array) {
|
||||||
|
var copy = [];
|
||||||
|
for (var i = 0, len = originalObject.length; i < len; i++) {
|
||||||
|
copy[i] = GeneralFunctions.clone(originalObject[i]);
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Object
|
||||||
|
if (originalObject instanceof Object) {
|
||||||
|
var copy = {};
|
||||||
|
for (var attr in originalObject) {
|
||||||
|
if (originalObject.hasOwnProperty(attr))
|
||||||
|
copy[attr] = GeneralFunctions.clone(originalObject[attr]);
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unable to copy obj! Its type isn't supported.");
|
||||||
}
|
}
|
||||||
}
|
};
|
2103
src/assets/js/libs/jquery/jquery-ui-timepicker-addon.js
vendored
Normal file
2103
src/assets/js/libs/jquery/jquery-ui-timepicker-addon.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue