* Fixed google sync problem with unavailable time periods.

* Fixed minor issues with backend. 
* Fixed get_provider_available_time_periods() method - now works correctly. 
* Added unavailable notes in the calendar (under the "Unavailable" title). 
* Updated the thesis code snippets.
* Added listings.pdf, a free ebook about latex listings.
This commit is contained in:
alextselegidis@gmail.com 2013-10-27 17:41:37 +00:00
parent 140cb62627
commit 2e7e668465
20 changed files with 276 additions and 302 deletions

View file

@ -47,7 +47,7 @@
emph =[1]{php}, emph =[1]{php},
emphstyle =[1]\color{black}, emphstyle =[1]\color{black},
emph =[2]{if,and,or,else,public,function,try,catch,return}, emph =[2]{if,and,or,else,public,function,try,catch,return},
emphstyle =[2]\color{dkyellow}, emphstyle =[2]\color{dkblue},
numbers = left, numbers = left,
tabsize = 2, tabsize = 2,
backgroundcolor = \color{ltgrey}, backgroundcolor = \color{ltgrey},

View file

@ -1,7 +1,7 @@
<?php <?php
/** /**
* Complete synchronization of appointments between Google Calendar * Complete synchronization of appointments between Google
* and Easy!Appointments. * Calendar and Easy!Appointments.
* *
* This method will completely sync the appointments of a provider * This method will completely sync the appointments of a provider
* with his Google Calendar account. The sync period needs to be * with his Google Calendar account. The sync period needs to be
@ -34,8 +34,8 @@ public function sync($provider_id = NULL) {
$google_sync = $this->providers_model $google_sync = $this->providers_model
->get_setting('google_sync', $provider['id']); ->get_setting('google_sync', $provider['id']);
if (!$google_sync) { if (!$google_sync) {
throw new Exception('The selected provider has not the ' throw new Exception('The selected provider has not the '
. 'google synchronization setting enabled.'); . 'Google synchronization setting enabled.');
} }
$google_token = json_decode($this->providers_model $google_token = json_decode($this->providers_model
@ -43,66 +43,75 @@ public function sync($provider_id = NULL) {
$this->load->library('google_sync'); $this->load->library('google_sync');
$this->google_sync->refresh_token($google_token->refresh_token); $this->google_sync->refresh_token($google_token->refresh_token);
// Fetch provider's appointments that belong to the // Fetch provider's appointments that belong to the sync
// sync time period. // time period.
$sync_past_days = $this->providers_model $sync_past_days = $this->providers_model
->get_setting('sync_past_days', $provider['id']); ->get_setting('sync_past_days', $provider['id']);
$sync_future_days = $this->providers_model $sync_future_days = $this->providers_model
->get_setting('sync_future_days', $provider['id']); ->get_setting('sync_future_days', $provider['id']);
$start = strtotime('-' . $sync_past_days . ' days', $start = strtotime('-' . $sync_past_days
strtotime(date('Y-m-d'))); . ' days', strtotime(date('Y-m-d')));
$end = strtotime('+' . $sync_future_days . ' days', $end = strtotime('+' . $sync_future_days
strtotime(date('Y-m-d'))); . ' days', strtotime(date('Y-m-d')));
$where_clause = array( $where_clause = array(
'start_datetime >=' => date('Y-m-d H:i:s', $start), 'start_datetime >=' => date('Y-m-d H:i:s', $start),
'end_datetime <=' => date('Y-m-d H:i:s', $end), 'end_datetime <=' => date('Y-m-d H:i:s', $end),
'id_users_provider' => $provider['id'], 'id_users_provider' => $provider['id']
'is_unavailable' => FALSE
); );
$appointments = $this->appointments_model $appointments =
->get_batch($where_clause); $this->appointments_model->get_batch($where_clause);
$company_settings = array( $company_settings = array(
'company_name' => 'company_name' => $this->settings_model
$this->settings_model->get_setting('company_name'), ->get_setting('company_name'),
'company_link' => 'company_link' => $this->settings_model
$this->settings_model->get_setting('company_link'), ->get_setting('company_link'),
'company_email' => 'company_email' => $this->settings_model
$this->settings_model->get_setting('company_email') ->get_setting('company_email')
); );
// Sync each appointment with Google Calendar by following // Sync each appointment with Google Calendar by following
// the project's sync protocol (see documentation). // the project's sync protocol (see documentation).
foreach($appointments as $appointment) { foreach($appointments as $appointment) {
$service = $this->services_model if ($appointment['is_unavailable'] == FALSE) {
->get_row($appointment['id_services']); $service = $this->services_model
$customer = $this->customers_model ->get_row($appointment['id_services']);
->get_row($appointment['id_users_customer']); $customer = $this->customers_model
->get_row($appointment['id_users_customer']);
} else {
$service = NULL;
$customer = NULL;
}
// If current appointment not synced yet, add to gcal. // If current appointment not synced yet, add to gcal.
if ($appointment['id_google_calendar'] == NULL) { if ($appointment['id_google_calendar'] == NULL) {
$google_event = $this->google_sync $google_event = $this->google_sync
->add_appointment($appointment, $provider, $service, ->add_appointment($appointment, $provider,
$customer, $company_settings); $service, $customer, $company_settings);
$appointment['id_google_calendar'] = $google_event->id; $appointment['id_google_calendar'] = $google_event->id;
$this->appointments_model->add($appointment); // Save gcal id $this->appointments_model->add($appointment);// Save gcal id
} else { } else {
// Appointment is synced with google calendar. // Appointment is synced with google calendar.
try { try {
$google_event = $this->google_sync $google_event = $this->google_sync
->get_event($appointment['id_google_calendar']); ->get_event($appointment['id_google_calendar']);
// If gcal event is different from e!a appointment then if ($google_event->status == 'cancelled') {
// update Easy!Appointments record. throw new Exception('Event is cancelled, remove the '
. 'record from Easy!Appointments.');
}
// If gcal event is different from e!a appointment
// then update e!a record.
$is_different = FALSE; $is_different = FALSE;
$appt_start = strtotime($appointment['start_datetime']); $appt_start = strtotime($appointment['start_datetime']);
$appt_end = strtotime($appointment['end_datetime']); $appt_end = strtotime($appointment['end_datetime']);
$event_start = $event_start =
strtotime($google_event->getStart()->getDateTime()); strtotime($google_event->getStart()->getDateTime());
$event_end = $event_end =
strtotime($google_event->getEnd()->getDateTime()); strtotime($google_event->getEnd()->getDateTime());
if ($appt_start != $event_start if ($appt_start != $event_start
|| $appt_end != $event_end) { || $appt_end != $event_end) {
@ -146,7 +155,6 @@ public function sync($provider_id = NULL) {
'id_users_customer' => NULL, 'id_users_customer' => NULL,
'id_services' => NULL, 'id_services' => NULL,
); );
$this->appointments_model->add($appointment); $this->appointments_model->add($appointment);
} }
} }

View file

@ -6,16 +6,16 @@
* This method is very important because there are many cases where * This method is very important because there are many cases where
* the system needs to know when a provider is avaible for an * the system needs to know when a provider is avaible for an
* appointment. This method will return an array that belongs to the * appointment. This method will return an array that belongs to the
* selected date and contains values that have the start and the end * selected date and contains values that have the start and the end
* time of an available time period. * time of an available time period.
* *
* @param numeric $provider_id The provider's record id. * @param numeric $provider_id The provider's record id.
* @param string $selected_date The date to be checked (MySQL * @param string $selected_date The date to be checked (MySQL
* formatted string). * formatted string).
* @param array $exclude_appointments This array contains the ids of * @param array $exclude_appointments This array contains the ids of
* the appointments that will not be taken into consideration when * the appointments that will not be taken into consideration when the
* the available time periods are calculated. * available time periods are calculated.
* @return array Returns an array with the available time periods of * @return array Returns an array with the available time periods of
* the provider. * the provider.
*/ */
private function get_provider_available_time_periods($provider_id, private function get_provider_available_time_periods($provider_id,
@ -28,16 +28,15 @@ private function get_provider_available_time_periods($provider_id,
->get_setting('working_plan', $provider_id), true); ->get_setting('working_plan', $provider_id), true);
$where_clause = array( $where_clause = array(
'DATE(start_datetime)' => 'DATE(start_datetime)'=>date('Y-m-d', strtotime($selected_date)),
date('Y-m-d', strtotime($selected_date)), 'id_users_provider'=>$provider_id
'id_users_provider' => $provider_id
); );
$reserved_appointments = $reserved_appointments = $this->appointments_model
$this->appointments_model->get_batch($where_clause); ->get_batch($where_clause);
// Sometimes it might be necessary to not take into account some // Sometimes it might be necessary to not take into account some
// appointment records in order to display what the providers' // appointment records in order to display what the providers'
// available time periods would be without them. // available time periods would be without them.
foreach ($exclude_appointments as $excluded_id) { foreach ($exclude_appointments as $excluded_id) {
foreach ($reserved_appointments as $index => $reserved) { foreach ($reserved_appointments as $index => $reserved) {
@ -47,12 +46,11 @@ private function get_provider_available_time_periods($provider_id,
} }
} }
// Find the empty spaces on the plan. The first split between // Find the empty spaces on the plan. The first split between the
// the plan is due to a break (if exist). After that every // plan is due to a break (if exist). After that every reserved
// reserved appointment is considered to be a taken space in the // appointment is considered to be a taken space in the plan.
// plan.
$selected_date_working_plan = $selected_date_working_plan =
$working_plan[strtolower(date('l', strtotime($selected_date)))]; $working_plan[strtolower(date('l', strtotime($selected_date)))];
$available_periods_with_breaks = array(); $available_periods_with_breaks = array();
if (isset($selected_date_working_plan['breaks'])) { if (isset($selected_date_working_plan['breaks'])) {
@ -65,8 +63,8 @@ private function get_provider_available_time_periods($provider_id,
$start_hour = $selected_date_working_plan['start']; $start_hour = $selected_date_working_plan['start'];
$end_hour = $break['start']; $end_hour = $break['start'];
} else { } else {
$start_hour = $selected_date_working_plan $start_hour = $selected_date_working_plan['breaks']
['breaks'][$last_break_index]['end']; [$last_break_index]['end'];
$end_hour = $break['start']; $end_hour = $break['start'];
} }
@ -75,118 +73,67 @@ private function get_provider_available_time_periods($provider_id,
'end' => $end_hour 'end' => $end_hour
); );
} }
// Add the period from the last break to the end of the day. // Add the period from the last break to the end of the day.
$available_periods_with_breaks[] = array( $available_periods_with_breaks[] = array(
'start' =>$selected_date_working_plan['breaks'][$index]['end'], 'start'=>$selected_date_working_plan['breaks'][$index]['end'],
'end' => $selected_date_working_plan['end'] 'end'=>$selected_date_working_plan['end']
); );
} }
// Break the empty periods with the reserved appointments. // Break the empty periods with the reserved appointments.
$available_periods_with_appointments = array(); $available_periods_with_appointments =
$available_periods_with_breaks;
if (count($reserved_appointments) > 0) { foreach($reserved_appointments as $appointment) {
foreach($available_periods_with_appointments as $index=>&$period){
foreach($available_periods_with_breaks as $period) { $a_start =
date('H:i',strtotime($appointment['start_datetime']));
foreach($reserved_appointments as $index=>$reserved) { $a_end =
$appointment_start = date('H:i', strtotime($appointment['end_datetime']));
date('H:i', strtotime($reserved['start_datetime'])); $p_start =
$appointment_end = date('H:i', strtotime($period['start']));
date('H:i', strtotime($reserved['end_datetime'])); $p_end =
$period_start = date('H:i', strtotime($period['end']));
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 if ($a_start <= $p_start && $a_end <= $p_end
// empty space. So we need to break the empty && $a_end <= $p_start) {
// space into two other spaces that don't include // The appointment does not belong in this time period, so
// the appointment. // we will not change anything.
$new_period = array( } else if ($a_start <= $p_start && $a_end <= $p_end
'start' => $period_start, && $a_end >= $p_start) {
'end' => $appointment_start // The appointment starts before the period and finishes
); // somewhere inside.We will need to break this period and
// leave the available part.
if (!in_array($new_period, $period['start'] = $a_end;
$available_periods_with_appointments)) { } else if ($a_start >= $p_start && $a_end <= $p_start) {
$available_periods_with_appointments[] = $new_period; // The appointment is inside the time period, so we will
} // split the period into two new others.
unset($available_periods_with_appointments[$index]);
$new_period = array( $available_periods_with_appointments[] = array(
'start' => $appointment_end, 'start' => $p_start,
'end' => $period_end 'end' => $a_start
); );
$available_periods_with_appointments[] = array(
if (!in_array($new_period, 'start' => $a_end,
$available_periods_with_appointments)) { 'end' => $p_end
$available_periods_with_appointments[] = $new_period; );
} } else if ($a_start >= $p_start && $a_end >= $p_start
&& $a_start <= $p_end) {
} else { // The appointment starts in the period and finishes out
// Check if there are any other appointments // of it. We will need to remove the time that is taken
// between this time space. If not, it is going // from the appointment.
// to be added as it is. $period['end'] = $a_start;
$found = FALSE; } else if ($a_start >= $p_start && $a_end >= $p_end
&& $a_start >= $p_end) {
foreach ($reserved_appointments as $tmp_appointment) { // The appointment does not belong in the period so do not
$appt_start = date('H:i', // change anything.
strtotime($tmp_appointment['start_datetime'])); } else if ($a_start <= $p_start && $a_end >= $p_end
$appt_end = date('H:i', && $a_start <= $p_end) {
strtotime($tmp_appointment['end_datetime'])); // The appointment is bigger than the period, so this period
// needs to be removed.
if ($period_start < $appt_start unset($available_periods_with_appointments[$index]);
&& $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 array_values($available_periods_with_appointments);
return $available_periods_with_appointments;
} }

Binary file not shown.

Binary file not shown.

View file

@ -513,92 +513,53 @@ class Appointments extends CI_Controller {
} }
// Break the empty periods with the reserved appointments. // Break the empty periods with the reserved appointments.
$available_periods_with_appointments = array(); $available_periods_with_appointments = $available_periods_with_breaks;
if (count($reserved_appointments) > 0) { foreach($reserved_appointments as $appointment) {
foreach($available_periods_with_appointments as $index => &$period) {
foreach($available_periods_with_breaks as $period) { $a_start = date('H:i', strtotime($appointment['start_datetime']));
$a_end = date('H:i', strtotime($appointment['end_datetime']));
foreach($reserved_appointments as $index=>$reserved) { $p_start = date('H:i', strtotime($period['start']));
$appointment_start = date('H:i', strtotime($reserved['start_datetime'])); $p_end = date('H:i', strtotime($period['end']));
$appointment_end = date('H:i', strtotime($reserved['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 if ($a_start <= $p_start && $a_end <= $p_end && $a_end <= $p_start) {
// we need to break the empty space into two other spaces that // The appointment does not belong in this time period, so we
// don't include the appointment. // will not change anything.
$new_period = array( } else if ($a_start <= $p_start && $a_end <= $p_end && $a_end >= $p_start) {
'start' => $period_start, // The appointment starts before the period and finishes somewhere inside.
'end' => $appointment_start // We will need to break this period and leave the available part.
); $period['start'] = $a_end;
if (!in_array($new_period, $available_periods_with_appointments)) { } else if ($a_start >= $p_start && $a_end <= $p_start) {
$available_periods_with_appointments[] = $new_period; // The appointment is inside the time period, so we will split the period
} // into two new others.
unset($available_periods_with_appointments[$index]);
$new_period = array( $available_periods_with_appointments[] = array(
'start' => $appointment_end, 'start' => $p_start,
'end' => $period_end 'end' => $a_start
); );
$available_periods_with_appointments[] = array(
if (!in_array($new_period, $available_periods_with_appointments)) { 'start' => $a_end,
$available_periods_with_appointments[] = $new_period; 'end' => $p_end
} );
} else { } else if ($a_start >= $p_start && $a_end >= $p_start && $a_start <= $p_end) {
// Check if there are any other appointments between this // The appointment starts in the period and finishes out of it. We will
// time space. If not, it is going to be added as it is. // need to remove the time that is taken from the appointment.
$found = FALSE; $period['end'] = $a_start;
foreach ($reserved_appointments as $tmp_appointment) { } else if ($a_start >= $p_start && $a_end >= $p_end && $a_start >= $p_end) {
$appt_start = date('H:i', strtotime($tmp_appointment['start_datetime'])); // The appointment does not belong in the period so do not change anything.
$appt_end = date('H:i', strtotime($tmp_appointment['end_datetime'])); } else if ($a_start <= $p_start && $a_end >= $p_end && $a_start <= $p_end) {
// The appointment is bigger than the period, so this period needs to be
if ($period_start < $appt_start && $period_end > $appt_end) { // removed.
$found = TRUE; unset($available_periods_with_appointments[$index]);
}
}
// 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; return array_values($available_periods_with_appointments);
} }
/** /**

View file

@ -112,8 +112,7 @@ class Google extends CI_Controller {
$where_clause = array( $where_clause = array(
'start_datetime >=' => date('Y-m-d H:i:s', $start), 'start_datetime >=' => date('Y-m-d H:i:s', $start),
'end_datetime <=' => date('Y-m-d H:i:s', $end), 'end_datetime <=' => date('Y-m-d H:i:s', $end),
'id_users_provider' => $provider['id'], 'id_users_provider' => $provider['id']
'is_unavailable' => FALSE
); );
$appointments = $this->appointments_model->get_batch($where_clause); $appointments = $this->appointments_model->get_batch($where_clause);
@ -127,8 +126,13 @@ class Google extends CI_Controller {
// Sync each appointment with Google Calendar by following the project's sync // Sync each appointment with Google Calendar by following the project's sync
// protocol (see documentation). // protocol (see documentation).
foreach($appointments as $appointment) { foreach($appointments as $appointment) {
$service = $this->services_model->get_row($appointment['id_services']); if ($appointment['is_unavailable'] == FALSE) {
$customer = $this->customers_model->get_row($appointment['id_users_customer']); $service = $this->services_model->get_row($appointment['id_services']);
$customer = $this->customers_model->get_row($appointment['id_users_customer']);
} else {
$service = NULL;
$customer = NULL;
}
// If current appointment not synced yet, add to gcal. // If current appointment not synced yet, add to gcal.
if ($appointment['id_google_calendar'] == NULL) { if ($appointment['id_google_calendar'] == NULL) {
@ -141,6 +145,10 @@ class Google extends CI_Controller {
try { try {
$google_event = $this->google_sync->get_event($appointment['id_google_calendar']); $google_event = $this->google_sync->get_event($appointment['id_google_calendar']);
if ($google_event->status == 'cancelled') {
throw new Exception('Event is cancelled, remove the record from Easy!Appointments.');
}
// If gcal event is different from e!a appointment then update e!a record. // If gcal event is different from e!a appointment then update e!a record.
$is_different = FALSE; $is_different = FALSE;
$appt_start = strtotime($appointment['start_datetime']); $appt_start = strtotime($appointment['start_datetime']);

View file

@ -108,7 +108,7 @@ class Google_Sync {
$this->CI->load->helper('general'); $this->CI->load->helper('general');
$event = new Google_Event(); $event = new Google_Event();
$event->setSummary($service['name']); $event->setSummary(($service != NULL) ? $service['name'] : 'Unavailable');
$event->setLocation($company_settings['company_name']); $event->setLocation($company_settings['company_name']);
$start = new Google_EventDateTime(); $start = new Google_EventDateTime();
@ -119,20 +119,21 @@ class Google_Sync {
$end->setDateTime(date3339(strtotime($appointment['end_datetime']))); $end->setDateTime(date3339(strtotime($appointment['end_datetime'])));
$event->setEnd($end); $event->setEnd($end);
$eventProvider = new Google_EventAttendee(); $event->attendees = array();
$eventProvider->setDisplayName($provider['first_name'] . ' '
$event_provider = new Google_EventAttendee();
$event_provider->setDisplayName($provider['first_name'] . ' '
. $provider['last_name']); . $provider['last_name']);
$eventProvider->setEmail($provider['email']); $event_provider->setEmail($provider['email']);
$event->attendees[] = $event_provider;
$eventCustomer = new Google_EventAttendee(); if ($customer != NULL) {
$eventCustomer->setDisplayName($customer['first_name'] . ' ' $event_customer = new Google_EventAttendee();
. $customer['last_name']); $event_customer->setDisplayName($customer['first_name'] . ' '
$eventCustomer->setEmail($customer['email']); . $customer['last_name']);
$event_customer->setEmail($customer['email']);
$event->attendees = array( $event->attendees[] = $event_customer;
$eventProvider, }
$eventCustomer
);
// Add the new event to the "primary" calendar. // Add the new event to the "primary" calendar.
$created_event = $this->service->events->insert('primary', $event); $created_event = $this->service->events->insert('primary', $event);
@ -171,20 +172,21 @@ class Google_Sync {
$end->setDateTime(date3339(strtotime($appointment['end_datetime']))); $end->setDateTime(date3339(strtotime($appointment['end_datetime'])));
$event->setEnd($end); $event->setEnd($end);
$event->attendees = array();
$event_provider = new Google_EventAttendee(); $event_provider = new Google_EventAttendee();
$event_provider->setDisplayName($provider['first_name'] . ' ' $event_provider->setDisplayName($provider['first_name'] . ' '
. $provider['last_name']); . $provider['last_name']);
$event_provider->setEmail($provider['email']); $event_provider->setEmail($provider['email']);
$event->attendees[] = $event_provider;
$event_customer = new Google_EventAttendee(); if ($customer != NULL) {
$event_customer->setDisplayName($customer['first_name'] . ' ' $event_customer = new Google_EventAttendee();
. $customer['last_name']); $event_customer->setDisplayName($customer['first_name'] . ' '
$event_customer->setEmail($customer['email']); . $customer['last_name']);
$event_customer->setEmail($customer['email']);
$event->attendees = array( $event->attendees[] = $event_customer;
$event_provider, }
$event_customer
);
$updated_event = $this->service->events->update('primary', $event->getId(), $event); $updated_event = $this->service->events->update('primary', $event->getId(), $event);

View file

@ -133,7 +133,7 @@
<form id="cancel-appointment-form" method="post" <form id="cancel-appointment-form" method="post"
action="' . $this->config->item('base_url') action="' . $this->config->item('base_url')
. 'appointments/cancel/' . $appointment_data['hash'] . '"> . 'appointments/cancel/' . $appointment_data['hash'] . '">
<textarea name="cancel_reason" style="display:none;"></textarea> <textarea name="cancel_reason" style="display:none"></textarea>
<button id="cancel-appointment" class="btn btn-inverse"> <button id="cancel-appointment" class="btn btn-inverse">
Cancel</button> Cancel</button>
</form> </form>

View file

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

View file

@ -43,7 +43,7 @@
<div class="btn-group"> <div class="btn-group">
<?php //if ($privileges[PRIV_USERS]['edit'] == TRUE) { ?> <?php //if ($privileges[PRIV_USERS]['edit'] == TRUE) { ?>
<?php if (($role_slug == DB_SLUG_ADMIN || $role_slug == DB_SLUG_PROVIDER) <?php if (($role_slug == DB_SLUG_ADMIN || $role_slug == DB_SLUG_PROVIDER)
&& $this->config->item('ea_google_sync') == TRUE) { ?> && $this->config->item('ea_google_sync_feature') == TRUE) { ?>
<button id="google-sync" class="btn btn-primary" <button id="google-sync" class="btn btn-primary"
title="Trigger the Google Calendar synchronization process."> title="Trigger the Google Calendar synchronization process.">
<i class="icon-refresh icon-white"></i> <i class="icon-refresh icon-white"></i>

View file

@ -65,7 +65,7 @@
<div id="save-cancel-group" class="btn-group" style="display:none;"> <div id="save-cancel-group" class="btn-group" style="display:none;">
<button id="save-customer" class="btn btn-primary"> <button id="save-customer" class="btn btn-primary">
<i class="icon-ok"></i> <i class="icon-ok icon-white"></i>
Save</button> Save</button>
<button id="cancel-customer" class="btn"> <button id="cancel-customer" class="btn">
<i class="icon-ban-circle"></i> <i class="icon-ban-circle"></i>

View file

@ -67,7 +67,7 @@
<div class="save-cancel-group btn-group" style="display:none;"> <div class="save-cancel-group btn-group" style="display:none;">
<button id="save-service" class="btn btn-primary"> <button id="save-service" class="btn btn-primary">
<i class="icon-ok"></i> <i class="icon-ok icon-white"></i>
Save</button> Save</button>
<button id="cancel-service" class="btn"> <button id="cancel-service" class="btn">
<i class="icon-ban-circle"></i> <i class="icon-ban-circle"></i>
@ -144,7 +144,7 @@
<div class="save-cancel-group btn-group" style="display:none;"> <div class="save-cancel-group btn-group" style="display:none;">
<button id="save-category" class="btn btn-primary"> <button id="save-category" class="btn btn-primary">
<i class="icon-ok"></i> <i class="icon-ok icon-white"></i>
Save</button> Save</button>
<button id="cancel-category" class="btn"> <button id="cancel-category" class="btn">
<i class="icon-ban-circle"></i> <i class="icon-ban-circle"></i>

View file

@ -93,7 +93,7 @@
<div class="save-cancel-group btn-group" style="display:none;"> <div class="save-cancel-group btn-group" style="display:none;">
<button id="save-admin" class="btn btn-primary"> <button id="save-admin" class="btn btn-primary">
<i class="icon-ok"></i> <i class="icon-ok icon-white"></i>
Save</button> Save</button>
<button id="cancel-admin" class="btn"> <button id="cancel-admin" class="btn">
<i class="icon-ban-circle"></i> <i class="icon-ban-circle"></i>
@ -201,7 +201,7 @@
<div class="save-cancel-group btn-group" style="display:none;"> <div class="save-cancel-group btn-group" style="display:none;">
<button id="save-provider" class="btn btn-primary"> <button id="save-provider" class="btn btn-primary">
<i class="icon-ok"></i> <i class="icon-ok icon-white"></i>
Save</button> Save</button>
<button id="cancel-provider" class="btn"> <button id="cancel-provider" class="btn">
<i class="icon-ban-circle"></i> <i class="icon-ban-circle"></i>
@ -409,7 +409,7 @@
<div class="save-cancel-group btn-group" style="display:none;"> <div class="save-cancel-group btn-group" style="display:none;">
<button id="save-secretary" class="btn btn-primary"> <button id="save-secretary" class="btn btn-primary">
<i class="icon-ok"></i> <i class="icon-ok icon-white"></i>
Save</button> Save</button>
<button id="cancel-secretary" class="btn"> <button id="cancel-secretary" class="btn">
<i class="icon-ban-circle"></i> <i class="icon-ban-circle"></i>

View file

@ -195,7 +195,7 @@
} }
#loading { #loading {
position: absolute; position: fixed;
top: 0px; top: 0px;
left: 0px; left: 0px;
width: 100%; width: 100%;

View file

@ -11,7 +11,7 @@ root {
#header { #header {
height: 70px; height: 70px;
background-color: #35B66F; background-color: #35B66F;
border-bottom: 6px solid #247A4B; border-bottom: 4px solid #247A4B;
} }
#header #header-logo { #header #header-logo {
@ -96,7 +96,7 @@ root {
} }
#loading { #loading {
position: absolute; position: fixed;
top: 0px; top: 0px;
left: 0px; left: 0px;
width: 100%; width: 100%;
@ -178,14 +178,19 @@ body .jspTrack {
#calendar-page #calendar .fc-unavailable { #calendar-page #calendar .fc-unavailable {
background-image: url('../images/unavailable.jpg'); background-image: url('../images/unavailable.jpg');
font-size: 24px; font-size: 17px;
border-radius: 0; border-radius: 0;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
text-shadow: 0px 1px 0px #FFF; text-shadow: 0px 1px 0px #E6E6E6;
opacity: 0.7; opacity: 0.7;
} }
#calendar-page #calendar .fc-event-title small {
font-weight: normal;
font-size: 12px;
}
#calendar-page #calendar .fc-break { #calendar-page #calendar .fc-break {
background-image: url('../images/break.jpg'); background-image: url('../images/break.jpg');
} }

View file

@ -52,7 +52,10 @@ var BackendCalendar = {
'dayClick': BackendCalendar.calendarDayClick, 'dayClick': BackendCalendar.calendarDayClick,
'eventClick': BackendCalendar.calendarEventClick, 'eventClick': BackendCalendar.calendarEventClick,
'eventResize': BackendCalendar.calendarEventResize, 'eventResize': BackendCalendar.calendarEventResize,
'eventDrop': BackendCalendar.calendarEventDrop 'eventDrop': BackendCalendar.calendarEventDrop,
'eventAfterAllRender': function(view) {
BackendCalendar.convertTitlesToHtml();
}
}); });
// Temporary fix: make the first letter capital in all the lowercase strings // Temporary fix: make the first letter capital in all the lowercase strings
@ -264,7 +267,7 @@ var BackendCalendar = {
if (response.warnings) { if (response.warnings) {
response.warnings = GeneralFunctions.parseExceptions(response.warnings); response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE); GeneralFunctions.displayMessageBox(GeneralFunctions.WARNINGS_TITLE, GeneralFunctions.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
} }
@ -396,7 +399,7 @@ var BackendCalendar = {
if (response.warnings) { if (response.warnings) {
response.warnings = GeneralFunctions.parseExceptions(response.warnings); response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE); GeneralFunctions.displayMessageBox(GeneralFunctions.WARNINGS_TITLE, GeneralFunctions.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
} }
@ -437,7 +440,7 @@ var BackendCalendar = {
if (response.warnings) { if (response.warnings) {
response.warnings = GeneralFunctions.parseExceptions(response.warnings); response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE); GeneralFunctions.displayMessageBox(GeneralFunctions.WARNINGS_TITLE, GeneralFunctions.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
} }
@ -510,7 +513,7 @@ var BackendCalendar = {
} }
// :: DEFINE SUCCESS EVENT CALLBACK // :: DEFINE SUCCESS EVENT CALLBACK
var successCallback = function(response) { var successCallback = function(response) {
if (response.exceptions) { if (response.exceptions) {
response.exceptions = GeneralFunctions.parseExceptions(response.exceptions); response.exceptions = GeneralFunctions.parseExceptions(response.exceptions);
GeneralFunctions.displayMessageBox(GeneralFunctions.EXCEPTIONS_TITLE, GeneralFunctions.EXCEPTIONS_MESSAGE); GeneralFunctions.displayMessageBox(GeneralFunctions.EXCEPTIONS_TITLE, GeneralFunctions.EXCEPTIONS_MESSAGE);
@ -527,6 +530,7 @@ var BackendCalendar = {
$dialog.find('.modal-message').text('Appointment saved successfully!'); $dialog.find('.modal-message').text('Appointment saved successfully!');
$dialog.find('.modal-message').addClass('alert-success').removeClass('alert-error'); $dialog.find('.modal-message').addClass('alert-success').removeClass('alert-error');
$dialog.find('.modal-message').fadeIn(); $dialog.find('.modal-message').fadeIn();
$dialog.find('.modal-body').scrollTop(0);
// Close the modal dialog and refresh the calendar appointments // Close the modal dialog and refresh the calendar appointments
// after one second. // after one second.
@ -542,6 +546,7 @@ var BackendCalendar = {
$dialog.find('.modal-message').text('A server communication error occured, please try again.'); $dialog.find('.modal-message').text('A server communication error occured, please try again.');
$dialog.find('.modal-message').addClass('alert-error'); $dialog.find('.modal-message').addClass('alert-error');
$dialog.find('.modal-message').fadeIn(); $dialog.find('.modal-message').fadeIn();
$dialog.find('.modal-body').scrollTop(0);
}; };
// :: CALL THE UPDATE APPOINTMENT METHOD // :: CALL THE UPDATE APPOINTMENT METHOD
@ -600,7 +605,7 @@ var BackendCalendar = {
if (response.warnings) { if (response.warnings) {
response.warnings = GeneralFunctions.parseExceptions(response.warnings); response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE); GeneralFunctions.displayMessageBox(GeneralFunctions.WARNINGS_TITLE, GeneralFunctions.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
} }
@ -913,7 +918,7 @@ var BackendCalendar = {
if (calendarDateStart < workDateStart) { if (calendarDateStart < workDateStart) {
unavailablePeriod = { unavailablePeriod = {
'title': 'Unavailable', 'title': 'Not Working',
'start': calendarDateStart, 'start': calendarDateStart,
'end': workDateStart, 'end': workDateStart,
'allDay': false, 'allDay': false,
@ -932,7 +937,7 @@ var BackendCalendar = {
'dd/MM/yyyy HH:mm'); // Use calendarDateStart *** 'dd/MM/yyyy HH:mm'); // Use calendarDateStart ***
if (calendarDateEnd > workDateEnd) { if (calendarDateEnd > workDateEnd) {
var unavailablePeriod = { var unavailablePeriod = {
'title': 'Unavailable', 'title': 'Not Working',
'start': workDateEnd, 'start': workDateEnd,
'end': calendarDateEnd, 'end': calendarDateEnd,
'allDay': false, 'allDay': false,
@ -965,7 +970,9 @@ var BackendCalendar = {
// Add custom unavailable periods. // Add custom unavailable periods.
$.each(response.unavailables, function(index, unavailable) { $.each(response.unavailables, function(index, unavailable) {
var unavailablePeriod = { var unavailablePeriod = {
'title': 'Unavailable', 'title': 'Unavailable <br><small>' + ((unavailable.notes.length > 30)
? unavailable.notes.substring(0, 30) + '...'
: unavailable.notes) + '</small>',
'start': Date.parse(unavailable.start_datetime), 'start': Date.parse(unavailable.start_datetime),
'end': Date.parse(unavailable.end_datetime), 'end': Date.parse(unavailable.end_datetime),
'allDay': false, 'allDay': false,
@ -991,7 +998,9 @@ var BackendCalendar = {
if (currDateStart.toString('dd/MM/yyyy') if (currDateStart.toString('dd/MM/yyyy')
=== Date.parse(unavailable.start_datetime).toString('dd/MM/yyyy')) { === Date.parse(unavailable.start_datetime).toString('dd/MM/yyyy')) {
var unavailablePeriod = { var unavailablePeriod = {
'title': 'Unavailable', 'title': 'Unavailable <br><small>' + ((unavailable.notes.length > 30)
? unavailable.notes.substring(0, 30) + '...'
: unavailable.notes) + '</small>',
'start': Date.parse(unavailable.start_datetime), 'start': Date.parse(unavailable.start_datetime),
'end': Date.parse(unavailable.end_datetime), 'end': Date.parse(unavailable.end_datetime),
'allDay': false, 'allDay': false,
@ -1004,7 +1013,6 @@ var BackendCalendar = {
} }
}); });
if (workingDay == null) { if (workingDay == null) {
// Add a full day unavailable event. // Add a full day unavailable event.
unavailablePeriod = { unavailablePeriod = {
@ -1029,7 +1037,7 @@ var BackendCalendar = {
+ ' ' + workingDay.start, 'dd/MM/yyyy HH:mm'); + ' ' + workingDay.start, 'dd/MM/yyyy HH:mm');
if (currDateStart < start) { if (currDateStart < start) {
unavailablePeriod = { unavailablePeriod = {
'title': 'Unavailable', 'title': 'Not Working',
'start': GeneralFunctions.clone(currDateStart), 'start': GeneralFunctions.clone(currDateStart),
'end': GeneralFunctions.clone(start), 'end': GeneralFunctions.clone(start),
'allDay': false, 'allDay': false,
@ -1045,7 +1053,7 @@ var BackendCalendar = {
+ ' ' + workingDay.end, 'dd/MM/yyyy HH:mm'); + ' ' + workingDay.end, 'dd/MM/yyyy HH:mm');
if (currDateEnd > end) { if (currDateEnd > end) {
unavailablePeriod = { unavailablePeriod = {
'title': 'Unavailable', 'title': 'Not Working',
'start': GeneralFunctions.clone(end), 'start': GeneralFunctions.clone(end),
'end': GeneralFunctions.clone(currDateEnd), 'end': GeneralFunctions.clone(currDateEnd),
'allDay': false, 'allDay': false,
@ -1082,6 +1090,8 @@ var BackendCalendar = {
} }
} }
}); });
// Convert the titles to html code.
//BackendCalendar.convertTitlesToHtml();
} }
}, 'json'); }, 'json');
}, },
@ -1209,7 +1219,7 @@ var BackendCalendar = {
if (response.warnings) { if (response.warnings) {
// Display warning information to the user. // Display warning information to the user.
response.warnings = GeneralFunctions.parseExceptions(response.warnings); response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE); GeneralFunctions.displayMessageBox(GeneralFunctions.WARNINGS_TITLE, GeneralFunctions.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
} }
@ -1262,7 +1272,7 @@ var BackendCalendar = {
if (response.warnings) { if (response.warnings) {
// Display warning information to the user. // Display warning information to the user.
response.warnings = GeneralFunctions.parseExceptions(response.warnings); response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE); GeneralFunctions.displayMessageBox(GeneralFunctions.WARNINGS_TITLE, GeneralFunctions.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
} }
@ -1307,6 +1317,7 @@ var BackendCalendar = {
calendarWindowResize: function(view) { calendarWindowResize: function(view) {
$('#calendar').fullCalendar('option', 'height', $('#calendar').fullCalendar('option', 'height',
BackendCalendar.getCalendarHeight()); BackendCalendar.getCalendarHeight());
//BackendCalendar.convertTitlesToHtml();
}, },
/** /**
@ -1476,7 +1487,7 @@ var BackendCalendar = {
if (response.warnings) { if (response.warnings) {
// Display warning information to the user. // Display warning information to the user.
response.warnings = GeneralFunctions.parseExceptions(response.warnings); response.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE); GeneralFunctions.displayMessageBox(GeneralFunctions.WARNINGS_TITLE, GeneralFunctions.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
} }
@ -1539,7 +1550,7 @@ var BackendCalendar = {
if (response.warnings) { if (response.warnings) {
reponse.warnings = GeneralFunctions.parseExceptions(response.warnings); reponse.warnings = GeneralFunctions.parseExceptions(response.warnings);
GeneralFunctions.displayMessageBox(Backend.WARNINGS_TITLE, Backend.WARNINGS_MESSAGE); GeneralFunctions.displayMessageBox(GeneralFunctions.WARNINGS_TITLE, GeneralFunctions.WARNINGS_MESSAGE);
$('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings)); $('#message_box').append(GeneralFunctions.exceptionsToHtml(response.warnings));
} }
@ -1598,8 +1609,17 @@ var BackendCalendar = {
$('#select-filter-item option:selected').attr('type'), $('#select-filter-item option:selected').attr('type'),
$('#calendar').fullCalendar('getView').visStart, $('#calendar').fullCalendar('getView').visStart,
$('#calendar').fullCalendar('getView').visEnd); $('#calendar').fullCalendar('getView').visEnd);
$(window).trigger('resize'); $(window).trigger('resize'); // Places the footer on the bottom.
// Change string from "all-day" to "All Day".
$('#calendar .fc-agenda-allday .fc-agenda-axis').text('All Day');
// Remove all open popovers.
$('.close-popover').each(function() {
$(this).parents().eq(2).remove();
});
// Add new pop overs.
$('.fv-events').each(function(index, eventHandle) { $('.fv-events').each(function(index, eventHandle) {
$(eventHandle).popover(); $(eventHandle).popover();
}); });
@ -1759,5 +1779,20 @@ var BackendCalendar = {
// Clear the unavailable notes field. // Clear the unavailable notes field.
$dialog.find('#unavailable-notes').val(''); $dialog.find('#unavailable-notes').val('');
},
/**
* On some calendar events the titles contain html markup that is not
* displayed properly due to the fullcalendar plugin. This plugin sets
* the .fc-event-title value by using the $.text() method and not the
* $.html() method. So in order for the title to display the html properly
* we convert all the .fc-event-titles where needed into html.
*/
convertTitlesToHtml: function() {
// Convert the titles to html code.
$('.fc-custom').each(function() {
var title = $(this).find('.fc-event-title').text();
$(this).find('.fc-event-title').html(title);
});
} }
}; };

View file

@ -397,13 +397,17 @@ CustomersHelper.prototype.filter = function(key, selectId, display) {
* @return {string} Returns the record html code. * @return {string} Returns the record html code.
*/ */
CustomersHelper.prototype.getFilterHtml = function(customer) { CustomersHelper.prototype.getFilterHtml = function(customer) {
var name = customer.first_name + ' ' + customer.last_name;
var info = customer.email;
info = (customer.phone_number != '' && customer.phone_number != null)
? info + ', ' + customer.phone_number : info;
var html = var html =
'<div class="customer-row" data-id="' + customer.id + '">' + '<div class="customer-row" data-id="' + customer.id + '">' +
'<strong>' + '<strong>' +
customer.first_name + ' ' + customer.last_name + name +
'</strong><br>' + '</strong><br>' +
'<span>' + customer.email + '</span> | ' + info +
'<span>' + customer.phone_number + '</span>' +
'</div><hr>'; '</div><hr>';
return html; return html;

View file

@ -214,8 +214,8 @@ var FrontendBook = {
+ 'minute to write the reason you are cancelling the appointment:', + 'minute to write the reason you are cancelling the appointment:',
dialogButtons); dialogButtons);
$('#message_box').append('<textarea id="cancel-reason"></textarea>'); $('#message_box').append('<textarea id="cancel-reason" rows="3"></textarea>');
$('#cancel-reason').css('width', '300px'); $('#cancel-reason').css('width', '353px');
}); });
} }
@ -553,11 +553,15 @@ var FrontendBook = {
html = '<strong>' + service.name + '</strong>'; html = '<strong>' + service.name + '</strong>';
if (service.description != '' && service.description != null) { if (service.description != '' && service.description != null) {
html += '<br>' + service.description; html += '<br>' + service.description + '<br>';
}
if (service.duration != '' && service.duration != null) {
html += '[Duration ' + service.duration + ' Minutes] ';
} }
if (service.price != '' && service.price != null) { if (service.price != '' && service.price != null) {
html += '<br> [Price ' + service.price + ' ' + service.currency + ']'; html += '[Price ' + service.price + ' ' + service.currency + ']';
} }
html += '<br>'; html += '<br>';

View file

@ -10,7 +10,7 @@ class SystemConfiguration {
public static $db_password = ''; public static $db_password = '';
// Google Calendar API Settings // Google Calendar API Settings
public static $google_sync_feature = FALSE; // Enter TRUE or FALSE; public static $google_sync_feature = TRUE; // Enter TRUE or FALSE;
public static $google_product_name = 'Easy!Appointments'; public static $google_product_name = 'Easy!Appointments';
public static $google_client_id = '396094740598-l9ohhdgs0hr6qi89628p3chf9lm59mkc.apps.googleusercontent.com'; public static $google_client_id = '396094740598-l9ohhdgs0hr6qi89628p3chf9lm59mkc.apps.googleusercontent.com';
public static $google_client_secret = '3kKEgx3mgxfFInrWf3jTUn4D'; public static $google_client_secret = '3kKEgx3mgxfFInrWf3jTUn4D';