Added loading image on backend ajax requests.

Made some code corrections on the appointments controller. 
Fixed minor problems.
This commit is contained in:
alextselegidis@gmail.com 2013-06-28 14:23:17 +00:00
parent 07e244247d
commit 71fb87d685
25 changed files with 479 additions and 253 deletions

View file

@ -1,16 +1,13 @@
VERSION 0.3
VERSION 0.4
===========
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.
- Complete functionality of Backend Calendar Page
- Enable Google Calendar sync (sync changes made from GCal to E!A)
Minor
- Display user friendly error messages.
- Added sync exception to Google Sync library.
- Display javascript ajax error messages to users.

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

View file

@ -0,0 +1,53 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of this License; and
You must cause any modified files to carry prominent notices stating that You changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -91,53 +91,16 @@ class Appointments extends CI_Controller {
$appointment_data['hash'] = $this->Appointments_Model
->get_value('hash', $appointment_data['id']);
// :: SEND NOTIFICATION EMAILS TO BOTH CUSTOMER AND PROVIDER
$this->load->library('Notifications');
$provider_data = $this->Providers_Model
->get_row($appointment_data['id_users_provider']);
$service_data = $this->Services_Model->get_row($appointment_data['id_services']);
$company_settings = array(
'company_name' => $this->Settings_Model->get_setting('company_name'),
'company_link' => $this->Settings_Model->get_setting('company_link'),
'company_email' => $this->Settings_Model->get_setting('company_email')
);
if (!$post_data['manage_mode']) {
$customer_title = 'Your appointment has been successfully booked!';
$customer_message = 'Thank you for arranging an appointment with us. ' .
'Below you can see the appointment details. Make changes ' .
'by clicking the appointment link.';
$customer_link = $this->config->item('base_url') . 'appointments/index/'
. $appointment_data['hash'];
$provider_title = 'A new appointment has been added to your plan.';
$provider_message = 'You can make changes by clicking the appointment ' .
'link below';
$provider_link = $this->config->item('base_url') . 'backend/'
. $appointment_data['hash'];
} else {
$customer_title = 'Appointment changes have been successfully saved!';
$customer_message = '';
$customer_link = $this->config->item('base_url') . 'appointments/index/'
. $appointment_data['hash'];
$provider_title = 'Appointment details have changed.';
$provider_message = '';
$provider_link = $this->config->item('base_url') . 'backend/'
. $appointment_data['hash'];
}
$this->notifications->send_appointment_details(
$appointment_data, $provider_data, $service_data, $customer_data,
$company_settings, $customer_title, $customer_message, $customer_link,
$customer_data['email']);
$this->notifications->send_appointment_details(
$appointment_data, $provider_data, $service_data, $customer_data,
$company_settings, $provider_title, $provider_message, $provider_link,
$provider_data['email']);
// :: SYNCHRONIZE APPOINTMENT WITH PROVIDER'S GOOGLE CALENDAR
// The provider must have previously granted access to his google calendar account
// in order to sync the appointment.
@ -156,13 +119,51 @@ class Appointments extends CI_Controller {
$this->google_sync->add_appointment($appointment_data['id']);
} else {
// Update appointment to Google Calendar.
$appointment_data['id_google_calendar'] = $this->Appointments_Model
$appointment_data['id_google_calendar'] =
$this->Appointments_Model
->get_value('id_google_calendar', $appointment_data['id']);
$this->google_sync->update_appointment($appointment_data, $provider_data,
$service_data, $customer_data, $company_settings);
}
}
// :: SEND NOTIFICATION EMAILS TO BOTH CUSTOMER AND PROVIDER
$this->load->library('Notifications');
if (!$post_data['manage_mode']) {
$customer_title = 'Your appointment has been successfully booked!';
$customer_message = 'Thank you for arranging an appointment with us. '
. 'Below you can see the appointment details. Make changes '
. 'by clicking the appointment link.';
$customer_link = $this->config->item('base_url') . 'appointments/index/'
. $appointment_data['hash'];
$provider_title = 'A new appointment has been added to your plan.';
$provider_message = 'You can make changes by clicking the appointment '
. 'link below';
$provider_link = $this->config->item('base_url') . 'backend/'
. $appointment_data['hash'];
} else {
$customer_title = 'Appointment changes have been successfully saved!';
$customer_message = '';
$customer_link = $this->config->item('base_url') . 'appointments/index/'
. $appointment_data['hash'];
$provider_title = 'Appointment details have changed.';
$provider_message = '';
$provider_link = $this->config->item('base_url') . 'backend/'
. $appointment_data['hash'];
}
$this->notifications->send_appointment_details($appointment_data, $provider_data,
$service_data, $customer_data,$company_settings, $customer_title,
$customer_message, $customer_link, $customer_data['email']);
$this->notifications->send_appointment_details($appointment_data, $provider_data,
$service_data, $customer_data, $company_settings, $provider_title,
$provider_message, $provider_link, $provider_data['email']);
// :: LOAD THE BOOK SUCCESS VIEW
$view_data = array(
'appointment_data' => $appointment_data,
@ -177,6 +178,7 @@ class Appointments extends CI_Controller {
'technical' => $exc->getTraceAsString()
);
}
$this->load->view('appointments/book_success', $view_data);
}
}
@ -210,6 +212,7 @@ class Appointments extends CI_Controller {
$provider_data = $this->Providers_Model->get_row($appointment_data['id_users_provider']);
$customer_data = $this->Customers_Model->get_row($appointment_data['id_users_customer']);
$service_data = $this->Services_Model->get_row($appointment_data['id_services']);
$company_settings = array(
'company_name' => $this->Settings_Model->get_setting('company_name'),
'company_email' => $this->Settings_Model->get_setting('company_email'),
@ -245,10 +248,10 @@ class Appointments extends CI_Controller {
} catch(Exception $exc) {
// Display the error message to the customer.
$view_data['error_message'] = $exc->getMessage();
$view_data['error'] = $exc->getMessage();
}
$this->load->view('appointments/cancel');
$this->load->view('appointments/cancel', $view_data);
}
/**
@ -257,8 +260,11 @@ class Appointments extends CI_Controller {
* This method answers to an AJAX request. It calculates the available hours for the
* given service, provider and date.
*
* @param array $_POST['post_data'] An associative array that contains the user selected
* 'service_id', 'provider_id', 'selected_date' and 'service_duration' in minutes.
* @param numeric $_POST['service_id'] The selected service's record id.
* @param numeric $_POST['provider_id'] The selected provider's record id.
* @param string $_POST['selected_date'] The selected date of which the available hours
* we want to see.
* @param numeric $_POST['service_duration'] The selected service duration in minutes.
* @return Returns a json object with the available hours.
*/
public function ajax_get_available_hours() {
@ -266,139 +272,14 @@ class Appointments extends CI_Controller {
$this->load->model('Appointments_Model');
$this->load->model('Settings_Model');
// Get the provider's working plan and reserved appointments.
$working_plan = json_decode($this->Providers_Model
->get_setting('working_plan', $_POST['provider_id']), true);
// If manage mode is TRUE then the following we should not consider the selected
// appointment when calculating the available time periods of the provider.
$exclude_appointments = ($_POST['manage_mode'] === 'true')
? array($_POST['appointment_id'])
: array();
$where_clause = array(
'DATE(start_datetime)' => date('Y-m-d', strtotime($_POST['selected_date'])),
'id_users_provider' => $_POST['provider_id']
);
$reserved_appointments = $this->Appointments_Model->get_batch($where_clause);
if ($_POST['manage_mode'] === 'true') {
// Current record id shouldn't be included as reserved time, when the
// manage mode is true.
foreach($reserved_appointments as $index=>$appointment) {
if ($appointment['id'] == $_POST['appointment_id']) {
unset($reserved_appointments[$index]);
}
}
}
// Find the empty spaces on the plan. The first split between
// the plan is due to a break (if exist). After that every reserved
// appointment is considered to be a taken space in the plan.
$sel_date_working_plan = $working_plan[strtolower(date('l',
strtotime($_POST['selected_date'])))];
$empty_spaces_with_breaks = array();
if (isset($sel_date_working_plan['breaks'])) {
foreach($sel_date_working_plan['breaks'] as $index=>$break) {
// Split the working plan to available time periods that do not
// contain the breaks in them.
$last_break_index = $index - 1;
if (count($empty_spaces_with_breaks) === 0) {
$start_hour = $sel_date_working_plan['start'];
$end_hour = $break['start'];
} else {
$start_hour = $sel_date_working_plan['breaks'][$last_break_index]['end'];
$end_hour = $break['start'];
}
$empty_spaces_with_breaks[] = array(
'start' => $start_hour,
'end' => $end_hour
);
}
// Add the space from the last break to the end of the day.
$empty_spaces_with_breaks[] = array(
'start' => $sel_date_working_plan['breaks'][$index]['end'],
'end' => $sel_date_working_plan['end']
);
}
// Break the empty spaces with the reserved appointments.
$empty_spaces_with_appointments = array();
if (count($reserved_appointments) > 0) {
foreach($empty_spaces_with_breaks as $space) {
foreach($reserved_appointments as $index=>$appointment) {
$appointment_start = date('H:i', strtotime($appointment['start_datetime']));
$appointment_end = date('H:i', strtotime($appointment['end_datetime']));
$space_start = date('H:i', strtotime($space['start']));
$space_end = date('H:i', strtotime($space['end']));
if ($space_start < $appointment_start && $space_end > $appointment_end) {
// We need to check whether another appointment fits in the current
// space. If this happens, then we need to consider the whole appointment
// time as one, because the provider will not be available.
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) {
if ($appointment_start > $appt_start) {
$appointment_start = $appt_start;
}
if ($appointment_end < $appt_end) {
$appointment_end = $appt_end;
}
}
}
// Current appointment is within the current empty space. So
// we need to break the empty space into two other spaces that
// don't include the appointment.
$new_space = array(
'start' => $space_start,
'end' => $appointment_start
);
if (!in_array($new_space, $empty_spaces_with_appointments)) {
$empty_spaces_with_appointments[] = $new_space;
}
$new_space = array(
'start' => $appointment_end,
'end' => $space_end
);
if (!in_array($new_space, $empty_spaces_with_appointments)) {
$empty_spaces_with_appointments[] = $new_space;
}
} else {
// 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;
}
}
}
}
} else {
$empty_spaces_with_appointments = $empty_spaces_with_breaks;
}
$empty_spaces = $empty_spaces_with_appointments;
$empty_periods = $this->get_provider_available_time_periods($_POST['provider_id'],
$_POST['selected_date'], $exclude_appointments);
// Calculate the available appointment hours for the given date.
// The empty spaces are broken down to 15 min and if the service
@ -406,9 +287,9 @@ class Appointments extends CI_Controller {
// $available hours array.
$available_hours = array();
foreach($empty_spaces as $space) {
$start_hour = new DateTime($_POST['selected_date'] . ' ' . $space['start']);
$end_hour = new DateTime($_POST['selected_date'] . ' ' . $space['end']);
foreach($empty_periods as $period) {
$start_hour = new DateTime($_POST['selected_date'] . ' ' . $period['start']);
$end_hour = new DateTime($_POST['selected_date'] . ' ' . $period['end']);
$minutes = $start_hour->format('i');
@ -427,8 +308,8 @@ class Appointments extends CI_Controller {
}
$curr_hour = $start_hour;
$diff = $curr_hour->diff($end_hour);
while(($diff->h * 60 + $diff->i) > intval($_POST['service_duration'])) {
$available_hours[] = $curr_hour->format('H:i');
$curr_hour->add(new DateInterval("PT15M"));
@ -464,6 +345,220 @@ class Appointments extends CI_Controller {
echo json_encode($available_hours);
}
/**
* Check whether the provider is still available in the selected appointment date.
*
* It might be times where two or more customers select the same appointment date and time.
* This shouldn't be allowed to happen, so one of the two customers will eventually get the
* prefered date and the other one will have to choose for another date. Use this method
* just before the customer confirms the appointment details. If the selected date was taken
* in the mean time, the customer must be prompted to select another time for his appointment.
*
* @param int $_POST['id_users_provider'] The selected provider's record id.
* @param int $_POST['id_services'] The selected service's record id.
* @param string $_POST['start_datetime'] This is a mysql formed string.
* @return bool Returns whether the selected datetime is still available.
*/
public function ajax_check_datetime_availability() {
try {
$this->load->model('Services_Model');
$service_duration = $this->Services_Model->get_value('duration', $_POST['id_services']);
$available_periods = $this->get_provider_available_time_periods(
$_POST['id_users_provider'], $_POST['start_datetime']);
$is_still_available = FALSE;
foreach($available_periods as $period) {
$appt_start = new DateTime($_POST['start_datetime']);
$appt_start = $appt_start->format('H:i');
$appt_end = new DateTime($_POST['start_datetime']);
$appt_end->add(new DateInterval('PT' . $service_duration . 'M'));
$appt_end = $appt_end->format('H:i');
$period_start = date('H:i', strtotime($period['start']));
$period_end = date('H:i', strtotime($period['end']));
if ($period_start < $appt_start && $period_end > $appt_end) {
$is_still_available = TRUE;
break;
}
}
echo json_encode($is_still_available);
} catch(Exception $exc) {
echo json_encode(array(
'error' => $exc->getMessage()
));
}
}
/**
* Get an array containing the free time periods (start - end) of a selected date.
*
* This method is very important because there are many cases where the system needs to
* know when a provider is avaible for an appointment. This method will return an array
* that belongs to the selected date and contains values that have the start and the end
* time of an available time period.
*
* @param numeric $provider_id The provider's record id.
* @param string $selected_date The date to be checked (MySQL formatted string).
* @param array $exclude_appointments This array contains the ids of the appointments that
* will not be taken into consideration when the available time periods are calculated.
* @return array Returns an array with the available time periods of the provider.
*/
private function get_provider_available_time_periods($provider_id, $selected_date,
$exclude_appointments = array()) {
$this->load->model('Appointments_Model');
$this->load->model('Providers_Model');
// Get the provider's working plan and reserved appointments.
$working_plan = json_decode($this->Providers_Model
->get_setting('working_plan', $provider_id), true);
$where_clause = array(
'DATE(start_datetime)' => date('Y-m-d', strtotime($selected_date)),
'id_users_provider' => $provider_id
);
$reserved_appointments = $this->Appointments_Model->get_batch($where_clause);
// Sometimes it might be necessary to not take into account some appointment records
// in order to display what the providers available time periods would be without them.
foreach($exclude_appointments as $excluded_appointment) {
foreach($reserved_appointments as $index=>$appointment) {
if ($appointment['id'] == $excluded_appointment['id']) {
unset($reserved_appointments[$index]);
}
}
}
// Find the empty spaces on the plan. The first split between the plan is due to
// a break (if exist). After that every reserved appointment is considered to be
// a taken space in the plan.
$selected_date_working_plan = $working_plan[strtolower(date('l',
strtotime($selected_date)))];
$available_periods_with_breaks = array();
if (isset($selected_date_working_plan['breaks'])) {
foreach($selected_date_working_plan['breaks'] as $index=>$break) {
// Split the working plan to available time periods that do not
// contain the breaks in them.
$last_break_index = $index - 1;
if (count($available_periods_with_breaks) === 0) {
$start_hour = $selected_date_working_plan['start'];
$end_hour = $break['start'];
} else {
$start_hour = $selected_date_working_plan['breaks'][$last_break_index]['end'];
$end_hour = $break['start'];
}
$available_periods_with_breaks[] = array(
'start' => $start_hour,
'end' => $end_hour
);
}
// Add the period from the last break to the end of the day.
$available_periods_with_breaks[] = array(
'start' => $selected_date_working_plan['breaks'][$index]['end'],
'end' => $selected_date_working_plan['end']
);
}
// Break the empty periods with the reserved appointments.
$available_periods_with_appointments = array();
if (count($reserved_appointments) > 0) {
foreach($available_periods_with_breaks as $period) {
foreach($reserved_appointments as $index=>$excluded_appointment) {
$appointment_start = date('H:i', strtotime($excluded_appointment['start_datetime']));
$appointment_end = date('H:i', strtotime($excluded_appointment['end_datetime']));
$period_start = date('H:i', strtotime($period['start']));
$period_end = date('H:i', strtotime($period['end']));
if ($period_start < $appointment_start && $period_end > $appointment_end) {
// We need to check whether another appointment fits in the current
// time period. If this happens, then we need to consider the whole
// appointment time as one, because the provider will not be available.
foreach ($reserved_appointments as $tmp_appointment) {
$appt_start = date('H:i', strtotime($tmp_appointment['start_datetime']));
$appt_end = date('H:i', strtotime($tmp_appointment['end_datetime']));
if ($period_start < $appt_start && $period_end > $appt_end) {
if ($appointment_start > $appt_start) {
$appointment_start = $appt_start;
}
if ($appointment_end < $appt_end) {
$appointment_end = $appt_end;
}
}
}
// Current appointment is within the current empty space. So
// we need to break the empty space into two other spaces that
// don't include the appointment.
$new_period = array(
'start' => $period_start,
'end' => $appointment_start
);
if (!in_array($new_period, $available_periods_with_appointments)) {
$available_periods_with_appointments[] = $new_period;
}
$new_period = array(
'start' => $appointment_end,
'end' => $period_end
);
if (!in_array($new_period, $available_periods_with_appointments)) {
$available_periods_with_appointments[] = $new_period;
}
} else {
// 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 $tmp_appointment) {
$appt_start = date('H:i', strtotime($tmp_appointment['start_datetime']));
$appt_end = date('H:i', strtotime($tmp_appointment['end_datetime']));
if ($period_start < $appt_start && $period_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_period = array(
'start' => $period_start,
'end' => $period_end
);
$already_exist = in_array($empty_period, $available_periods_with_appointments);
if ($found === FALSE && $already_exist === FALSE) {
$available_periods_with_appointments[] = $empty_period;
}
}
}
}
} else {
$available_periods_with_appointments = $available_periods_with_breaks;
}
return $available_periods_with_appointments;
}
}
/* End of file appointments.php */

View file

@ -120,18 +120,24 @@ class Backend extends CI_Controller {
$provider_data = $this->Providers_Model->get_row($appointment_data['id_users_provider']);
$customer_data = $this->Customers_Model->get_row($appointment_data['id_users_customer']);
$service_data = $this->Services_Model->get_row($appointment_data['id_services']);
$company_settings = array(
'company_name' => $this->Settings_Model->get_setting('company_name')
'company_name' => $this->Settings_Model->get_setting('company_name'),
'company_link' => $this->Settings_Model->get_setting('company_link'),
'company_email' => $this->Settings_Model->get_setting('company_email')
);
// :: SYNC APPOINTMENT CHANGES WITH GOOGLE CALENDAR
if ($appointment_data['id_google_calendar'] != NULL) {
$google_sync = $this->Providers_Model
->get_setting('google_sync', $appointment_data['id_users_provider']);
if ($google_sync == TRUE) {
$google_token = json_decode($this->Providers_Model
->get_setting('google_token', $appointment_data['id_users_provider']));
$this->load->library('Google_Sync');
$this->google_sync->refresh_token($google_token->refresh_token);
$this->google_sync->update_appointment($appointment_data, $provider_data,
$service_data, $customer_data, $company_settings);
@ -142,12 +148,23 @@ class Backend extends CI_Controller {
$this->load->library('Notifications');
$customer_title = 'Appointment Changes Saved Successfully!';
$provider_title = 'Appointment Details Have Changed';
$customer_message = 'Your appointment details have changed. The new details are '
. 'listed below';
$customer_link = $this->config->item('base_url') . 'appointments/index/'
. $appointment_data['hash'];
$this->notifications->send_book_success(
$customer_data, $appointment_data, $customer_title);
$this->notifications->send_new_appointment(
$customer_data, $appointment_data, $provider_title);
$provider_title = 'Appointment Details Have Changed';
$provider_message = 'The new appointment details are listed below:';
$provider_link = $this->config->item('base_url') . 'backend/index/'
. $appointment_data['hash'];
$this->notifications->send_appointment_details($appointment_data, $provider_data,
$service_data, $customer_data, $company_settings, $customer_title,
$customer_message, $customer_link, $customer_data['email']);
$this->notifications->send_appointment_details($appointment_data, $provider_data,
$service_data, $customer_data, $company_settings, $provider_title,
$provider_message, $provider_link, $provider_data['email']);
echo json_encode('SUCCESS');
@ -185,6 +202,7 @@ class Backend extends CI_Controller {
$provider_data = $this->Providers_Model->get_row($appointment_data['id_users_provider']);
$customer_data = $this->Customers_Model->get_row($appointment_data['id_users_customer']);
$service_data = $this->Services_Model->get_row($appointment_data['id_services']);
$company_settings = array(
'company_name' => $this->Settings_Model->get_setting('company_name'),
'company_email' => $this->Settings_Model->get_setting('company_email'),

View file

@ -48,12 +48,12 @@
<div id="success-frame" class="frame-container">
<img id="success-icon" src="<?php echo $this->config->base_url(); ?>assets/images/success.png" />
<h3>Your appointment has been successfully cancelled!</h3>
<h3>Your appointment has been successfully canceled!</h3>
<?php
// Display exception message (if any).
if (isset($error_message)) {
echo $error_message;
if (isset($error)) {
echo $error;
}
?>
</div>

View file

@ -108,4 +108,9 @@
</div>
</div>
<div id="notification" style="display: none;"></div>
<div id="loading" style="display: none;">
<img src="<?php echo $base_url; ?>assets/images/loading.gif" />
</div>

View file

@ -31,6 +31,9 @@ root {
#footer #footer-content { padding: 15px; }
#notification strong { margin-right: 15px; }
#loading { position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; z-index: 999999;
background: rgba(255, 255, 255, 0.75);}
#loading img { margin: auto; display: block; }
/* BACKEND CALENDAR PAGE
-------------------------------------------------------------------- */

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View file

@ -5,8 +5,15 @@ $(document).ready(function() {
$(window).resize(function() {
Backend.placeFooterToBottom();
}).trigger('resize');
$(document).ajaxStart(function() {
$('#loading').show();
});
$(document).ajaxStop(function() {
$('#loading').hide();
});
});
/**
* This namespace contains functions that are used in the backend section of

View file

@ -27,7 +27,7 @@ var BackendCalendar = {
defaultView : 'agendaWeek',
height : BackendCalendar.getCalendarHeight(),
editable : true,
slotMinutes : 15,
slotMinutes : 30,
columnFormat : {
month : 'ddd',
week : 'ddd d/M',
@ -230,7 +230,7 @@ var BackendCalendar = {
});
/**
* Event: Delete Popover Button "Click"
* Event: Popover Delete Button "Click"
*
* Displays a prompt on whether the user wants the appoinmtent to be
* deleted. If he confirms the deletion then an ajax call is made to
@ -545,6 +545,9 @@ var BackendCalendar = {
*/
calendarEventResize : function(event, dayDelta, minuteDelta, revertFunc,
jsEvent, ui, view) {
if ($('#notification').is(':visible')) {
$('#notification').hide('bind');
}
// :: PREPARE THE APPOINTMENT DATA
var appointmentData = GeneralFunctions.clone(event.data);
@ -564,7 +567,7 @@ var BackendCalendar = {
var successCallback = function(response) {
if (response.error) {
// Display error message to the user.
Backend.displayNotification(reponse.error);
Backend.displayNotification(response.error);
return;
}
@ -577,6 +580,7 @@ var BackendCalendar = {
var postUrl = GlobalVariables.baseUrl
+ 'backend/ajax_save_appointment_changes';
var postData = {
'appointment_data' : JSON.stringify(appointmentData)
};
@ -688,6 +692,10 @@ var BackendCalendar = {
*/
calendarEventDrop : function(event, dayDelta, minuteDelta, allDay,
revertFunc, jsEvent, ui, view) {
if ($('#notification').is(':visible')) {
$('#notification').hide('bind');
}
// :: PREPARE THE APPOINTMENT DATA
var appointmentData = GeneralFunctions.clone(event.data);

View file

@ -1,7 +1,7 @@
/**
* This namespace contains functions that implement the book appointment
* page functionality. Once the initialize() method is called the page is
* fully functional and can serve the appointment booking process.
* This namespace contains functions that implement the book appointment page
* functionality. Once the initialize() method is called the page is fully
* functional and can serve the appointment booking process.
*
* @namespace FrontendBook
*/
@ -16,11 +16,10 @@ var FrontendBook = {
/**
* This method initializes the book appointment page.
*
* @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.
* @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, manageMode) {
if (bindEventHandlers === undefined) {
@ -121,8 +120,8 @@ var FrontendBook = {
* be perfomed, depending the current wizard step.
*/
$('.button-next').click(function() {
// If we are on the 2nd tab then the user should have
// an appointment hour selected.
// If we are on the 2nd tab then the user should have an appointment hour
// selected.
if ($(this).attr('data-step_index') === '2') {
if ($('.selected-hour').length == 0) {
if ($('#select-hour-prompt').length == 0) {
@ -209,6 +208,47 @@ var FrontendBook = {
+ 'be undone.', dialogButtons);
});
}
/**
* Event : Book Appointment Form "Submit"
*
* Before the form is submitted to the server we need to make sure that
* in the meantime the selected appointment date/time wasn't reserved by
* another customer or event.
*/
$('#book-appointment-form').submit(function() {
event.preventDefault();
var formData = jQuery.parseJSON($('input[name="post_data"]').val());
var postData = {
'id_users_provider' : formData['appointment']['id_users_provider'],
'id_services' : formData['appointment']['id_services'],
'start_datetime' : formData['appointment']['start_datetime']
};
var postUrl = GlobalVariables.baseUrl + 'appointments/ajax_check_datetime_availability';
$.post(postUrl, postData, function(response) {
////////////////////////////////////////////////////////////////////////
console.log('Check Date/Time Availability Post Response :', response);
////////////////////////////////////////////////////////////////////////
if (response.error) {
GeneralFunctions.displayMessageBox('An Unexpected Error Occured',
response.error);
}
if (response === true) {
$('#book-appointment-form').submit();
} else {
GeneralFunctions.displayMessageBox('Appointment Hour Taken', 'Unfortunately '
+ 'the selected appointment hour is not available anymore. Please select '
+ 'another hour.');
FrontendBook.getAvailableHours($('#select-date').datepicker('getDate'));
}
}, 'json');
});
},
/**
@ -463,4 +503,4 @@ var FrontendBook = {
return false;
}
}
}
};

View file

@ -1,13 +1,13 @@
<?php
class SystemConfiguration {
// General Settings
public static $base_url = 'http://localhost/dev/easy_appointments/trunk/src/';
public static $base_url = 'http://localhost/dev/external/easy_appointments/trunk/src/';
// Database Settings
public static $db_host = 'localhost';
public static $db_name = 'easy_appointments';
public static $db_username = 'root';
public static $db_password = 'root';
public static $db_password = '';
// Google Calendar API Settings
public static $google_product_name = 'Easy!Appointments';