mirror of
https://github.com/alextselegidis/easyappointments.git
synced 2024-11-22 07:52:29 +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.
|
||||
- Display error message to users.
|
||||
- Includes complete Google Sync protocol document.
|
||||
- Customers can book appointments only for the available hours.
|
||||
- Generation of code documentation.
|
||||
- Customers can edit the application through unique links (from their emails).
|
||||
- Minor Fixes
|
||||
Main
|
||||
|
||||
- First backend-calendar page implementation (not complete, admin's perspective).
|
||||
- Javascript Google API usage from the customer's perspective.
|
||||
- Backend google calendar authentication Process.
|
||||
- Sync every appointment change made from E!A to Google Calendar.
|
||||
- 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() {
|
||||
|
||||
echo '<h1>Not implemented yet.</h1>';
|
||||
}
|
||||
|
||||
public function services() {
|
||||
|
||||
echo '<h1>Not implemented yet.</h1>';
|
||||
}
|
||||
|
||||
public function providers() {
|
||||
|
||||
echo '<h1>Not implemented yet.</h1>';
|
||||
}
|
||||
|
||||
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).
|
||||
*
|
||||
* @param {numeric} $_POST['record_id'] Selected record id.
|
||||
* @param {string} $_POST['filter_type'] Could be either FILTER_TYPE_PROVIDER or
|
||||
* FILTER_TYPE_SERVICE.
|
||||
* @param {string} $_POST['filter_type'] Could be either FILTER_TYPE_PROVIDER
|
||||
* or FILTER_TYPE_SERVICE.
|
||||
* @param {string} $_POST['start_date'] The user selected start date.
|
||||
* @param {string} $_POST['end_date'] The user selected end date.
|
||||
*/
|
||||
|
@ -86,6 +86,40 @@ class Backend extends CI_Controller {
|
|||
|
||||
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 */
|
||||
|
|
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"
|
||||
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"
|
||||
src="<?php echo $base_url; ?>assets/js/backend_calendar.js"></script>
|
||||
|
||||
|
@ -39,9 +42,15 @@
|
|||
Enable Sync
|
||||
</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"
|
||||
title="During unavailalbe period the provider won't accept new appointments.">
|
||||
<i class="icon-plus"></i>
|
||||
<i class="icon-ban-circle"></i>
|
||||
Unavailable
|
||||
</button>
|
||||
</div>
|
||||
|
@ -62,6 +71,8 @@
|
|||
<fieldset>
|
||||
<legend>Appointment Details</legend>
|
||||
|
||||
<input id="appointment-id" type="hidden" />
|
||||
|
||||
<div class="control-group">
|
||||
<label for="select-service" class="control-label">Service</label>
|
||||
<div class="controls">
|
||||
|
@ -77,9 +88,16 @@
|
|||
</div>
|
||||
|
||||
<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">
|
||||
<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>
|
||||
</fieldset>
|
||||
|
@ -87,6 +105,8 @@
|
|||
<fieldset>
|
||||
<legend>Customer Details</legend>
|
||||
|
||||
<input id="customer-id" type="hidden" />
|
||||
|
||||
<div class="control-group">
|
||||
<label for="first-name" class="control-label">First Name</label>
|
||||
<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"
|
||||
type="text/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
|
||||
// ------------------------------------------------------------
|
||||
|
@ -104,3 +108,4 @@
|
|||
|
||||
</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; }
|
||||
|
||||
#notification strong { margin-right: 15px; }
|
||||
|
||||
/* BACKEND CALENDAR PAGE
|
||||
-------------------------------------------------------------------- */
|
||||
#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;
|
||||
font-weight: bold; font-size: 18px; }
|
||||
#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; }
|
||||
|
||||
/* 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'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
var BackendCalendar = {
|
||||
// :: NAMESPACE CONSTANTS
|
||||
FILTER_TYPE_PROVIDER : 'provider',
|
||||
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
|
||||
|
@ -17,7 +19,7 @@ var BackendCalendar = {
|
|||
* @param {bool} defaultEventHandlers (OPTIONAL = TRUE) Determines whether the
|
||||
* default event handlers will be set for the current page.
|
||||
*/
|
||||
initialize: function(defaultEventHandlers) {
|
||||
initialize : function(defaultEventHandlers) {
|
||||
if (defaultEventHandlers === undefined) defaultEventHandlers = true;
|
||||
|
||||
// :: INITIALIZE THE DOM ELEMENTS OF THE PAGE
|
||||
|
@ -25,6 +27,7 @@ var BackendCalendar = {
|
|||
defaultView : 'agendaWeek',
|
||||
height : BackendCalendar.getCalendarHeight(),
|
||||
editable : true,
|
||||
slotMinutes : 15,
|
||||
columnFormat : {
|
||||
month : 'ddd',
|
||||
week : 'ddd d/M',
|
||||
|
@ -40,84 +43,20 @@ var BackendCalendar = {
|
|||
center : 'title',
|
||||
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({
|
||||
placement : 'top',
|
||||
title : event.title,
|
||||
content : html,
|
||||
html : true,
|
||||
container : 'body',
|
||||
trigger : 'manual'
|
||||
});
|
||||
|
||||
$(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();
|
||||
});
|
||||
}
|
||||
// Calendar events need to be declared on initialization.
|
||||
windowResize : BackendCalendar.calendarWindowResize,
|
||||
viewDisplay : BackendCalendar.calendarViewDisplay,
|
||||
dayClick : BackendCalendar.calendarDayClick,
|
||||
eventClick : BackendCalendar.calendarEventClick,
|
||||
eventResize : BackendCalendar.calendarEventResize,
|
||||
eventDrop : BackendCalendar.calendarEventDrop
|
||||
});
|
||||
|
||||
// Trigger once to set the proper footer position after calendar
|
||||
// initialization.
|
||||
BackendCalendar.calendarWindowResize();
|
||||
|
||||
// :: FILL THE SELECT ELEMENTS OF THE PAGE
|
||||
var optgroupHtml = '<optgroup label="Providers">';
|
||||
$.each(GlobalVariables.availableProviders, function(index, provider) {
|
||||
|
@ -131,8 +70,8 @@ var BackendCalendar = {
|
|||
optgroupHtml = '<optgroup label="Services">';
|
||||
$.each(GlobalVariables.availableServices, function(index, service) {
|
||||
optgroupHtml += '<option value="' + service['id'] + '" ' +
|
||||
'type="' + BackendCalendar.FILTER_TYPE_SERVICE + '">'
|
||||
+ service['name'] + '</option>';
|
||||
'type="' + BackendCalendar.FILTER_TYPE_SERVICE + '">' +
|
||||
service['name'] + '</option>';
|
||||
});
|
||||
optgroupHtml += '</optgroup>';
|
||||
$('#select-filter-item').append(optgroupHtml)
|
||||
|
@ -149,9 +88,9 @@ var BackendCalendar = {
|
|||
* page. If you do not need the default handlers then initialize the page
|
||||
* 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
|
||||
* display them on the calendar.
|
||||
|
@ -164,11 +103,20 @@ var BackendCalendar = {
|
|||
$('#calendar').fullCalendar('getView').visStart,
|
||||
$('#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.
|
||||
*/
|
||||
|
@ -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.
|
||||
*/
|
||||
$(document).on('click', '.edit-popover', function() {
|
||||
$(this).parents().eq(2).remove(); // Hide the popover
|
||||
|
||||
var appointmentData = BackendCalendar.lastFocusedEvent.data;
|
||||
var appointmentData = BackendCalendar.lastFocusedEventData.data;
|
||||
var modalHandle = $('#manage-appointment');
|
||||
|
||||
// :: APPLY APPOINTMENT DATA AND SHOW TO MODAL DIALOG
|
||||
modalHandle.find('input, textarea').val('');
|
||||
modalHandle.find('#appointment-id').val(appointmentData['id']);
|
||||
|
||||
// Fill the services listbox and select the appointment service.
|
||||
$.each(GlobalVariables.availableServices, function(index, service) {
|
||||
|
@ -220,7 +169,25 @@ var BackendCalendar = {
|
|||
|
||||
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'];
|
||||
modalHandle.find('#customer-id').val(appointmentData['id_users_customer']);
|
||||
modalHandle.find('#first-name').val(customerData['first_name']);
|
||||
modalHandle.find('#last-name').val(customerData['last_name']);
|
||||
modalHandle.find('#email').val(customerData['email']);
|
||||
|
@ -228,16 +195,14 @@ var BackendCalendar = {
|
|||
modalHandle.find('#address').val(customerData['address']);
|
||||
modalHandle.find('#city').val(customerData['city']);
|
||||
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
|
||||
$('#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.
|
||||
*/
|
||||
|
@ -246,16 +211,108 @@ var BackendCalendar = {
|
|||
});
|
||||
|
||||
/**
|
||||
* Event: Manage Appointments Dialog Save Button "Clicked"
|
||||
* Event: Manage Appointments Dialog Save Button "Click"
|
||||
*
|
||||
* Stores the appointment changes.
|
||||
*/
|
||||
$('#manage-appointment #save-button').click(function() {
|
||||
// :: 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
|
||||
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.
|
||||
*/
|
||||
getCalendarHeight: function () {
|
||||
getCalendarHeight : function () {
|
||||
var result = window.innerHeight - $('#footer').height() - $('#header').height()
|
||||
- $('#calendar-toolbar').height() - 80; // 80 for fine tuning
|
||||
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 {type} endDate Visible end date of the calendar.
|
||||
*/
|
||||
refreshCalendarAppointments: function(calendarHandle, recordId, filterType,
|
||||
refreshCalendarAppointments : function(calendarHandle, recordId, filterType,
|
||||
startDate, endDate) {
|
||||
var ajaxUrl = GlobalVariables.baseUrl + 'backend/ajax_get_calendar_appointments';
|
||||
var postUrl = GlobalVariables.baseUrl + 'backend/ajax_get_calendar_appointments';
|
||||
|
||||
var postData = {
|
||||
record_id : recordId,
|
||||
|
@ -293,7 +350,7 @@ var BackendCalendar = {
|
|||
filter_type : filterType
|
||||
};
|
||||
|
||||
$.post(ajaxUrl, postData, function(response) {
|
||||
$.post(postUrl, postData, function(response) {
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//console.log('Refresh Calendar Appointments Response :', response);
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
@ -327,8 +384,295 @@ var BackendCalendar = {
|
|||
* @param {object} appointmentData Contain the new appointment data. The
|
||||
* id of the appointment MUST be already included. The rest values must
|
||||
* 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) {
|
||||
// @task Save the appointment changes (ajax call).
|
||||
updateAppointmentData : function(appointmentData, customerData,
|
||||
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.getUTCMinutes())+':'
|
||||
+ 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