Αλλαγές στα υπάρχον αρχεία που σχετίζονται με την κράτηση ραντεβού έτσι ώστε να είναι εφικτή η δημιουργία link, τα οποία θα επιτρέπουν στους πελάτες να πραγματοποιούν αλλαγές στα ραντεβού που έχουν καταχωρήσει.

This commit is contained in:
alextselegidis@gmail.com 2013-06-03 14:42:19 +00:00
parent 2112164b4a
commit f9a6b20052
16 changed files with 3552 additions and 180 deletions

View File

@ -1,53 +1,146 @@
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Appointments extends CI_Controller {
/**
* This page displays the book appointment wizard
* for the customers.
*/
public function index() {
if (strtoupper($_SERVER['REQUEST_METHOD']) != 'POST') {
// Display the appointment booking page to the customer.
// Get business name.
/**
* Default callback method of the application.
*
* This method creates the appointment book wizard. If an appointment hash
* is provided then it means that the customer followed the appointment
* manage link that was send with the book success email.
*
* @param string $appointment_hash The db appointment hash of an existing
* record.
*/
public function index($appointment_hash = '') {
if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') {
$this->load->model('Settings_Model');
$view_data['company_name'] = $this->Settings_Model->get_setting('company_name');
// Get the available services and providers.
$this->load->model('Services_Model');
$view_data['available_services'] = $this->Services_Model->get_available_services();
$this->load->model('Providers_Model');
$view_data['available_providers'] = $this->Providers_Model->get_available_providers();
$company_name = $this->Settings_Model->get_setting('company_name');
$available_services = $this->Services_Model->get_available_services();
$available_providers = $this->Providers_Model->get_available_providers();
// If an appointment hash is provided then it means that the customer
// is trying to edit a registered record.
if ($appointment_hash !== ''){
// Load the appointments data and set the manage mode of the page.
$this->load->model('Appointments_Model');
$this->load->model('Customers_Model');
$manage_mode = TRUE;
$appointment_data = $this->Appointments_Model
->get_batch(array('hash' => $appointment_hash))[0];
$provider_data = $this->Providers_Model
->get_row($appointment_data['id_users_provider']);
$customer_data = $this->Customer_Model
->get_row($appointment_data['id_users_customer']);
} else {
// The customer is going to book an appointment so there is no
// need for the manage functionality to be initialized.
$manage_mode = false;
$appointment_data = array();
$provider_data = array();
$customer_data = array();
}
// Load the book appointment view.
$view_data = array (
'available_services' => $available_services,
'available_providers' => $available_providers,
'company_name' => $company_name,
'manage_mode' => $manage_mode,
'appointment_data' => $appointment_data,
'provider_data' => $provider_data,
'customer_data' => $customer_data
);
$this->load->view('appointments/book', $view_data);
} else {
$post_data = json_decode($_POST['post_data'], true);
// Add customer
// The page is a post-back. Register the appointment and send
// notification emails to the provider and the customer that are
// related to the appointment.
$post_data = json_decode($_POST['post_data'], true);
$appointment_data = $post_data['appointment'];
$customer_data = $post_data['customer'];
$this->load->model('Customers_Model');
$customer_id = $this->Customers_Model->add($post_data['customer']);
// Add appointment
$post_data['appointment']['id_users_customer'] = $customer_id;
$this->load->model('Appointments_Model');
$view_data['appointment_id'] = $this->Appointments_Model->add($post_data['appointment']);
$customer_id = $this->Customers_Model->add($customer_data);
$appointment_data['id_users_customer'] = $customer_id;
$appointment_data['id'] = $this->Appointments_Model->add($appointment_data);
$appointment_data['hash'] = $this->Appointments_Model
->get_value('hash', $appointment_data['id']);
// Send an email to the customer with the appointment info.
$this->load->library('Notifications');
try {
$this->notifications->send_book_success($post_data['customer'], $post_data['appointment']);
$this->notifications->send_new_appointment($post_data['customer'], $post_data['appointment']);
$this->notifications->send_book_success($customer_data, $appointment_data);
$this->notifications->send_new_appointment($customer_data, $appointment_data);
} catch (NotificationException $not_exc) {
$view_data['notification_error'] = '<br><br><pre>An unexpected error occured while sending '
. 'you an email. Please backup the appointment details so that you can restore them '
. 'later. <br><br>Error:<br>' . $not_exc->getMessage() . '</pre>';
$view_data['notification_error'] = '<br><br>'
. '<pre>An unexpected error occured while sending you an '
. 'email. Please backup the appointment details so that '
. 'you can restore them later. <br><br>Error: <br>'
. $not_exc->getMessage() . '</pre>';
}
// Load the book appointment view.
$view_data['appointment_id'] = $appointment_data['id'];
$this->load->view('appointments/book_success', $view_data);
}
}
/**
* Display the view - manage screen of an external
* appointment link.
*
* This method loads the page that is going to be displayed
* whenever a customer, or a provider clicks on the email link
* that enables him to preview and make changes to the selected
* appointment.
*
* @param string $user_type Link user type (one of the
* DB_SLUG_CUSTOMER, ... constants).
* @param string $appointment_hash the appointment db hash. This
* is used to identify the appointment record.
*
*/
public function external_link($user_type, $appointment_hash) {
if (strtoupper($_SERVER['REQUEST_METHOD']) != 'POST') {
// Prepare and display the external manage view page.
$this->load->model('Appointments_Model');
$this->load->model('Providers_Model');
$this->load->model('Customers_Model');
$this->load->model('Services_Model');
$appointment_data = $this->Appointments_Model
->get_batch(array('hash' => $appointment_hash))[0];
$provider_data = $this->Providers_Model
->get_row($appointment_data['id_users_provider']);
$customer_data = $this->Customers_Model
->get_row($appointment_data['id_users_customer']);
$available_providers = $this->Providers_Model->get_available_providers();
$available_services = $this->Services_Model->get_available_services();
$view_data = array(
'appointment_data' => $appointment_data,
'customer_data' => $customer_data,
'provider_data' => $provider_data,
'user_type' => $user_type,
'available_providers' => $available_providers,
'available_services' => $available_services
);
$this->load->view('appointments/external_manage', $view_data);
} else {
// The external manage page was posted back. Save the
// changes of the user.
// @task Save user changes.
}
}
}
/**
* [AJAX] Get the available appointment hours for the given date.
@ -69,12 +162,20 @@ class Appointments extends CI_Controller {
$working_plan = json_decode($this->Providers_Model
->get_value('working_plan', $_POST['provider_id']), true);
$reserved_appointments = $this->Appointments_Model->get_batch(
array(
'DATE(start_datetime)' => date('Y-m-d', strtotime($_POST['selected_date'])),
'id_users_provider' => $_POST['provider_id'],
'id_services' => $_POST['service_id']
));
$where_clause = array(
'DATE(start_datetime)' => date('Y-m-d', strtotime($_POST['selected_date'])),
'id_users_provider' => $_POST['provider_id'],
'id_services' => $_POST['service_id']
);
if ($_POST['manage_mode']) {
// Current record id shouldn't be included as reserved time,
// whent the manage mode is true.
$where_clause['id !='] = $_POST['appointment_id'];
}
$reserved_appointments = $this->Appointments_Model->get_batch($where_clause);
// Find the empty spaces on the plan. The first split between
// the plan is due to a break (if exist). After that every reserved
@ -109,7 +210,7 @@ class Appointments extends CI_Controller {
'end' => $sel_date_working_plan['end']
);
}
// PROBLEM
// Break the empty spaces with the reserved appointments.
$empty_spaces_with_appointments = array();
if (count($reserved_appointments) > 0) {
@ -133,10 +234,27 @@ class Appointments extends CI_Controller {
'end' => $space_end
);
} else {
$empty_spaces_with_appointments[] = array(
'start' => $space_start,
'end' => $space_end
);
// Check if there are any other appointments between this
// time space. If not, it is going to be added as it is.
$found = FALSE;
foreach($reserved_appointments as $appt) {
$appt_start = date('H:i', strtotime($appt['start_datetime']));
$appt_end = date('H:i', strtotime($appt['end_datetime']));
if ($space_start < $appt_start && $space_end > $appt_end) {
$found = TRUE;
}
}
// It is also necessary to check that this time period doesn't
// already exist in the "$empty_spaces_with_appointments" array.
$empty_space = array(
'start' => $space_start,
'end' => $space_end
);
$already_exist = in_array($empty_space, $empty_spaces_with_appointments);
if ($found === FALSE && $already_exist === FALSE) {
$empty_spaces_with_appointments[] = $empty_space;
}
}
}
}

View File

@ -69,7 +69,7 @@ class Unit_tests extends CI_Driver_Library {
*/
public function run_library_tests($output_report = true) {
// @task Implement unit tests for the libraries.
if ($output_report) {
$this->CI->output->append_output($this->CI->unit->report());
}

View File

@ -74,8 +74,9 @@ class Unit_tests_appointments_model extends CI_Driver {
// Check if the record is the one that was inserted.
$db_data = $this->CI->db->get_where('ea_appointments', array('id' => $appointment_data['id']))->row_array();
unset($db_data['hash']); // This should not be included because is generate when the record is inserted.
$this->CI->unit->run($appointment_data, $db_data, 'Test if add() appointment (insert operation) has successfully inserted a record.');
// Delete inserted record.
$this->CI->db->delete('ea_appointments', array('id' => $appointment_data['id']));
}
@ -355,7 +356,7 @@ class Unit_tests_appointments_model extends CI_Driver {
$appointment_data = array(
'start_datetime' => '2013-05-01 12:30:00',
'end_datetime' => '2013-05-01 13:00:00',
'notes' => 'Some notes right here...',
'notes' => 'Some notes right here...',
'id_users_provider' => $this->provider_id,
'id_users_customer' => $this->customer_id,
'id_services' => $this->service_id
@ -407,6 +408,7 @@ class Unit_tests_appointments_model extends CI_Driver {
'start_datetime' => '2013-05-01 12:30:00',
'end_datetime' => '2013-05-01 13:00:00',
'notes' => 'Some notes right here...',
'hash' => '91de2d31f5cbb6d26a5b1b3e710d38d1',
'id_users_provider' => $this->provider_id,
'id_users_customer' => $this->customer_id,
'id_services' => $this->service_id

View File

@ -120,7 +120,7 @@ class Unit_tests_customers_model extends CI_Driver {
$has_thrown_exception = TRUE;
}
$this->CI->unit->run($has_thrown_exception, TRUE, 'Test add() customer with invalid email address');
$this->CI->unit->run($has_thrown_exception, TRUE, 'Test add() customer with invalid email address.');
}
private function test_add_missing_no_last_name() {

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ require_once dirname(__FILE__) . '/external/class.phpmailer.php';
* on the system.
*
* Custom system settings for the notification section are loaded
* during the execution of the class methods.
* during the execution of each class methods.
*/
class Notifications {
private $CI;
@ -29,8 +29,8 @@ class Notifications {
* @param array $replace_array Array that contains the variables
* to be replaced.
* @param string $email_html The email template hmtl.
* @return string Returns the new email html that contain the variables
* of the $replace_array.
* @return string Returns the new email html that contain the
* variables of the $replace_array.
*/
private function replace_template_variables($replace_array, $email_html) {
foreach($replace_array as $var=>$value) {
@ -58,17 +58,20 @@ class Notifications {
$this->CI->load->model('Services_Model');
$this->CI->load->model('Settings_Model');
$provider_data = $this->CI->Providers_Model->get_row($appointment_data['id_users_provider']);
$service_data = $this->CI->Services_Model->get_row($appointment_data['id_services']);
$provider_data = $this->CI->Providers_Model
->get_row($appointment_data['id_users_provider']);
$service_data = $this->CI->Services_Model
->get_row($appointment_data['id_services']);
$replace_array = array(
'$appointment_service' => $service_data['name'],
'$appointment_service' => $service_data['name'],
'$appointment_provider' => $provider_data['first_name'] . ' ' . $provider_data['last_name'],
'$appointment_date' => date('d/m/Y H:i', strtotime($appointment_data['start_datetime'])),
'$appointment_date' => date('d/m/Y H:i', strtotime($appointment_data['start_datetime'])),
'$appointment_duration' => $service_data['duration'] . ' minutes',
'$company_link' => $this->CI->Settings_Model->get_setting('company_link'),
'$company_name' => $this->CI->Settings_Model->get_setting('company_name'),
'$customer_name' => $customer_data['first_name'] . ' ' . $customer_data['last_name']
'$appointment_link' => $this->CI->config->item('base_url') . $appointment_data['hash'],
'$company_link' => $this->CI->Settings_Model->get_setting('company_link'),
'$company_name' => $this->CI->Settings_Model->get_setting('company_name'),
'$customer_name' => $customer_data['first_name'] . ' ' . $customer_data['last_name']
);
$email_html = file_get_contents(dirname(dirname(__FILE__)) . '/views/emails/book_success.php');
@ -105,7 +108,6 @@ class Notifications {
* @return bool Returns the operation result.
*/
public function send_new_appointment($customer_data, $appointment_data) {
$this->CI =& get_instance();
$this->CI->load->model('Providers_Model');
$this->CI->load->model('Services_Model');
$this->CI->load->model('Settings_Model');
@ -114,31 +116,33 @@ class Notifications {
$service_data = $this->CI->Services_Model->get_row($appointment_data['id_services']);
$replace_array = array(
'$appointment_service' => $service_data['name'],
'$appointment_service' => $service_data['name'],
'$appointment_provider' => $provider_data['first_name'] . ' ' . $provider_data['last_name'],
'$appointment_date' => date('d/m/Y H:i', strtotime($appointment_data['start_datetime'])),
'$appointment_date' => date('d/m/Y H:i', strtotime($appointment_data['start_datetime'])),
'$appointment_duration' => $service_data['duration'] . ' minutes',
'$company_link' => $this->CI->Settings_Model->get_setting('company_link'),
'$company_name' => $this->CI->Settings_Model->get_setting('company_name'),
'$customer_name' => $customer_data['first_name'] . ' ' . $customer_data['last_name'],
'$customer_email' => $customer_data['email'],
'$customer_phone' => $customer_data['phone_number'],
'$customer_address' => $customer_data['address']
'$appointment_link' => $this->CI->config->item('base_url') . 'appointments/admin/' . $appointment_data['hash'],
'$company_link' => $this->CI->Settings_Model->get_setting('company_link'),
'$company_name' => $this->CI->Settings_Model->get_setting('company_name'),
'$customer_name' => $customer_data['first_name'] . ' ' . $customer_data['last_name'],
'$customer_email' => $customer_data['email'],
'$customer_phone' => $customer_data['phone_number'],
'$customer_address' => $customer_data['address']
);
$email_html = file_get_contents(dirname(dirname(__FILE__)) . '/views/emails/new_appointment.php');
$email_html = file_get_contents(dirname(dirname(__FILE__))
. '/views/emails/new_appointment.php');
$email_html = $this->replace_template_variables($replace_array, $email_html);
$mail = new PHPMailer();
$mail->From = $this->CI->Settings_Model->get_setting('company_email');
$mail->FromName = $this->CI->Settings_Model->get_setting('company_name');;
$mail->AddAddress($provider_data['email']); // Do not use the name argument, phpmailer crushes.
$mail->AddAddress($provider_data['email']); // "Name" argument crushes the phpmailer class.
$mail->IsHTML(true);
$mail->CharSet = 'UTF-8';
$mail->Subject = 'New Appointment';
$mail->Body = $email_html;
if(!$mail->Send()) {
if (!$mail->Send()) {
throw new NotificationException('Email could not been sent. '
. 'Mailer Error (Line ' . __LINE__ . '): ' . $mail->ErrorInfo);
}

View File

@ -83,6 +83,7 @@ class Appointments_Model extends CI_Model {
* @return int Returns the id of the new record.
*/
private function insert($appointment_data) {
$appointment_data['hash'] = $this->generate_hash();
if (!$this->db->insert('ea_appointments', $appointment_data)) {
throw new DatabaseException('Could not insert appointment record.');
}
@ -287,6 +288,21 @@ class Appointments_Model extends CI_Model {
return $this->db->get('ea_appointments')->result_array();
}
/**
* Generate a unique hash for the given appointment data.
*
* This method uses the current date-time to generate a unique
* hash string that is later used to identify this appointment.
* Hash is needed when the email is send to the user with an
* edit link.
*
* @return string Returns the unique appointment hash.
*/
public function generate_hash() {
$current_date = new DateTime();
return md5($current_date->getTimestamp());
}
}
/* End of file appointments_model.php */

View File

@ -179,7 +179,6 @@ class Customers_Model extends CI_Model {
return TRUE;
} catch (Exception $exc) {
echo $exc->getMessage();
return FALSE;
}
}

View File

@ -3,7 +3,10 @@
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<?php // INCLUDE CSS FILES ?>
<?php
// ------------------------------------------------------------
// INCLUDE CSS FILES
// ------------------------------------------------------------ ?>
<link
rel="stylesheet"
type="text/css"
@ -25,7 +28,10 @@
type="text/css"
href="<?php echo $this->config->base_url(); ?>assets/css/style.css">
<?php // INCLUDE JS FILES ?>
<?php
// ------------------------------------------------------------
// INCLUDE JAVASCRIPT FILES
// ------------------------------------------------------------ ?>
<script
type="text/javascript"
src="<?php echo $this->config->base_url(); ?>assets/js/libs/jquery/jquery.min.js">
@ -55,37 +61,50 @@
src="<?php echo $this->config->base_url(); ?>assets/js/general_functions.js">
</script>
<?php // SET FAVICON FOR PAGE ?>
<?php
// ------------------------------------------------------------
// WEBPAGE FAVICON
// ------------------------------------------------------------ ?>
<link
rel="icon"
type="image/x-icon"
href="<?php echo $this->config->base_url(); ?>assets/images/favicon.ico">
<?php // JS GLOBAL VARIABLE DECLARATION ?>
<?php
// ------------------------------------------------------------
// VIEW FILE JAVASCRIPT CODE
// ------------------------------------------------------------ ?>
<script type="text/javascript">
// Define some global variables.
GlobalVariables = {
services : <?php echo json_encode($available_services); ?>,
providers : <?php echo json_encode($available_providers); ?>,
baseUrl : <?php echo '"' . $this->config->base_url() . '"'; ?>
}
</script>
<?php // JQUERY PAGE STUFF ?>
<script type="text/javascript">
availableServices : <?php echo json_encode($available_services); ?>,
availableProviders : <?php echo json_encode($available_providers); ?>,
baseUrl : <?php echo '"' . $this->config->base_url() . '"'; ?>,
manageMode : <?php echo ($manage_mode) ? 'true' : 'false'; ?>,
appointmentData : <?php echo json_encode($appointment_data); ?>,
providerData : <?php echo json_encode($provider_data); ?>,
customerData : <?php echo json_encode($customer_data); ?>
};
$(document).ready(function() {
bookAppointment.initialize(true);
GeneralFunctions.centerElementOnPage($('#book-appointment'));
bookAppointment.initialize(true, GlobalVariables.manageMode);
GeneralFunctions.centerElementOnPage($('#book-appointment-wizard'));
});
</script>
</head>
<body>
<div id="main" class="container">
<div id="book-appointment">
<div id="top-bar">
<span id="business-name"><?php echo $company_name; ?></span>
<div id="book-steps">
<div id="book-appointment-wizard">
<?php
// ------------------------------------------------------
// FRAME TOP BAR
// ------------------------------------------------------ ?>
<div id="header">
<span id="company-name"><?php echo $company_name; ?></span>
<div id="steps">
<div id="step-1" class="book-step active-step" title="Select Service & Provider">
<strong>1</strong>
</div>
@ -101,13 +120,36 @@
</div>
</div>
</div>
<?php // SELECT SERVICE AND PROVIDER ?>
<div id="book-appointment-1" class="book-appoinment-step">
<div class="step-frame">
<h2 class="step-title">Select Service & Provider</h2>
<div class="step-content" style="width:270px">
<label for="select-service"><strong>Select Service</strong></label>
<?php
// ------------------------------------------------------
// CANCEL APPOINTMENT BUTTON
// ------------------------------------------------------
if ($manage_mode === TRUE) {
echo '
<div id="cancel-appointment-frame">
<p>
Press the "Cancel" button to remove the appointment
from the company schedule.
</p>
<button id="cancel-appointment" class="button">Cancel</button>
</div>';
}
?>
<?php
// ------------------------------------------------------
// SELECT SERVICE AND PROVIDER
// ------------------------------------------------------ ?>
<div id="wizard-frame-1" class="wizard-frame">
<div class="frame-container">
<h2 class="frame-title">Select Service & Provider</h2>
<div class="frame-content" style="width:270px">
<label for="select-service">
<strong>Select Service</strong>
</label>
<select id="select-service">
<?php
foreach($available_services as $service) {
@ -117,7 +159,10 @@
?>
</select>
<label for="select-provider"><strong>Select Provider</strong></label>
<label for="select-provider">
<strong>Select Provider</strong>
</label>
<select id="select-provider"></select>
</div>
</div>
@ -128,11 +173,16 @@
</div>
</div>
<?php // APPOINTMENT DATE ?>
<div id="book-appointment-2" class="book-appoinment-step" style="display:none;">
<div class="step-frame">
<h2 class="step-title">Select Appointment Date And Time</h2>
<div class="step-content" style="width:600px">
<?php
// ------------------------------------------------------
// SELECT APPOINTMENT DATE
// ------------------------------------------------------ ?>
<div id="wizard-frame-2" class="wizard-frame" style="display:none;">
<div class="frame-container">
<h2 class="frame-title">Select Appointment Date And Time</h2>
<div class="frame-content" style="width:600px">
<div class="span3">
<div id="select-date"></div>
</div>
@ -152,11 +202,16 @@
</div>
</div>
<?php // CUSTOMER'S INFO ?>
<div id="book-appointment-3" class="book-appoinment-step" style="display:none;">
<div class="step-frame">
<h2 class="step-title">Fill In Your Information</h2>
<div class="step-content" style="width:600px">
<?php
// ------------------------------------------------------
// ENTER CUSTOMER DATA
// ------------------------------------------------------ ?>
<div id="wizard-frame-3" class="wizard-frame" style="display:none;">
<div class="frame-container">
<h2 class="frame-title">Fill In Your Information</h2>
<div class="frame-content" style="width:600px">
<div class="span3">
<label for="last-name">Last Name *</label>
<input type="text" id="last-name" class="required" maxlength="250" />
@ -198,11 +253,14 @@
</div>
</div>
<?php // CONFIRMATION STEP ?>
<div id="book-appointment-4" class="book-appoinment-step" style="display:none;">
<div class="step-frame">
<h2 class="step-title">Confirm Appointment</h2>
<div class="step-content" style="width:600px">
<?php
// ------------------------------------------------------
// APPOINTMENT DATA CONFIRMATION
// ------------------------------------------------------ ?>
<div id="wizard-frame-4" class="wizard-frame" style="display:none;">
<div class="frame-container">
<h2 class="frame-title">Confirm Appointment</h2>
<div class="frame-content" style="width:600px">
<div id="appointment-info" class="span3"></div>
<div id="customer-info" class="span3"></div>
</div>
@ -213,14 +271,25 @@
data-step_index="4"><i class="icon-backward"></i> Back</button>
<form id="book-appointment-form" style="display:inline-block" method="post">
<button type="submit" class="btn btn-success">
<i class="icon-ok icon-white"></i>Confirm</button>
<i class="icon-ok icon-white"></i>
<?php
echo (!$manage_mode) ? "Confirm" : "Update";
?>
</button>
<input type="hidden" name="post_data" />
</form>
</div>
</div>
<?php
// ------------------------------------------------------
// FRAME FOOTER
// ------------------------------------------------------ ?>
<div id="frame-footer">
Powered By <a href="https://code.google.com/p/easy-appointments/">Easy!Appointments</a>
Powered By
<a href="https://code.google.com/p/easy-appointments/">
Easy!Appointments
</a>
</div>
</div>
</div>

View File

@ -47,7 +47,7 @@
</style>
</head>
<body>
<div id="success-frame" class="container">
<div id="success-frame" class="frame-container">
<img id="success-icon" src="<?php echo $this->config->base_url(); ?>assets/images/success.png" />
<h2>Your appointment has been successfully registered.</h2>

View File

@ -41,7 +41,7 @@
</style>
</head>
<body>
<div id="message-frame" class="container">
<div id="message-frame" class="frame-container">
<img
id="message-icon"
src="<?php echo $this->config->base_url(); ?>assets/images/<?php echo $image; ?>" />

View File

@ -0,0 +1,56 @@
<html>
<head>
<title>Appointment Book Success</title>
</head>
<body style="font: 13px arial, helvetica, tahoma;">
<div class="email-container" style="width: 650px;border: 1px solid #eee;">
<div id="header" style="background-color: #3DD481; border-bottom: 4px solid #1A865F;
height: 40px;padding: 10px 15px;">
<strong id="logo" style="color: white; font-size: 31px;
text-shadow: 1px 1px 1px #8F8888;">$company_name</strong>
</div>
<div id="content" style="padding: 10px 15px;">
<h2>Your appointment has been successfully booked!</h2>
<p>
Thank you $customer_name for arranging an appointment with us.
Below you can see the appointment details. Click on the edit
link to make changes to your appointment.
</p>
<h2>Appointment Details</h2>
<table id="appointment-details">
<tr>
<td class="label" style="padding: 3px;font-weight: bold;">Service</td>
<td style="padding: 3px;">$appointment_service</td>
</tr>
<tr>
<td class="label" style="padding: 3px;font-weight: bold;">Provider</td>
<td style="padding: 3px;">$appointment_provider</td>
</tr>
<tr>
<td class="label" style="padding: 3px;font-weight: bold;">Date</td>
<td style="padding: 3px;">$appointment_date</td>
</tr>
<tr>
<td class="label" style="padding: 3px;font-weight: bold;">Duration</td>
<td style="padding: 3px;">$appointment_duration</td>
</tr>
</table>
<h2>Edit Link</h2>
<p>
Press the following link to make changes to your appointment reservation.
You are able to change the appointment details three hours before
the appointment.
</p>
<a href="$appointment_link">$appointment_link</a>
</div>
<div id="footer" style="padding: 10px; text-align: center;
border-top: 1px solid #EEE;background: #FAFAFA;">
<a href="$company_link">$company_name</a> | Powered by Easy!Appointments
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,67 @@
<html>
<head>
<title>New Appointment</title>
</head>
<body style="font: 13px arial, helvetica, tahoma;">
<div class="email-container" style="width: 650px;border: 1px solid #eee;">
<div id="header" style="background-color: #3DD481; border-bottom: 4px solid #1A865F;
height: 40px; padding: 10px 15px;">
<strong id="logo" style="color: white; font-size: 31px;
text-shadow: 1px 1px 1px #8F8888;">$company_name</strong>
</div>
<div id="content" style="padding: 10px 15px;">
<h2>A new appointment has been added to your plan.</h2>
<h2>Appointment Details</h2>
<table id="appointment-details">
<tr>
<td class="label" style="padding: 3px;font-weight: bold;">Service</td>
<td style="padding: 3px;">$appointment_service</td>
</tr>
<tr>
<td class="label" style="padding: 3px;font-weight: bold;">Provider</td>
<td style="padding: 3px;">$appointment_provider</td>
</tr>
<tr>
<td class="label" style="padding: 3px;font-weight: bold;">Date</td>
<td style="padding: 3px;">$appointment_date</td>
</tr>
<tr>
<td class="label" style="padding: 3px;font-weight: bold;">Duration</td>
<td style="padding: 3px;">$appointment_duration</td>
</tr>
</table>
<h2>Customer Details</h2>
<table id="appointment-details">
<tr>
<td class="label" style="padding: 3px;font-weight: bold;">Name</td>
<td style="padding: 3px;">$customer_name</td>
</tr>
<tr>
<td class="label" style="padding: 3px;font-weight: bold;">Email</td>
<td style="padding: 3px;">$customer_email</td>
</tr>
<tr>
<td class="label" style="padding: 3px;font-weight: bold;">Phone</td>
<td style="padding: 3px;">$customer_phone</td>
</tr>
<tr>
<td class="label" style="padding: 3px;font-weight: bold;">Address</td>
<td style="padding: 3px;">$customer_address</td>
</tr>
</table>
<h2>Appointment Link</h2>
<p>You can make more actions by pressing the following link.</p>
<a href="$appointment_link">$appointment_link</a>
</div>
<div id="footer" style="padding: 10px; text-align: center;
border-top: 1px solid #EEE; background: #FAFAFA;">
<a href="$company_link">$company_name</a> | Powered by Easy!Appointments
</div>
</div>
</body>
</html>

View File

@ -10,23 +10,23 @@ body {
}
/* REGISTER APPOINTMENT
/* BOOK APPOINTMENT WIZARD
------------------------------------------------------------------------------ */
#book-appointment {
#book-appointment-wizard {
width: 660px;
background: #FFF;
box-shadow: 0px 1px 1px #B6B6B6;
min-height: 480px;
}
#book-appointment #top-bar {
#book-appointment-wizard #header {
padding: 5px;
height: 70px;
background: #3DD481;
border-bottom: 4px solid #1A865F;
}
#book-appointment #business-name {
#book-appointment-wizard #company-name {
font-weight: bold;
color: #FFF;
font-size: 27px;
@ -36,43 +36,43 @@ body {
float: left;
}
#book-appointment #book-steps {
#book-appointment-wizard #steps {
width: 204px;
display: inline-block;
float: right;
margin-top: 15px;
}
#book-appointment .book-appoinment-step {
#book-appointment-wizard .wizard-frame {
padding: 10px 20px;
height: 434px;
}
#book-appointment .book-appoinment-step .step-frame {
#book-appointment-wizard .wizard-frame .frame-container {
height: 370px;
margin-bottom: 10px;
}
#book-appointment .step-frame .step-title {
#book-appointment-wizard .frame-container .frame-title {
text-align: center;
margin-bottom: 28px;
}
#book-appointment .step-frame .step-content {
#book-appointment-wizard .frame-container .frame-content {
margin: auto;
float: none;
}
#book-appointment .command-buttons {
#book-appointment-wizard .wizard-frame .command-buttons {
float: right;
}
#book-appointment .command-buttons .btn {
#book-appointment-wizard .wizard-frame .command-buttons .btn {
min-width: 80px;
margin-right: 10px;
}
#book-appointment .book-step {
#book-appointment-wizard .book-step {
display: inline-block;
height: 20px;
width: 20px;
@ -84,14 +84,14 @@ body {
border: 3px solid #38A07A;
}
#book-appointment .book-step strong {
#book-appointment-wizard .book-step strong {
font-size: 18px;
display: block;
text-align: center;
color: #B6DFC6;
}
#book-appointment .active-step {
#book-appointment-wizard .active-step {
display: inline-block;
height: 20px;
width: 20px;
@ -103,40 +103,49 @@ body {
border: 3px solid #38A07A;
}
#book-appointment .active-step strong {
#book-appointment-wizard .active-step strong {
color: #396946;
font-size: 25px;
}
#book-appointment #frame-footer {
#book-appointment-wizard #frame-footer {
padding: 10px;
text-align: center;
border-top: 1px solid #EEE;
background: #FAFAFA;
}
.custom-qtip {
#book-appointment-wizard #steps .custom-qtip {
border-width: 2px;
}
#available-hours .available-hour {
#book-appointment-wizard #available-hours .available-hour {
font-size: 15px;
padding: 1px;
display: inline-block;
}
#available-hours .available-hour:hover {
#book-appointment-wizard #available-hours .available-hour:hover {
font-weight: bold;
background-color: #CAEDF3;
cursor: pointer;
}
#available-hours .selected-hour {
#book-appointment-wizard #available-hours .selected-hour {
color: #0088cc;
font-weight: bold;
text-decoration: underline;
}
#book-appointment .span3 {
#book-appointment-wizard .span3 {
min-width: 270px; /* This is especially needed for ie8 */
}
#cancel-appointment-frame {
}

View File

@ -1,20 +1,34 @@
/**
* This class implements the book appointment page functionality.
* Once the initialize() method is called the page is fully functional
* and can serve the appointment booking process.
* This class implements the book appointment page functionality. Once
* the initialize() method is called the page is fully functional and
* can serve the appointment booking process.
*
* @class Implelements the js part of the appointment booking page.
* @class Implements the js part of the appointment booking page.
*/
var bookAppointment = {
/**
* Determines the functionality of the page.
*
* @type Boolean
*/
manageMode : false,
/**
* This method initializes the book appointment page.
*
* @param {bool} bindEventHandlers (OPTIONAL) Determines wether
* @param {bool} bindEventHandlers (OPTIONAL) Determines whether
* the default event handlers will be binded to the dom elements.
* @param {bool} manageMode (OPTIONAL) Determines whether the customer
* is going to make changes to an existing appointment rather than
* booking a new one.
*/
initialize : function(bindEventHandlers) {
if (bindEventHandlers == undefined) {
bindEventHandlers = true; // Default value
initialize : function(bindEventHandlers, manageMode) {
if (bindEventHandlers === undefined) {
bindEventHandlers = true; // Default Value
}
if (manageMode === undefined) {
bookAppointment.manageMode = false; // Default Value
}
// Initialize page's components (tooltips, datepickers etc).
@ -34,7 +48,7 @@ var bookAppointment = {
defaultDate : Date.today(),
onSelect : function(dateText, instance) {
bookAppointment.getAvailableHours(dateText);
bookAppointment.updateConfirmData();
bookAppointment.updateConfirmFrame();
}
});
@ -43,22 +57,31 @@ var bookAppointment = {
if (bindEventHandlers) {
bookAppointment.bindEventHandlers();
}
// Execute other necessary operations on startup.
$('#select-service').trigger('change');
// If the manage mode is true, the appointments data should be
// loaded by default.
if (bookAppointment.manageMode) {
bookAppointment.applyAppointmentData(GlobalVariables.appointmentData,
GlobalVariables.providerData, GlobalVariables.customerData);
} else {
$('#select-service').trigger('change'); // Load the available hours.
}
},
/**
* This method binds the necessary event handlers
* for the book appointments page.
* This method binds the necessary event handlers for the book
* appointments page.
*/
bindEventHandlers : function() {
/**
* Event : Selected Provider "Changed"
*
* Whenever the provider changes the available appointment
* date - time periods must be updated.
*/
$('#select-provider').change(function() {
bookAppointment.getAvailableHours(Date.today().toString('dd-MM-yyyy'));
bookAppointment.updateConfirmData();
bookAppointment.updateConfirmFrame();
});
/**
@ -71,21 +94,21 @@ var bookAppointment = {
var currServiceId = $('#select-service').val();
$('#select-provider').empty();
$.each(GlobalVariables.providers, function(indexProvider, provider) {
$.each(GlobalVariables.availableProviders, function(indexProvider, provider) {
$.each(provider['services'], function(indexService, serviceId) {
// If the current provider is able to provide the selected
// service, add him to the listbox.
// If the current provider is able to provide the selected service,
// add him to the listbox.
if (serviceId == currServiceId) {
var optionHtml = '<option value="' + provider['id'] + '">'
+ provider['last_name'] + ' ' + provider['first_name']
+ '</option>';
+ provider['last_name'] + ' ' + provider['first_name']
+ '</option>';
$('#select-provider').append(optionHtml);
}
});
});
bookAppointment.getAvailableHours(Date.today().toString('dd-MM-yyyy'));
bookAppointment.updateConfirmData();
bookAppointment.updateConfirmFrame();
});
/**
@ -101,9 +124,10 @@ var bookAppointment = {
if ($(this).attr('data-step_index') === '2') {
if ($('.selected-hour').length == 0) {
if ($('#select-hour-prompt').length == 0) {
$('#available-hours').append('<br><br><strong id="select-hour-prompt"'
+ ' class="text-error">Please select an appointment hour before '
+ 'continuing!</strong>');
$('#available-hours').append('<br><br>'
+ '<strong id="select-hour-prompt" class="text-error">'
+ 'Please select an appointment hour before continuing!'
+ '</strong>');
}
return;
}
@ -112,10 +136,10 @@ var bookAppointment = {
// If we are on the 3rd tab then we will need to validate the user's
// input before proceeding to the next step.
if ($(this).attr('data-step_index') === '3') {
if (!bookAppointment.validateCustomerDataForm()) {
if (!bookAppointment.validateCustomerForm()) {
return; // Validation failed, do not continue.
} else {
bookAppointment.updateConfirmData();
bookAppointment.updateConfirmFrame();
}
}
@ -125,7 +149,7 @@ var bookAppointment = {
$(this).parents().eq(1).hide('fade', function() {
$('.active-step').removeClass('active-step');
$('#step-' + nextTabIndex).addClass('active-step');
$('#book-appointment-' + nextTabIndex).show('fade');
$('#wizard-frame-' + nextTabIndex).show('fade');
});
});
@ -141,7 +165,7 @@ var bookAppointment = {
$(this).parents().eq(1).hide('fade', function() {
$('.active-step').removeClass('active-step');
$('#step-' + prevTabIndex).addClass('active-step');
$('#book-appointment-' + prevTabIndex).show('fade');
$('#wizard-frame-' + prevTabIndex).show('fade');
});
});
@ -154,7 +178,7 @@ var bookAppointment = {
$('#available-hours').on('click', '.available-hour', function() {
$('.selected-hour').removeClass('selected-hour');
$(this).addClass('selected-hour');
bookAppointment.updateConfirmData();
bookAppointment.updateConfirmFrame();
});
},
@ -169,24 +193,31 @@ var bookAppointment = {
// Find the selected service duration (it is going to
// be send within the "postData" object.
var selServiceDuration = 15; // Default value of duration (in minutes).
$.each(GlobalVariables.services, function(index, service) {
$.each(GlobalVariables.availableServices, function(index, service) {
if (service['id'] == $('#select-service').val()) {
selServiceDuration = service['duration'];
}
});
// If the manage mode is true then the appointment's start
// date should return as available too.
var appointmentId = (bookAppointment.manageMode)
? GlobalVariables.appointmentData['id'] : undefined;
var postData = {
'service_id' : $('#select-service').val(),
'provider_id' : $('#select-provider').val(),
'selected_date' : selDate,
'service_duration' : selServiceDuration
'service_id' : $('#select-service').val(),
'provider_id' : $('#select-provider').val(),
'selected_date' : selDate,
'service_duration' : selServiceDuration,
'manage_mode' : bookAppointment.manageMode,
'appointment_id' : appointmentId
};
// Make ajax post request and get the available hours.
var ajaxurl = GlobalVariables.baseUrl + 'appointments/ajax_get_available_hours';
jQuery.post(ajaxurl, postData, function(postResponse) {
////////////////////////////////////////////////////////////////////////////////
console.log('\n\n Get Available Hours Post Response :', postResponse, '\n\n');
//console.log('\n\n Get Available Hours Post Response :', postResponse, '\n\n');
////////////////////////////////////////////////////////////////////////////////
try {
@ -199,27 +230,32 @@ var bookAppointment = {
// Fill the available time div
var currColumn = 1;
$('#available-hours').html('<div style="width:50px; float:left;"></div>');
$.each(jsonResponse, function(index, availableHour) {
if ((currColumn * 10) < (index + 1)) {
currColumn++;
$('#available-hours').append('<div style="width:50px; float:left;"></div>');
$('#available-hours')
.append('<div style="width:50px; float:left;"></div>');
}
$('#available-hours div:eq(' + (currColumn - 1) + ')')
.append('<span class="available-hour">' + availableHour + '</span><br/>');
.append('<span class="available-hour">' + availableHour
+ '</span><br/>');
});
// Set the first item as selected.
$('.available-hour:eq(0)').addClass('selected-hour');
bookAppointment.updateConfirmData();
bookAppointment.updateConfirmFrame();
} else {
$('#available-hours').text('There are not available appointment hours for '
+ 'the selected date. Please choose another date.');
$('#available-hours').text('There are no available appointment'
+ 'hours for the selected date. Please choose another '
+ 'date.');
}
} catch(exception) {
GeneralFunctions.displayMessageBox('Unexpected Error', 'An unexpected error occured '
+ 'during the available hours calculation. Please refresh the page and try again.');
GeneralFunctions.displayMessageBox('Unexpected Error', 'An unexpected'
+ 'error occured during the available hours calculation. Please'
+ 'refresh the page and try again.');
}
});
},
@ -230,7 +266,7 @@ var bookAppointment = {
*
* @return {bool} Returns the validation result.
*/
validateCustomerDataForm : function() {
validateCustomerForm : function() {
var validationResult = true;
$('.required').css('border', '');
@ -249,7 +285,7 @@ var bookAppointment = {
* page with the latest customer settigns and input for the appointment
* booking.
*/
updateConfirmData : function() {
updateConfirmFrame : function() {
/*** SET APPOINTMENT INFO ***/
var selectedDate = $('#select-date').datepicker('getDate');
if (selectedDate !== null) {
@ -308,7 +344,7 @@ var bookAppointment = {
// Find selected service duration.
var selServiceDuration = undefined;
$.each(GlobalVariables.services, function(index, service) {
$.each(GlobalVariables.availableServices, function(index, service) {
if (service.id == $('#select-service').val()) {
selServiceDuration = service.duration;
return; // Stop searching ...
@ -328,5 +364,47 @@ var bookAppointment = {
}
return endDatetime.toString('yyyy-MM-dd HH:mm:ss');
},
/**
* This method applies the appointment's data to the wizard so
* that the user can start making changes on an existing record.
*
* @param {object} appointmentData Selected appointment's data.
* @param {object} providerData Selected provider's data.
* @param {object} customerData Selected customer's data.
* @returns {bool} Returns the operation result.
*/
applyAppointmentData : function(appointmentData, providerData, customerData) {
try {
// Select Service & Provider
$('#select-service').val(appointmentData['id_services']);
$('#select-provider').val(appointmentData['id_users_provider']);
// Set Appointment Date
$('.available-hour').removeClass('selected-hour');
$('.available-hour').filter(function() {
return $(this).text() === Date.parseExact(appointmentData['start_datetime'],
'yyyy-MM-dd HH:mm').toString('HH:mm');
}).addClass('selected-hour');
// Apply Customer's Data
$('last-name').val(customerData['last_name']);
$('first-name').val(customerData['first_name']);
$('email').val(customerData['email']);
$('phone-number').val(customerData['phone_number']);
$('address').val(customerData['address']);
$('city').val(customerData['city']);
$('zip-code').val(customerData['zip_code']);
$('notes').text(customerData['notes']);
bookAppointment.updateConfirmFrame();
return true;
} catch(exc) {
console.log(exc);
return false;
}
}
}

View File

@ -82,4 +82,24 @@ GeneralFunctions.centerElementOnPage = function(elementHandle) {
});
});
$(window).resize();
}
/**
* This function retrieves a parameter from a "GET" formed url.
*
* @link http://www.netlobo.com/url_query_string_javascript.html
*
* @param {string} url The selected url.
* @param {string} name The parameter name.
* @returns {String} Returns the parameter value.
*/
GeneralFunctions.getUrlParameter = function(url, parameterName) {
parameterName = parameterName.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\#&]"+parameterName+"=([^&#]*)";
var regex = new RegExp( regexS );
var results = regex.exec( url );
if( results == null )
return "";
else
return results[1];
}