Ολοκλήρωση της βασικής ροής της περίπτωσης χρήσης 'Κράτηση Ραντεβού'.

This commit is contained in:
alextselegidis@gmail.com 2013-04-20 17:20:16 +00:00
parent 918903de62
commit d8d6a8f400
57 changed files with 730 additions and 267 deletions

View File

@ -3,7 +3,7 @@
-- http://www.phpmyadmin.net
--
-- Φιλοξενητής: localhost
-- Χρόνος δημιουργίας: 14 Απρ 2013 στις 19:41:00
-- Χρόνος δημιουργίας: 20 Απρ 2013 στις 20:18:59
-- Έκδοση διακομιστή: 5.5.24-log
-- Έκδοση PHP: 5.4.3
@ -30,7 +30,7 @@ CREATE TABLE IF NOT EXISTS `ea_appointments` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`start_datetime` datetime DEFAULT NULL,
`end_datetime` datetime DEFAULT NULL,
`comments` text,
`notes` text,
`id_users_provider` bigint(20) unsigned NOT NULL,
`id_users_customer` bigint(20) unsigned NOT NULL,
`id_services` bigint(20) unsigned NOT NULL,
@ -38,7 +38,7 @@ CREATE TABLE IF NOT EXISTS `ea_appointments` (
KEY `id_users_customer` (`id_users_customer`),
KEY `id_services` (`id_services`),
KEY `id_users_provider` (`id_users_provider`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=18 ;
-- --------------------------------------------------------
@ -49,6 +49,7 @@ CREATE TABLE IF NOT EXISTS `ea_appointments` (
CREATE TABLE IF NOT EXISTS `ea_roles` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(256) DEFAULT NULL,
`slug` varchar(256) DEFAULT NULL,
`is_admin` tinyint(4) DEFAULT NULL COMMENT '0',
`services` int(4) DEFAULT NULL COMMENT '0',
`providers` int(4) DEFAULT NULL COMMENT '0',
@ -56,14 +57,16 @@ CREATE TABLE IF NOT EXISTS `ea_roles` (
`notifications` int(4) DEFAULT NULL COMMENT '0',
`appointments` int(4) DEFAULT NULL COMMENT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
--
-- Άδειασμα δεδομένων του πίνακα `ea_roles`
--
INSERT INTO `ea_roles` (`id`, `name`, `is_admin`, `services`, `providers`, `customers`, `notifications`, `appointments`) VALUES
(1, 'Administrator', 1, 15, 15, 15, 15, 15);
INSERT INTO `ea_roles` (`id`, `name`, `slug`, `is_admin`, `services`, `providers`, `customers`, `notifications`, `appointments`) VALUES
(1, 'Administrator', 'administrator', 1, 15, 15, 15, 15, 15),
(2, 'Provider', 'provider', 0, 0, 0, 15, 0, 15),
(3, 'Customer', 'customer', 0, 0, 0, 0, 0, 0);
-- --------------------------------------------------------
@ -81,14 +84,43 @@ CREATE TABLE IF NOT EXISTS `ea_services` (
`id_service_categories` bigint(20) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `id_service_categories` (`id_service_categories`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
--
-- Άδειασμα δεδομένων του πίνακα `ea_services`
--
INSERT INTO `ea_services` (`id`, `name`, `duration`, `price`, `currency`, `description`, `id_service_categories`) VALUES
(1, 'General Examination', 20, '50.00', 'euro', 'Sample Description ...', NULL);
(1, 'Γενική Εξέταση', 20, '50.00', 'euro', 'Γενική εξέταση του ασθενή.', NULL),
(2, 'Εξέταση Καρδιάς', 30, '40.00', 'euro', 'Εξέταση του ασθενή για νοσήματα καρδιάς.', NULL),
(3, 'Νευρολογική Εξέταση', 20, '35.00', 'euro', 'Νευρολογική εξέταση του ασθενή.', NULL);
-- --------------------------------------------------------
--
-- Δομή πίνακα για τον πίνακα `ea_services_providers`
--
CREATE TABLE IF NOT EXISTS `ea_services_providers` (
`id_users` bigint(20) unsigned NOT NULL,
`id_services` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id_users`,`id_services`),
KEY `id_services` (`id_services`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Άδειασμα δεδομένων του πίνακα `ea_services_providers`
--
INSERT INTO `ea_services_providers` (`id_users`, `id_services`) VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 1),
(3, 2),
(4, 2),
(2, 3),
(3, 3);
-- --------------------------------------------------------
@ -114,14 +146,16 @@ CREATE TABLE IF NOT EXISTS `ea_settings` (
`name` varchar(512) DEFAULT NULL,
`value` longtext,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
--
-- Άδειασμα δεδομένων του πίνακα `ea_settings`
--
INSERT INTO `ea_settings` (`id`, `name`, `value`) VALUES
(1, 'business_name', 'Javation & Co');
(1, 'business_name', 'Javation & Co'),
(2, 'business_working_hours', '{}'),
(3, NULL, NULL);
-- --------------------------------------------------------
@ -146,14 +180,17 @@ CREATE TABLE IF NOT EXISTS `ea_users` (
`id_roles` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `id_roles` (`id_roles`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=35 ;
--
-- Άδειασμα δεδομένων του πίνακα `ea_users`
--
INSERT INTO `ea_users` (`id`, `username`, `password`, `first_name`, `last_name`, `email`, `mobile_number`, `phone_number`, `address`, `city`, `state`, `zip_code`, `notes`, `id_roles`) VALUES
(1, 'admin', 'admin', 'Alex', 'Tselegidis', 'alextselegidis@gmail.com', '123456789', '987654321', 'Pantazopoulou 11', 'Thessaloniki', NULL, '56121', 'This is me making Easy!Appointments', 1);
(1, 'admin', 'admin', 'Alex', 'Tselegidis', 'alextselegidis@gmail.com', '123456789', '987654321', 'Pantazopoulou 11', 'Thessaloniki', NULL, '56121', 'This is me making Easy!Appointments', 1),
(2, 'provider_1', 'provider_1', 'Γιώργος', 'Παπαδόπουλος', 'prov1@testing.gr', '1212121212', '2121212121', 'John Doe 23', 'Washington DC', NULL, '12345', 'This is a test provider', 2),
(3, 'provider_2', 'provider_2', 'Νίκος', 'Αναστασίου', 'prov2@test.gr', '1313133113131', '32132165146', 'Some Street 3', NULL, NULL, NULL, NULL, 2),
(4, 'provider_3', 'provider_3', 'Ηρώ', 'Καριοφύλη', 'prov3@test.gr', '239203490', '029340923', 'John Doe 3 ', NULL, NULL, NULL, NULL, 2);
--
-- Περιορισμοί για άχρηστους πίνακες
@ -173,6 +210,13 @@ ALTER TABLE `ea_appointments`
ALTER TABLE `ea_services`
ADD CONSTRAINT `ea_services_ibfk_1` FOREIGN KEY (`id_service_categories`) REFERENCES `ea_service_categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Περιορισμοί για πίνακα `ea_services_providers`
--
ALTER TABLE `ea_services_providers`
ADD CONSTRAINT `ea_services_providers_ibfk_1` FOREIGN KEY (`id_users`) REFERENCES `ea_users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `ea_services_providers_ibfk_2` FOREIGN KEY (`id_services`) REFERENCES `ea_services` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
--
-- Περιορισμοί για πίνακα `ea_users`
--

View File

@ -6,19 +6,62 @@ class Appointments extends CI_Controller {
* for the customers.
*/
public function index() {
// Get business name.
$this->load->model('Settings');
$viewData['businessName'] = $this->Settings->getSetting('business_name');
// Get the available services and providers.
$this->load->model('Services');
$viewData['availableServices'] = $this->Services->getAvailableServices();
$this->load->model('Providers');
$viewData['availableProviders'] = $this->Providers->getAvailableProviders(); // Provider rows contain an array of which services they can provide.
// Load the book appointment view.
$this->load->view('appointments/book', $viewData);
if (strtoupper($_SERVER['REQUEST_METHOD']) != 'POST') {
// Display the appointment booking page to the customer.
// Get business name.
$this->load->model('Settings_Model');
$viewData['businessName'] = $this->Settings_Model->getSetting('business_name');
// Get the available services and providers.
$this->load->model('Services_Model');
$viewData['availableServices'] = $this->Services_Model->getAvailableServices();
$this->load->model('Providers_Model');
$viewData['availableProviders'] = $this->Providers_Model->getAvailableProviders(); // Provider rows contain an array of which services they can provide.
// Load the book appointment view.
$this->load->view('appointments/book', $viewData);
} else {
// Add the appointment to the database and display the success
// page to the customer.
$customerData = array(
'first_name' => $_POST['firstName'],
'last_name' => $_POST['lastName'],
'email' => $_POST['email'],
'phone_number' => $_POST['phoneNumber'],
'address' => $_POST['address'],
'city' => $_POST['city'],
'zip_code' => $_POST['zipCode']
);
$this->load->model('Customers_Model');
$customerId = $this->Customers_Model->add($customerData);
$appointmentData = array(
'start_datetime' => $_POST['startDatetime'],
'end_datetime' => $_POST['endDatetime'],
'notes' => $_POST['notes'],
'id_users_provider' => $_POST['providerId'],
'id_users_customer' => $customerId,
'id_services' => $_POST['serviceId'],
);
$this->load->model('Appointments_Model');
$this->Appointments_Model->add($appointmentData);
// Send an email to the customer with the appointment info.
$to = $customerData['email'];
$subject = 'Appointment Book Success';
$message = 'Hi there! Your appointment has been successfully booked.';
$headers = 'From: alextselegidis@gmail.com' . "\r\n" .
'Reply-To: alextselegidis@gmail.com' . "\r\n" .
'X-Mailer: PHP/' . phpversion();
mail($to, $subject, $message, $headers);
// Load the book appointment view.
$this->load->view('appointments/book-success');
}
}
/**
@ -38,12 +81,26 @@ class Appointments extends CI_Controller {
// For now we just need to return a sample array. Dynamic calculation
// will be adding in future development.
$startTime = strtotime($_POST['selectedDate'] . ' 08:00') / 60; // Convert to minutes
$endTime = strtotime($_POST['selectedDate'] . ' 16:00') / 60; // Convert to minutes
$endTime = strtotime($_POST['selectedDate'] . ' 19:00') / 60; // Convert to minutes
for ($i=0; ($startTime*60 + $i*60)<=($endTime*60); $i+=intval($_POST['serviceDuration'])) {
$availableHours[] = date('H:i', $startTime * 60 + $i * 60);
}
// If the selected date is today, remove past hours.
if (date('m/d/Y', strtotime($_POST['selectedDate'])) == date('m/d/Y')) {
foreach($availableHours as $index=>$value) {
$availableHour = date('m/d/Y H:i', strtotime($value));
$currentHour = date('m/d/Y H:i');
if ($availableHour < $currentHour) {
unset($availableHours[$index]);
}
}
}
$availableHours = array_values($availableHours);
echo json_encode($availableHours);
}
}

View File

@ -1,7 +0,0 @@
<?php
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
?>

View File

@ -0,0 +1,36 @@
<?php
class Appointments_Model extends CI_Model {
public function __construct() {
parent::__construct();
}
public function add($appointmentData) {
try {
if (!$this->exists($appointmentData)) {
$this->insert($appointmentData);
} else {
$this->update($appointmentData);
}
} catch (Exception $exc) {
echo $exc->getTraceAsString();
}
}
public function exists($appointmentData) {
return false; // @task This check is going to be more complicated than just checking an email (customers model)
}
private function insert($appointmentData) {
$this->db->insert('ea_appointments', $appointmentData);
}
private function update($appointmentData) {
$this->db->where('id', $appointmentData['id']);
$this->db->update('ea_appointments', $appointmentData);
}
public function delete($appointmentId) {
}
}
?>

View File

@ -1,7 +0,0 @@
<?php
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
?>

View File

@ -0,0 +1,84 @@
<?php
class Customers_Model extends CI_Model {
public function __construct() {
parent::__construct();
}
/**
* This method adds a customer to the database. If the customer
* already exists then its data are going to be updated.
*
* @param array $customerData The customer data we want to add.
*/
public function add($customerData) {
try {
if (!$this->exists($customerData['email'])) {
$this->insert($customerData);
} else {
$this->update($customerData);
}
} catch(Exception $exc) {
// Send some info for the exception back to the browser.
}
return $this->db->insert_id();
}
/**
* This method checks if a customer exists on the database.
* The unique identifier for a customer is his email.
*
* @param string $customerEmail The customers email.
* @return bool Returns the operation result.
*/
public function exists($customerEmail) {
return false; // @task This should be implemented soon.
}
/**
* This method inserts a customer into the database.
*
* @param array $customerData Associative array with the customers
* data. The key of the array should be the same as the database
* field names.
*/
private function insert($customerData) {
$this->db
->select('id')
->from('ea_roles')
->where('slug', 'customer');
$customerRoleId = $this->db->get()->row()->id;
if ($customerRoleId !== NULL) {
$customerData['id_roles'] = $customerRoleId;
}
if (!$this->db->insert('ea_users', $customerData)) {
throw new Exception('Could not insert customer to the database.');
}
}
private function update($customerData) {
$this->db->where('email', $customerData['email']);
if (!$this->db->update('ea_users', $customerData)) {
throw new Exception('Could not update customer to the database.');
}
}
/**
* This method deletes a customers from the database.
* All his appointmets will be deleted too (automatically
* through the foreign key constraint).
*
* @param int $customerId The id of the customer to be
* deleted.
*/
public function delete($customerId) {
}
}
?>

View File

@ -1,5 +1,5 @@
<?php
class Providers extends CI_Model {
class Providers_Model extends CI_Model {
public function __construct() {
parent::__construct();
}
@ -12,22 +12,21 @@ class Providers extends CI_Model {
* data.
*/
public function getAvailableProviders() {
$sql = '
SELECT ea_users.*
FROM ea_users
INNER JOIN ea_roles
ON ea_users.id_roles = ea_roles.id
WHERE ea_roles.slug = "provider"';
$this->db
->select('ea_users.*')
->from('ea_users')
->join('ea_roles', 'ea_roles.id = ea_users.id_roles', 'inner')
->where('ea_roles.slug', 'provider');
$providers = $this->db->query($sql)->result_array();
$providers = $this->db->get()->result_array();
foreach($providers as &$provider) {
$sql = '
SELECT id_services
FROM ea_services_providers
WHERE id_users = ' . $this->db->escape($provider['id']);
$this->db
->select('id_services')
->from('ea_services_providers')
->where('id_users', $provider['id']);
$providerServices = $this->db->query($sql)->result_array();
$providerServices = $this->db->get()->result_array();
if (!isset($provider['services'])) {
$provider['services'] = array();

View File

@ -1,5 +1,5 @@
<?php
class Services extends CI_Model {
class Services_Model extends CI_Model {
function __construct() {
parent::__construct();
}

View File

@ -1,7 +1,6 @@
<?php
class Settings extends CI_Model {
function __construct()
{
class Settings_Model extends CI_Model {
function __construct() {
parent::__construct();
}
@ -13,8 +12,7 @@ class Settings extends CI_Model {
* @return string Returns the database value for
* the selected setting.
*/
function getSetting($name)
{
function getSetting($name) {
$query = $this->db->get_where('ea_settings', array('name' => $name));
$setting = ($query->num_rows() > 0) ? $query->row() : '';
return $setting->value;
@ -30,8 +28,7 @@ class Settings extends CI_Model {
* @return bool Returns the operation success - failure
* result.
*/
function setSetting($name, $value)
{
function setSetting($name, $value) {
$query = $this->db->get_where('ea_settings', array('name' => $name));
if ($query->num_rows() > 0) {
// Update setting

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<?php // INCLUDE CSS FILES ?>
<link rel="stylesheet" type="text/css" href="<?php echo $this->config->base_url(); ?>assets/css/libs/bootstrap/bootstrap.css">
<link rel="stylesheet" type="text/css" href="<?php echo $this->config->base_url(); ?>assets/css/libs/bootstrap/bootstrap-responsive.css">
<?php // SET FAVICON FOR PAGE ?>
<link rel="icon" type="image/x-icon" href="<?php echo $this->config->base_url(); ?>assets/images/favicon.ico">
<style>
body {
background-color: #CAEDF3;
}
#success-frame {
width: 660px;
margin: 150px auto 0 auto;
background: #FFF;
box-shadow: 0px 1px 1px #B6B6B6;
min-height: 197px;
padding: 108px 10px;
}
#success-icon {
float: left;
margin: 0px 26px 0 20px;
}
</style>
</head>
<body>
<div id="success-frame" class="container">
<img id="success-icon" src="<?php echo $this->config->base_url(); ?>/assets/images/success.png" />
<h2>Your appointment has been successfully registered.</h2>
<p>An email with the appointment details has been sented to you.</p>
</div>
</body>
</html>

View File

@ -16,150 +16,25 @@
<script type="text/javascript" src="<?php echo $this->config->base_url(); ?>assets/js/libs/jquery/jquery.qtip.min.js"></script>
<script type="text/javascript" src="<?php echo $this->config->base_url(); ?>assets/js/libs/bootstrap/bootstrap.min.js"></script>
<script type="text/javascript" src="<?php echo $this->config->base_url(); ?>assets/js/libs/date.js"></script>
<script type="text/javascript" src="<?php echo $this->config->base_url(); ?>assets/js/frontend/book-appointment.js"></script>
<?php // SET FAVICON FOR PAGE ?>
<link rel="icon" type="image/x-icon" href="<?php echo $this->config->base_url(); ?>assets/images/favicon.ico">
<?php // JS GLOBAL VARIABLE DECLARATION ?>
<script type="text/javascript">
// Define some global variables.
GlobalVariables = {
services : <?php echo json_encode($availableServices); ?>,
providers : <?php echo json_encode($availableProviders); ?>,
baseUrl : '<?php echo $this->config->base_url(); ?>'
}
</script>
<?php // JQUERY PAGE STUFF ?>
<script type="text/javascript">
$(document).ready(function() {
var services = <?php echo json_encode($availableServices); ?>;
var providers = <?php echo json_encode($availableProviders); ?>;
// When the user clicks on a service, its available providers should
// become visible.
$('#select-service').change(function() {
var currServiceId = $('#select-service').val();
$('#select-provider').empty();
$.each(providers, function(index, provider) {
$.each(provider['services'], function(index, serviceId) {
if (serviceId == currServiceId) {
// This provider can provide the selected service.
// Add him to the list box.
var option = new Option(provider['last_name'] + ' ' + provider['first_name'], provider['id']);
$('#select-provider').append(option);
}
});
});
refreshAvailableHours(Date.today().toString('MM/dd/yyyy'));
});
/**
* Selected Provider Changed Event Handler
*/
$('#select-provider').change(function() {
refreshAvailableHours(Date.today().toString('MM/dd/yyyy'));
});
$('#select-service').trigger('change');
$('.book-step').qtip({
position: {
my: 'top center',
at: 'bottom center'
},
style: {
classes: 'qtip-green qtip-shadow custom-qtip'
}
});
$('#select-date').datepicker({
//dateFormat : 'dd/mm/yy',
onSelect : function(dateText, inst) {
refreshAvailableHours(dateText);
}
});
refreshAvailableHours(Date.today().toString('MM/dd/yyyy'));
/**
* Next Step Button Clicked
*/
$('.button-next').click(function() {
var nextTabIndex = parseInt($(this).attr('data-step_index')) + 1;
$(this).parents().eq(1).hide('fade', function() {
$('.active-step').removeClass('active-step');
$('#step-' + nextTabIndex).addClass('active-step');
$('#book-appointment-' + nextTabIndex).show('fade');
});
});
/**
* Back Step Button Clicked
*/
$('.button-back').click(function() {
var prevTabIndex = parseInt($(this).attr('data-step_index')) - 1;
$(this).parents().eq(1).hide('fade', function() {
$('.active-step').removeClass('active-step');
$('#step-' + prevTabIndex).addClass('active-step');
$('#book-appointment-' + prevTabIndex).show('fade');
});
});
/**
* Available Hour Click Event Handler
*/
$('#available-hours').on('click', '.available-hour', function() {
$('.selected-hour').removeClass('selected-hour');
$(this).addClass('selected-hour');
});
/**
* This function makes an ajax call and returns the available
* hours for the selected service, provider and date.
*/
function refreshAvailableHours(selDate) {
// Fetch the available hours of the current date
// for the chosen service and provider.
var selServiceDuration = 15; // Default duration.
$.each(services, function(index, service) {
if (service['id'] == $('#select-service').val()) {
selServiceDuration = service['duration'];
}
})
var postData = {
'serviceId' : $('#select-service').val(),
'providerId' : $('#select-provider').val(),
'selectedDate' : selDate,
'serviceDuration' : selServiceDuration
};
console.log('\n\n Get Available Hours Post Data:', postData, '\n\n');
// Make ajax post request and get the available hours.
var ajaxurl = '<?php echo $this->config->base_url(); ?>index.php/appointments/getAvailableHours';
jQuery.post(ajaxurl, postData, function(postResponse) {
////////////////////////////////////////////////////////////////////////////////
console.log('\n\n Get Available Hours Post Response :', postResponse, '\n\n');
////////////////////////////////////////////////////////////////////////////////
try {
var jsonResponse = jQuery.parseJSON(postResponse);
////////////////////////////////////////////////////////////////////////////////
console.log('\n\n Get Available Hours Json Response :', jsonResponse, '\n\n');
////////////////////////////////////////////////////////////////////////////////
// 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 div:eq(' + (currColumn - 1) + ')')
.append('<span class="available-hour">' + availableHour + '</span><br/>');
});
} catch(exception) {
// @task Display message to the user.
};
});
}
bookAppointment.initialize(); // Begin jquery magic :P
});
</script>
</head>
@ -188,97 +63,120 @@
<?php // SELECT SERVICE AND PROVIDER ?>
<div id="book-appointment-1" class="book-appoinment-step">
<h2>Select Service & Provider</h2>
<label for="select-service">Select Service</label>
<select id="select-service">
<?php
foreach($availableServices as $service) {
echo '<option value="' . $service['id'] . '">' . $service['name'] . '</option>';
}
?>
</select>
<div class="book-step-content">
<h2>Select Service & Provider</h2>
<div class="span5">
<label for="select-service">Select Service</label>
<select id="select-service">
<?php
foreach($availableServices as $service) {
echo '<option value="' . $service['id'] . '">' . $service['name'] . '</option>';
}
?>
</select>
<label for="select-provider">Select Provider</label>
<select id="select-provider"></select>
<label for="select-provider">Select Provider</label>
<select id="select-provider"></select>
</div>
</div>
<div class="command-buttons">
<button type="button" id="button-next-1" class="btn button-next" data-step_index="1">Next</button>
<button type="button" id="button-next-1" class="btn button-next btn-primary" data-step_index="1">Next <i class="icon-forward icon-white"></i></button>
</div>
</div>
<?php // APPOINTMENT DATE ?>
<div id="book-appointment-2" class="book-appoinment-step" style="display:none;">
<h2>Select Appointment Date And Time</h2>
<div class="span3">
<div id="select-date"></div>
</div>
<div class="span3">
<?php // Available hours are going to be fetched via ajax call. ?>
<div id="available-hours"></div>
<div class="book-step-content">
<h2>Select Appointment Date And Time</h2>
<div class="span3">
<div id="select-date"></div>
</div>
<div class="span3">
<?php // Available hours are going to be fetched via ajax call. ?>
<div id="available-hours"></div>
</div>
</div>
<div class="command-buttons">
<button type="button" id="button-back-2" class="btn button-back" data-step_index="2">Back</button>
<button type="button" id="button-next-2" class="btn button-next" data-step_index="2">Next</button>
<button type="button" id="button-back-2" class="btn button-back" data-step_index="2"><i class="icon-backward"></i> Back</button>
<button type="button" id="button-next-2" class="btn button-next btn-primary" data-step_index="2">Next <i class="icon-forward icon-white"></i></button>
</div>
</div>
<?php // CUSTOMER'S INFO ?>
<div id="book-appointment-3" class="book-appoinment-step" style="display:none;">
<h2>Fill In Your Information</h2>
<div class="span3">
<label for="last-name">Last Name *</label>
<input type="text" id="last-name" maxlength="250" />
<div class="book-step-content">
<h2>Fill In Your Information</h2>
<label for="first-name">First Name</label>
<input type="text" id="first-name" maxlength="100" />
<div class="span3">
<label for="last-name">Last Name *</label>
<input type="text" id="last-name" class="required" maxlength="250" />
<label for="email">Email *</label>
<input type="text" id="email" maxlength="250" />
<label for="first-name">First Name</label>
<input type="text" id="first-name" maxlength="100" />
<label for="phone-number">Phone Number *</label>
<input type="text" id="phone-number" maxlength="60" />
</div>
<div class="span3">
<label for="address">Address</label>
<input type="text" id="address" maxlength="250" />
<label for="email">Email *</label>
<input type="text" id="email" class="required" maxlength="250" />
<label for="city">City</label>
<input type="text" id="city" maxlength="120" />
<label for="phone-number">Phone Number *</label>
<input type="text" id="phone-number" class="required" maxlength="60" />
<br/><br/>
<em class="text-error">Fields with * are mandatory.</em>
</div>
<label for="zip-code">Zip Code</label>
<input type="text" id="zip-code" maxlength="120" />
<div class="span3">
<label for="address">Address</label>
<input type="text" id="address" maxlength="250" />
<label for="notes">Notes</label>
<textarea id="notes" maxlength="500"></textarea>
<label for="city">City</label>
<input type="text" id="city" maxlength="120" />
<label for="zip-code">Zip Code</label>
<input type="text" id="zip-code" maxlength="120" />
<label for="notes">Notes</label>
<textarea id="notes" maxlength="500" rows="4"></textarea>
</div>
</div>
<div class="command-buttons">
<button type="button" id="button-back-3" class="btn button-back" data-step_index="3">Back</button>
<button type="button" id="button-next-3" class="btn button-next" data-step_index="3">Next</button>
<button type="button" id="button-back-3" class="btn button-back" data-step_index="3"><i class="icon-backward"></i> Back</button>
<button type="button" id="button-next-3" class="btn button-next btn-primary" data-step_index="3">Next <i class="icon-forward icon-white"></i></button>
</div>
</div>
<?php // CONFIRMATION STEP ?>
<div id="book-appointment-4" class="book-appoinment-step" style="display:none;">
<h2>Confirm Appointment</h2>
<div id="appointment-info">
</div>
<div id="customer-info">
<div class="book-step-content">
<h2>Confirm Appointment</h2>
<div id="appointment-info" class="span3"></div>
<div id="customer-info" class="span3"></div>
</div>
<div class="command-buttons">
<button type="button" id="button-back-4" class="btn button-back" data-step_index="4">Back</button>
<form>
<button type="submit" class="btn-success">Confirm Appointment</button>
<button type="button" id="button-back-4" class="btn button-back" 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>
<input type="hidden" name="lastName" />
<input type="hidden" name="firstName" />
<input type="hidden" name="email" />
<input type="hidden" name="phoneNumber" />
<input type="hidden" name="address" />
<input type="hidden" name="city" />
<input type="hidden" name="zipCode" />
<input type="hidden" name="startDatetime" />
<input type="hidden" name="endDatetime" />
<input type="hidden" name="notes" />
<input type="hidden" name="providerId" />
<input type="hidden" name="serviceId" />
</form>
</div>
</div>
@ -289,4 +187,4 @@
</div>
</div>
</body>
</html>
</html>

View File

@ -2282,7 +2282,7 @@ table th[class*="span"],
*margin-right: .3em;
line-height: 14px;
vertical-align: text-top;
background-image: url("../../../img/glyphicons-halflings.png");
background-image: url("../../../images/glyphicons-halflings.png");
background-position: 14px 14px;
background-repeat: no-repeat;
}
@ -2306,7 +2306,7 @@ table th[class*="span"],
.dropdown-submenu:focus > a > [class^="icon-"],
.dropdown-submenu:hover > a > [class*=" icon-"],
.dropdown-submenu:focus > a > [class*=" icon-"] {
background-image: url("../../../img/glyphicons-halflings-white.png");
background-image: url("../../../images/glyphicons-halflings-white.png");
}
.icon-glass {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 B

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

View File

@ -38,7 +38,7 @@ body {
}
#book-appointment #book-steps {
width: 252px;
width: 204px;
display: inline-block;
float: right;
margin-top: 15px;
@ -46,7 +46,21 @@ body {
#book-appointment .book-appoinment-step {
padding: 10px 20px;
height: 363px;
height: 434px;
}
#book-appointment .book-appoinment-step .book-step-content {
height: 370px;
margin-bottom: 10px;
}
#book-appointment .command-buttons {
float: right;
}
#book-appointment .command-buttons .btn {
min-width: 80px;
margin-right: 10px;
}
#book-appointment .book-step {
@ -55,20 +69,34 @@ body {
width: 20px;
float: left;
background: #FFF;
padding: 10px;
padding: 3px;
margin-right: 15px;
margin-top: 10px;
border: 3px solid #38A07A;
}
#book-appointment .book-step strong {
font-size: 25px;
font-size: 18px;
display: block;
text-align: center;
color: #B6DFC6;
}
#book-appointment .active-step {
display: inline-block;
height: 20px;
width: 20px;
float: left;
background: #FFF;
padding: 10px;
margin-right: 15px;
margin-top: 0px;
border: 3px solid #38A07A;
}
#book-appointment .active-step strong {
color: #396946;
font-size: 25px;
}
#book-appointment #frame-footer {
@ -82,6 +110,12 @@ body {
border-width: 2px;
}
#available-hours .available-hour {
font-size: 15px;
padding: 1px;
display: inline-block;
}
#available-hours .available-hour:hover {
font-weight: bold;
background-color: #CAEDF3;
@ -93,3 +127,7 @@ body {
font-weight: bold;
text-decoration: underline;
}
#book-appointment .span3 {
min-width: 270px; /* This is especially needed for ie8 */
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -0,0 +1,283 @@
/**
* 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.
*/
var bookAppointment = {
/**
* This method initializes the book appointment page.
*/
initialize : function() {
// Initialize page components.
$('.book-step').qtip({
position: {
my: 'top center',
at: 'bottom center'
},
style: {
classes: 'qtip-green qtip-shadow custom-qtip'
}
});
$('#select-date').datepicker({
//dateFormat : 'dd/mm/yy',
minDate : 0,
defaultDate : Date.today(),
onSelect : function(dateText, inst) {
bookAppointment.refreshAvailableHours(dateText);
bookAppointment.updateConfirmData();
}
});
// Bind event handlers.
bookAppointment.bindEventHandlers();
// Execute other necessary operations.
$('#select-service').trigger('change');
},
/**
* This method binds the necessary event handlers
* for the book appointments page.
*/
bindEventHandlers : function() {
/**
* Event : Selected Provider "Changed"
*/
$('#select-provider').change(function() {
bookAppointment.refreshAvailableHours(Date.today().toString('MM/dd/yyyy'));
bookAppointment.updateConfirmData();
});
/**
* Event : Selected Service "Changed"
*
* When the user clicks on a service, its available providers should
* become visible.
*/
$('#select-service').change(function() {
var currServiceId = $('#select-service').val();
$('#select-provider').empty();
$.each(GlobalVariables.providers, function(indexProvider, provider) {
$.each(provider['services'], function(indexService, serviceId) {
if (serviceId == currServiceId) {
// This provider can provide the selected service.
// Add him to the list box.
var optionHtml = '<option value="' + provider['id'] + '">' + provider['last_name']
+ ' ' + provider['first_name'] + '</option>';
$('#select-provider').append(optionHtml);
}
});
});
bookAppointment.refreshAvailableHours(Date.today().toString('MM/dd/yyyy'));
bookAppointment.updateConfirmData();
});
/**
* Event : Next Step Button "Clicked"
*/
$('.button-next').click(function() {
// If we are on the 3rd tab then we will need to validate the user's
// input.
if ($(this).attr('data-step_index') == 3) {
if (!bookAppointment.validateCustomerDataForm()) {
return; // Do not continue.
} else {
bookAppointment.updateConfirmData();
}
}
var nextTabIndex = parseInt($(this).attr('data-step_index')) + 1;
$(this).parents().eq(1).hide('fade', function() {
$('.active-step').removeClass('active-step');
$('#step-' + nextTabIndex).addClass('active-step');
$('#book-appointment-' + nextTabIndex).show('fade');
});
});
/**
* Event : Back Step Button "Clicked"
*/
$('.button-back').click(function() {
var prevTabIndex = parseInt($(this).attr('data-step_index')) - 1;
$(this).parents().eq(1).hide('fade', function() {
$('.active-step').removeClass('active-step');
$('#step-' + prevTabIndex).addClass('active-step');
$('#book-appointment-' + prevTabIndex).show('fade');
});
});
/**
* Event : Available Hour "Click"
*/
$('#available-hours').on('click', '.available-hour', function() {
$('.selected-hour').removeClass('selected-hour');
$(this).addClass('selected-hour');
bookAppointment.updateConfirmData();
});
},
/**
* This function makes an ajax call and returns the available
* hours for the selected service, provider and date.
*
* @param {string} selDate The selected date of which the available
* hours we need to receive.
*/
refreshAvailableHours : function(selDate) {
// Fetch the available hours of the current date
// for the chosen service and provider.
var selServiceDuration = 15; // Default duration.
$.each(GlobalVariables.services, function(index, service) {
if (service['id'] == $('#select-service').val()) {
selServiceDuration = service['duration'];
}
})
var postData = {
'serviceId' : $('#select-service').val(),
'providerId' : $('#select-provider').val(),
'selectedDate' : selDate,
'serviceDuration' : selServiceDuration
};
// Make ajax post request and get the available hours.
var ajaxurl = GlobalVariables.baseUrl + 'index.php/appointments/getAvailableHours';
jQuery.post(ajaxurl, postData, function(postResponse) {
////////////////////////////////////////////////////////////////////////////////
//console.log('\n\n Get Available Hours Post Response :', postResponse, '\n\n');
////////////////////////////////////////////////////////////////////////////////
try {
var jsonResponse = jQuery.parseJSON(postResponse);
////////////////////////////////////////////////////////////////////////////////
//console.log('\n\n Get Available Hours JSON Response :', jsonResponse, '\n\n');
////////////////////////////////////////////////////////////////////////////////
// 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 div:eq(' + (currColumn - 1) + ')')
.append('<span class="available-hour">' + availableHour + '</span><br/>');
});
// Set the first item as selected.
$('.available-hour:eq(0)').addClass('selected-hour');
bookAppointment.updateConfirmData();
} catch(exception) {
// @task Display message to the user.
};
});
},
/**
* This function validates the customer's data input.
* It only checks for empty fields by the time.
*
* @return {bool} Returns the validation result.
*/
validateCustomerDataForm : function() {
var validationResult = true;
$('.required').css('border', '');
$('.required').each(function() {
if ($(this).val() == '') {
validationResult = false;
$(this).css('border', '2px solid red');
}
});
return validationResult;
},
/**
* Every time this function is executed, it updates the confirmation
* page with the latest customer settigns and input for the appointment
* booking.
*/
updateConfirmData : function() {
/*** SET APPOINTMENT INFO ***/
var selectedDate = $('#select-date').datepicker('getDate');
if (selectedDate != null) {
selectedDate = Date.parse(selectedDate).toString('dd/MM/yyyy');
}
$('#appointment-info').html(
'<h4>' + $('#select-service option:selected').text() + '</h4>' +
$('#select-provider option:selected').text() + '<br/>' +
'<strong class="text-info">' + selectedDate + ' ' + $('.selected-hour').text() + '</strong>'
);
/*** SET CUSTOMER'S INFO ***/
$('#customer-info').html(
'<h4>' + $('#last-name').val() + ' ' + $('#first-name').val() + '</h4>' +
'Phone: ' + $('#phone-number').val() + '<br/>' +
'Email: ' + $('#email').val() + '<br/>' +
'Address: ' + $('#address').val() + '<br/>' +
'City: ' + $('#city').val() + '<br/>' +
'Zip Code: ' + $('#zip-code').val()
);
/*** UPDATE HIDDEN FIELD VALUES ***/
$('input[name="lastName"]').val($('#last-name').val());
$('input[name="firstName"]').val($('#first-name').val());
$('input[name="email"]').val($('#email').val());
$('input[name="phoneNumber"]').val($('#phone-number').val());
$('input[name="address"]').val($('#address').val());
$('input[name="city"]').val($('#city').val());
$('input[name="zipCode"]').val($('#zip-code').val());
var startDatetime = $('#select-date').datepicker('getDate').toString('yyyy-MM-dd') + ' ' + $('.selected-hour').text();
var endDatetime = bookAppointment.getEndDatetime();
$('input[name="startDatetime"]').val(startDatetime);
$('input[name="endDatetime"]').val(endDatetime);
$('input[name="notes"]').val($('#notes').val());
$('input[name="providerId"]').val($('#select-provider').val());
$('input[name="serviceId"]').val($('#select-service').val());
},
/**
* This method calculates the end datetime of the current appointment.
* End datetime is depending on the service and start datetime fieldss.
*
* @return {string} Returns the end datetime in string format.
*/
getEndDatetime : function() {
// Find selected service duration.
var selServiceDuration = undefined;
$.each(GlobalVariables.services, function(index, service) {
if (service.id == $('#select-service').val()) {
selServiceDuration = service.duration;
return; // Stop searching ...
}
});
// Add the duration to the start datetime.
var startDatetime = $('#select-date').datepicker('getDate').toString('MM/dd/yyyy') + ' ' + $('.selected-hour').text();
startDatetime = Date.parseExact(startDatetime, 'MM/dd/yyyy HH:mm');
var endDatetime = undefined;
if (selServiceDuration != undefined && startDatetime != null) {
endDatetime = startDatetime.add({ minutstartDatetimees : parseInt(selServiceDuration) });
} else {
endDatetime = new Date();
}
return endDatetime.toString('yyyy-MM-dd HH:mm');
}
}