/home/edulekha/crm.edulekha.com/modules/appointly/assets/js/pages/update_js.php
<script>
$(function() {
// Check for success parameter and show alert
var urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('updated') === '1') {
alert_float('success', "<?= _l('appointment_updated'); ?>");
// Clean up URL by removing the parameter
var cleanUrl = window.location.href.split('?')[0];
window.history.replaceState({}, document.title, cleanUrl);
}
// Global variables to track state
var isSubmitting = false;
var timeSlotRequestInProgress = false;
// make sidebar active
$('li.menu-item-appointly').addClass('active');
$('li.menu-item-appointly > ul').addClass('in');
$('li.sub-menu-item-appointly-user-dashboard').addClass('active');
// Load company schedule for date picker validation
window.appointlyCompanySchedule = <?php
$CI = &get_instance();
$CI->db->select('weekday, is_enabled');
$CI->db->from(db_prefix() . 'appointly_company_schedule');
$company_schedule = $CI->db->get()->result_array();
$schedule_by_day = [];
foreach ($company_schedule as $day) {
$schedule_by_day[$day['weekday']] = [
'is_enabled' => (bool)$day['is_enabled']
];
}
echo json_encode($schedule_by_day);
?>;
// Initialize UI components
if (typeof init_selectpicker === 'function') {
init_selectpicker();
}
// Initialize editors
if (typeof tinyMCE !== 'undefined') {
tinyMCE.remove("#description");
tinyMCE.remove("#notes");
}
if (typeof init_editor === 'function') {
init_editor('textarea[name="description"]', {
menubar: false,
height: 150
});
init_editor('textarea[name="notes"]', {
menubar: false,
height: 100
});
}
// Cache DOM elements for better performance
var $relType = $('#rel_type');
var $serviceId = $('#service_id');
var $providerId = $('#provider_id');
var $appointmentDate = $('#appointment_date');
var $availableTimes = $('#available_times');
var $appointmentDuration = $('#appointment_duration');
// Initialize appointment data based on current inputs
initializeAppointmentData();
// Initialize with proper options
// Get blocked days from PHP
var blockedDays = <?php
$blocked_days = get_appointly_blocked_days();
echo json_encode($blocked_days);
?>;
// Get the setting for showing past dates
var showPastDates = <?php echo get_option('appointments_show_past_dates') == '1' ? 'true' : 'false'; ?>;
// Get appointment type
var relType = $('#rel_type').val();
// Configure datepicker with helper function
var appointmentDatePickerOptions = appointlyConfigureDatePicker(
blockedDays,
showPastDates,
relType
);
// Apply datepicker to the appointment date field
$('#appointment_date').datetimepicker(appointmentDatePickerOptions);
// Load initial provider schedule if provider is already selected
var initialProviderId = $('#provider_id').val();
if (initialProviderId && relType !== 'internal_staff') {
loadProviderScheduleAndUpdateCalendar(initialProviderId);
}
// Helper function to pad numbers with leading zeros
function padZero(num) {
return (num < 10 ? '0' : '') + num;
}
/**
* Load provider schedule and update calendar
*/
function loadProviderScheduleAndUpdateCalendar(providerId) {
// Get current blocked days and settings
var blockedDays = <?php
$blocked_days = get_appointly_blocked_days();
echo json_encode($blocked_days);
?>;
var showPastDates = <?php echo get_option('appointments_show_past_dates') == '1' ? 'true' : 'false'; ?>;
var relType = $('#rel_type').val();
// Load provider schedule
$.ajax({
url: admin_url + 'appointly/services/get_staff_schedule',
type: 'POST',
data: {
staff_id: providerId
},
dataType: 'json',
success: function(response) {
if (response && response.success && response.data && response.data.schedule) {
// Store provider schedule globally
window.providerSchedule = response.data.schedule;
// Reinitialize the date picker with provider schedule
updateDatePickerWithProviderSchedule(blockedDays, showPastDates, relType);
} else {
// Initialize normal calendar
initializeDateTimePickers();
}
},
error: function(xhr, status, error) {
// Initialize normal calendar as fallback
initializeDateTimePickers();
}
});
}
/**
* Update date picker with provider schedule
*/
function updateDatePickerWithProviderSchedule(blockedDays, showPastDates, relType) {
// Destroy existing datepicker
if ($('#appointment_date').data('xdsoft_datetimepicker')) {
$('#appointment_date').datetimepicker('destroy');
}
// Configure datepicker with provider schedule
var appointmentDatePickerOptions = appointlyConfigureDatePicker(
blockedDays,
showPastDates,
relType,
window.providerSchedule // Pass provider schedule
);
// Apply datepicker to the appointment date field
$('#appointment_date').datetimepicker(appointmentDatePickerOptions);
}
/**
* Initialize date picker normally
*/
function initializeDateTimePickers() {
var blockedDays = <?php
$blocked_days = get_appointly_blocked_days();
echo json_encode($blocked_days);
?>;
var showPastDates = <?php echo get_option('appointments_show_past_dates') == '1' ? 'true' : 'false'; ?>;
var relType = $('#rel_type').val();
// Destroy existing datepicker
if ($('#appointment_date').data('xdsoft_datetimepicker')) {
$('#appointment_date').datetimepicker('destroy');
}
// Configure datepicker without provider schedule
var appointmentDatePickerOptions = appointlyConfigureDatePicker(
blockedDays,
showPastDates,
relType
);
// Apply datepicker to the appointment date field
$('#appointment_date').datetimepicker(appointmentDatePickerOptions);
}
// Date picker icon click handler
$('.calendar-icon, #appointment_date').off('click').on('click', function(e) {
e.stopPropagation(); // Prevent event bubbling
// If already using datetimepicker, just focus
if ($('#appointment_date').data('xdsoft_datetimepicker')) {
$('#appointment_date').datetimepicker('show');
return;
}
// If using Perfex's standard datepicker, reconfigure with our options
var showPastDates = <?php echo get_option('appointments_show_past_dates') == '1' ? 'true' : 'false'; ?>;
var relType = $('#rel_type').val();
// Initialize with Perfex's function if available
if (typeof appDatepicker === 'function') {
var dateOptions = {
element_date: $appointmentDate,
open_immediately: true,
disabledDates: blockedDays
};
// Add minDate if we're not showing past dates
if (!showPastDates) {
dateOptions.minDate = new Date();
}
appDatepicker(dateOptions);
} else if (typeof init_datepicker === 'function') {
init_datepicker();
$appointmentDate.datepicker('show');
}
});
// Date change handler - loads available time slots
$('#appointment_date').off('change').on('change', function() {
var date = $(this).val();
var relType = $('#rel_type').val();
var isStaffOnly = (relType === 'internal_staff');
var formattedDate = appointlyFormatDate(date);
if (!date) {
$('#slot_loading').addClass('hide');
return;
}
if (isStaffOnly) {
// For staff-only meetings, we don't need service/provider
$('#slot_loading').removeClass('hide');
var providerId = $('input[name="created_by"]').val() || <?= get_staff_user_id(); ?>;
appointlyGetTimeSlots(0, providerId, formattedDate, $('input[name="start_hour"]').val(), $('#timezone').val(), $('input[name="appointment_id"]').val());
} else {
// For regular appointments, we need provider and service
var providerId = $('#provider_id').val();
var serviceId = $('#service_id').val();
if (providerId && serviceId) {
$('#slot_loading').removeClass('hide');
appointlyGetTimeSlots(serviceId, providerId, formattedDate, $('input[name="start_hour"]').val(), $('#timezone').val(), $('input[name="appointment_id"]').val());
} else {
resetTimeSelection();
}
}
});
// Provider change event
$('#provider_id').on('change', function() {
var providerId = $(this).val();
if (providerId) {
// Clear existing time slots
resetTimeSelection();
// Load provider schedule and update calendar
loadProviderScheduleAndUpdateCalendar(providerId);
// Load time slots if date and service are selected
var currentDate = $('#appointment_date').val();
var serviceId = $('#service_id').val();
if (currentDate && serviceId) {
$('#slot_loading').removeClass('hide');
appointlyGetTimeSlots(serviceId, providerId, appointlyFormatDate(currentDate), $('input[name="start_hour"]').val(), $('#timezone').val(), $('input[name="appointment_id"]').val());
}
} else {
resetDateAndTimeSelections();
}
});
// Service change event
$('#service_id').on('change', function() {
var serviceId = $(this).val();
if (!serviceId) {
resetProviderSelection();
return;
}
// Get duration from selected service
var duration = $(this).find('option:selected').data('duration');
if (duration) {
$('#appointment_duration').val(duration);
}
// Load available providers for this service
appointlyLoadProviders(serviceId);
});
// Notification checkboxes handling
function toggleReminderFields() {
var anyChecked = $('#by_sms').prop('checked') || $('#by_email').prop('checked');
$('.appointment-reminder').toggleClass('hide', !anyChecked);
}
$('#by_sms, #by_email').on('change', toggleReminderFields);
// Store previous values for different types to restore when switching back
var storedValues = {
internal: {
contact_id: null
},
lead_related: {
rel_id: null
},
external: {
name: '',
email: '',
phone: ''
},
service_id: null,
provider_id: null
};
// Appointment type (rel_type) handling
$relType.off('change').on('change', function() {
var oldType = window.currentAppointmentType;
var newType = $(this).val();
// Store current values before switching
if (oldType === 'internal') {
storedValues.internal.contact_id = $('#contact_id').val();
} else if (oldType === 'lead_related') {
storedValues.lead_related.rel_id = $('#rel_id').val();
} else if (oldType === 'external') {
storedValues.external.name = $('#name').val();
storedValues.external.email = $('#email').val();
storedValues.external.phone = $('#phone').val();
}
// Always store service and provider values
if (oldType !== 'internal_staff') {
storedValues.service_id = $('#service_id').val();
storedValues.provider_id = $('#provider_id').val();
}
// Clear any previous errors or warnings
$('.time-slot-unavailable').addClass('hide');
$('.time-slot-unavailable-info').addClass('hide');
// Reset time slots when changing types
resetTimeSelection();
// Use centralized function for all dynamic field logic
handleAppointmentTypeChange(newType, false);
// Restore previously stored values if switching back to a type
if (newType !== 'internal_staff') {
// Restore service and provider
if (storedValues.service_id) {
$('#service_id').val(storedValues.service_id).prop('disabled', false).selectpicker('refresh');
}
if (storedValues.provider_id) {
$('#provider_id').val(storedValues.provider_id).prop('disabled', false).selectpicker('refresh');
}
// Restore type-specific values
if (newType === 'internal' && storedValues.internal.contact_id) {
$('#contact_id').val(storedValues.internal.contact_id).selectpicker('refresh');
appointlyFetchContactData(storedValues.internal.contact_id, false);
} else if (newType === 'lead_related' && storedValues.lead_related.rel_id) {
setTimeout(function() {
$('#rel_id').val(storedValues.lead_related.rel_id).selectpicker('refresh');
appointlyFetchContactData(storedValues.lead_related.rel_id, true);
}, 300);
} else if (newType === 'external') {
$('#name').val(storedValues.external.name);
$('#email').val(storedValues.external.email);
$('#phone').val(storedValues.external.phone);
$('#div_name input, #div_email input, #div_phone input').prop('disabled', false);
}
}
// Update the stored type
window.currentAppointmentType = newType;
});
// Centralized function for all dynamic field logic
function handleAppointmentTypeChange(relType, isInit) {
appointlyHandleTypeChange(relType, isInit);
}
// On page load, set up initial state using the centralized function
var initialType = $relType.val();
window.currentAppointmentType = initialType;
// Handle time slot selection change
$('#available_times').on('change', function() {
handleTimeSlotChange($(this));
});
// Contact and lead selection handlers
$('#contact_id').on('change', function() {
var contactId = $(this).val();
if (!contactId) {
appointlyResetContactFields();
return;
}
appointlyFetchContactData(contactId, false);
});
// Lead selection handler
$('#rel_id').on('change', function() {
var leadId = $(this).val();
if (!leadId) {
appointlyResetContactFields();
return;
}
appointlyFetchContactData(leadId, true);
});
// Initialize form validation
$('#appointment-update-form').appFormValidator({
rules: {
subject: 'required',
service_id: {
required: function() {
return $('#rel_type').val() !== 'internal_staff';
}
},
provider_id: {
required: function() {
return $('#rel_type').val() !== 'internal_staff';
}
},
date: 'required',
name: {
required: function() {
return $('#rel_type').val() === 'external';
}
},
email: {
required: function() {
return $('#rel_type').val() === 'external';
},
email: true
},
rel_id: {
required: function() {
return $('#rel_type').val() === 'lead_related';
}
},
contact_id: {
required: function() {
return $('#rel_type').val() === 'internal';
}
}
},
submitHandler: function(form) {
var $form = $(form);
var $submitBtn = $form.find('button[type="submit"]');
// Prevent double submission
if ($submitBtn.prop('disabled')) {
return false;
}
// Disable ALL buttons in the form and panel footer
var $panel = $form.closest('.panel-body');
$form.find('button').prop('disabled', true);
$panel.find('.btn').prop('disabled', true);
// Show loading overlay
showAppointmentLoadingOverlay();
// Check attendees for internal_staff appointments
var relType = $('#rel_type').val();
if (relType === 'internal_staff') {
var selectedAttendees = $('select[name="attendees[]"]').val();
if (!selectedAttendees || selectedAttendees.length === 0) {
alert_float('danger', "<?= _l('appointment_attendees_required'); ?>");
// Hide loading overlay
hideAppointmentLoadingOverlay();
// Expand attendees section
var $attendeesSection = $('.appointment-attendees');
var $attendeesHeader = $attendeesSection.prev('.appointment-collapsible-header');
$attendeesSection.show();
$attendeesHeader.find('.appointment-toggle-plus').text('-');
$('select[name="attendees[]"]').focus();
// Re-enable all buttons
$form.find('button').prop('disabled', false);
$panel.find('.btn').prop('disabled', false);
return false;
}
}
// Set submitting flag
window.isSubmitting = true;
// Add loading spinner to submit button
$submitBtn.html('<i class="fa fa-spinner fa-spin"></i>');
// For internal_staff meetings, ensure there's a duration and transfer value from display field
var relType = $('#rel_type').val();
if (relType === 'internal_staff') {
// Remove irrelevant fields before submit
$('#contact_id').val('');
$('#rel_id').val('');
$('#rel_lead_type').val('');
// Set duration if not already set
if (!$('#appointment_duration').val()) {
$('#appointment_duration').val(60);
}
} else if (relType === 'internal') {
$('#rel_id').val('');
// Keep rel_lead_type for debugging
$('#name, #email, #phone').val('');
} else if (relType === 'lead_related') {
$('#contact_id').val('');
// Make sure rel_lead_type is set to 'leads'
$('#rel_lead_type').val('leads');
// Validate that rel_id is set
if (!$('#rel_id').val()) {
alert_float('danger', "<?= _l('appointment_lead_required'); ?>");
$submitBtn.prop('disabled', false).html("<?= _l('appointment_save_changes_btn_label'); ?>");
$panel.find('.btn').prop('disabled', false);
return false;
}
$('#name, #email, #phone').val('');
} else if (relType === 'external') {
$('#contact_id').val('');
$('#rel_id').val('');
$('#rel_lead_type').val('');
}
// Sync TinyMCE content back to textareas before submission
if (typeof tinyMCE !== 'undefined') {
tinyMCE.triggerSave();
// Explicitly sync each editor
var descEditor = tinyMCE.get('description');
if (descEditor) {
descEditor.save();
}
var notesEditor = tinyMCE.get('notes');
if (notesEditor) {
notesEditor.save();
}
}
// Ensure all form fields are enabled for submission (including hidden sections)
$form.find('input, select, textarea').each(function() {
// Temporarily enable all fields for serialization
if ($(this).prop('disabled')) {
$(this).attr('data-was-disabled', 'true').prop('disabled', false);
}
});
var formData = $form.serialize();
// Submit the form
$.ajax({
url: $form.attr('action'),
type: 'POST',
data: formData,
dataType: 'json',
beforeSend: function() {
// Disable all buttons to prevent multiple submissions
$form.find('button').prop('disabled', true);
$panel.find('.btn').prop('disabled', true);
$submitBtn.html('<i class="fa fa-spinner fa-spin"></i> <?= _l("appointment_updating"); ?>');
},
success: function(response) {
if (response.success) {
// Reload with success parameter
var currentUrl = window.location.href.split('?')[0];
window.location.href = currentUrl + '?updated=1';
} else {
hideAppointmentLoadingOverlay();
alert_float('danger', response.message);
// Re-enable all buttons
$form.find('button').prop('disabled', false);
$panel.find('.btn').prop('disabled', false);
$submitBtn.html("<?= _l('appointment_save_changes_btn_label'); ?>");
}
},
error: function() {
hideAppointmentLoadingOverlay();
alert_float('danger', "<?= _l('appointment_update_failed'); ?>");
// Re-enable all buttons
$form.find('button').prop('disabled', false);
$panel.find('.btn').prop('disabled', false);
$submitBtn.html("<?= _l('appointment_save_changes_btn_label'); ?>");
}
});
}
});
/**
* HELPER FUNCTIONS
*/
// Function to initialize appointment data based on current values
function initializeAppointmentData() {
// Set current values
var appointmentType = $relType.val();
// Handle appointment type display
handleAppointmentTypeChange(appointmentType, true);
// Initialize toggles based on current state
toggleReminderFields();
// For internal_staff appointments, show duration field
if (appointmentType === 'internal_staff') {
$('#duration_wrapper').removeClass('hidden');
} else {
// Hide duration for all other appointment types
$('#duration_wrapper').addClass('hidden');
}
// Initialize AJAX search for relationships
init_ajax_search('contact', '#contact_id.ajax-search');
init_ajax_search('lead', '#rel_id.ajax-search');
// Check if we have existing contact/lead data to fetch
if (appointmentType === 'internal') {
var contactId = $('#contact_id').val();
if (contactId) {
setTimeout(function() {
appointlyFetchContactData(contactId, false);
}, 500);
}
} else if (appointmentType === 'lead_related') {
var leadId = $('#rel_id').val();
if (leadId) {
setTimeout(function() {
appointlyFetchContactData(leadId, true);
}, 500);
}
} else if (appointmentType === 'external') {
// For external type, just make sure the fields are visible and editable
if ($('#name').val() || $('#email').val() || $('#phone').val()) {
$('#div_name, #div_email, #div_phone').removeClass('hidden');
$('#div_name input, #div_email input, #div_phone input').prop('disabled', false);
}
}
// Load time slots if we have all necessary data
var currentDate = $('#appointment_date').val();
if (currentDate) {
// For service-based appointments, we need provider and service
var providerId = $('#provider_id').val();
var serviceId = $('#service_id').val();
if (providerId && serviceId) {
$('#slot_loading').removeClass('hide');
setTimeout(function() {
loadAvailableTimeSlots(currentDate, providerId, serviceId);
}, 300);
}
}
}
// Reset time selection function
function resetTimeSelection() {
// Use the shared function from helpers.js
window.resetTimeSelection();
}
// Reset date and time selections
function resetDateAndTimeSelections() {
$('#appointment_date').val('');
resetTimeSelection();
}
// Reset provider selection
function resetProviderSelection() {
var providersSelect = $('#provider_id');
providersSelect.html('<option value=""><?= _l("appointment_select_provider"); ?></option>');
providersSelect.prop('disabled', true);
providersSelect.selectpicker('refresh');
// Only reset time slots, preserve the selected date
resetTimeSelection();
}
// Function to load available time slots
function loadAvailableTimeSlots(date, providerId, serviceId) {
// Check if this is an internal_staff appointment
var isStaffOnly = $('#rel_type').val() === 'internal_staff';
if (!date) {
$('#slot_loading').addClass('hide');
return;
}
// For staff-only appointments, provider and service are optional
if (!isStaffOnly && (!providerId || !serviceId)) {
$('#slot_loading').addClass('hide');
return;
}
// Show loading indicator
$('#slot_loading').removeClass('hide');
// Prevent multiple concurrent requests
if (window.timeSlotRequestInProgress) {
return;
}
window.timeSlotRequestInProgress = true;
// Clear any existing time slot error messages
$('.time-slot-unavailable').addClass('hide');
$('.time-slot-unavailable-info').addClass('hide');
var appointmentId = $('input[name="appointment_id"]').val() || '0';
var appointmentDate = $('#appointment_date').val();
// Get the current appointment time from hidden field - important for preserving selection
var currentStartHour = $('input[name="start_hour"]').val();
var currentEndHour = $('input[name="end_hour"]').val();
var currentDate = $('#appointment_date').data('current-date');
// Flag to track if we're viewing the original appointment date
var isViewingOriginalDate = (appointmentDate === currentDate);
// Prepare data for AJAX request
var requestData = {
date: date,
appointment_id: appointmentId,
<?= $this->security->get_csrf_token_name(); ?>: '<?= $this->security->get_csrf_hash(); ?>'
};
// For staff meetings, we need special handling
if (isStaffOnly) {
// Use current user as provider if not specified
requestData.provider_id = providerId || $('input[name="created_by"]').val() || <?= get_staff_user_id(); ?>;
// Don't send service_id for staff-only meetings
// Leave it undefined so the server will handle it
} else {
// For regular appointments, use the selected provider and service
// Ensure we have a valid service ID (not 0 or empty)
requestData.provider_id = providerId;
// Make sure we have a valid service_id
if (serviceId && serviceId !== '0') {
requestData.service_id = serviceId;
} else {
// Try to get from select field
var selectedServiceId = $('#service_id').val();
if (selectedServiceId && selectedServiceId !== '0') {
requestData.service_id = selectedServiceId;
} else {
// If no valid service ID is available, show a warning and stop
$('#slot_loading').addClass('hide');
window.timeSlotRequestInProgress = false;
alert_float('warning', "<?= _l('appointment_service_required'); ?>");
return;
}
}
}
$.ajax({
url: admin_url + 'appointly/appointments/get_available_time_slots',
type: 'POST',
data: requestData,
dataType: 'json',
success: function(response) {
window.timeSlotRequestInProgress = false;
// Hide loading indicator
$('#slot_loading').addClass('hide');
var timesSelect = $('#available_times');
// Get the current selected time from the dropdown or the hidden field
var currentSelectedTime = timesSelect.val() || currentStartHour;
// Clear options
timesSelect.empty();
timesSelect.append('<option value=""><?= _l("dropdown_non_selected_tex"); ?></option>');
if (response.success && response.time_slots && response.time_slots.length > 0) {
// If we're editing an existing appointment on the original date, always add current time first
if (currentStartHour && isViewingOriginalDate) {
var formattedTime = appointlyFormatTimeDisplay(currentStartHour, currentEndHour);
var currentOption = new Option(formattedTime + ' (current)', currentStartHour, true, true);
$(currentOption).addClass('current-slot');
timesSelect.append(currentOption);
}
// Add all time slots (available and unavailable)
$.each(response.time_slots, function(index, slot) {
// Skip if this is the current appointment time (already added above)
if (currentStartHour && slot.value === currentStartHour && isViewingOriginalDate) {
return;
}
if (slot.available !== false) {
// Available slot
var option = new Option(slot.text, slot.value);
if (slot.end_time) {
$(option).attr('data-end-time', slot.end_time);
}
timesSelect.append(option);
} else {
// Unavailable slot - show in red with strikethrough
var reason = slot.unavailable_reason ? ' - ' + slot.unavailable_reason : '';
var $option = $('<option>', {
value: '', // Empty value so it can't be selected
disabled: true,
class: 'unavailable-time-slot',
text: slot.text + reason,
css: {
'color': '#ff6666',
'background-color': '#ffeeee',
'text-decoration': 'line-through',
'font-style': 'italic'
}
});
timesSelect.append($option);
}
});
timesSelect.prop('disabled', false);
// Initialize selectpicker with proper styling
if (typeof $.fn.selectpicker === 'function') {
try {
timesSelect.selectpicker('destroy');
timesSelect.selectpicker({
liveSearch: isStaffOnly, // Enable search for staff meetings (many options)
showSubtext: false,
hideDisabled: false // Important - show disabled options so users can see unavailable slots
});
} catch (e) {
// Silently ignore error
}
}
} else {
// No time slots available - show current time if editing existing appointment
if (currentStartHour && isViewingOriginalDate) {
var formattedTime = appointlyFormatTimeDisplay(currentStartHour, currentEndHour);
var option = new Option(formattedTime + ' (current)', currentStartHour, true, true);
$(option).addClass('current-slot');
timesSelect.append(option);
timesSelect.prop('disabled', false);
} else {
timesSelect.prop('disabled', true);
alert_float('warning', "<?= _l('appointment_no_slots_available'); ?>");
}
timesSelect.selectpicker('refresh');
}
},
error: function(xhr, status, error) {
window.timeSlotRequestInProgress = false;
$('#slot_loading').addClass('hide');
var timesSelect = $('#available_times');
timesSelect.empty();
timesSelect.append('<option value=""><?= _l("dropdown_non_selected_tex"); ?></option>');
// Show current time if editing existing appointment, otherwise disable
if (currentStartHour && isViewingOriginalDate) {
var formattedTime = appointlyFormatTimeDisplay(currentStartHour, currentEndHour);
var option = new Option(formattedTime + ' (current)', currentStartHour, true, true);
$(option).addClass('current-slot');
timesSelect.append(option);
timesSelect.prop('disabled', false);
} else {
timesSelect.prop('disabled', true);
alert_float('danger', "<?= _l('appointment_error_loading_slots'); ?>");
}
timesSelect.selectpicker('refresh');
}
});
}
});
// Google and Outlook calendar integration functions
function addEventToGoogleCalendar(button) {
// First, validate the form for internal_staff appointments
var relType = $('#rel_type').val();
if (relType === 'internal_staff') {
var attendeesSelect = $('select[name="attendees[]"]');
var selectedAttendees = attendeesSelect.val();
if (!selectedAttendees || selectedAttendees.length === 0) {
// Show validation error and expand attendees section
alert_float('danger', "<?= _l('appointment_attendees_required'); ?>");
// Automatically expand attendees section
var $attendeesSection = $('.appointment-attendees');
var $attendeesHeader = $attendeesSection.prev('.appointment-collapsible-header');
$attendeesSection.show();
$attendeesHeader.find('.appointment-toggle-plus').text('-');
// Focus on attendees select
attendeesSelect.focus();
return;
}
}
var form = $('#appointment-update-form').serialize();
var url = "<?= admin_url('appointly/appointments/addEventToGoogleCalendar'); ?>";
var modalBody = $('#appointment-update-form .modal-body');
$.ajax({
url: url,
type: "POST",
data: form,
beforeSend: function() {
$(button).attr('disabled', true);
$('.modal .btn').attr('disabled', true);
modalBody.addClass('filterBlur');
$(button).html('<i class="fa fa-refresh fa-spin fa-fw"></i> <?= _l("appointment_calendar_adding_to_google") ?>');
},
success: function(r) {
alert_float('success', r.message);
window.location.reload();
$(button).remove();
}
});
}
function addEventToOutlookCalendar(button, appointmentId) {
// First, validate the form for internal_staff appointments
var relType = $('#rel_type').val();
if (relType === 'internal_staff') {
var attendeesSelect = $('select[name="attendees[]"]');
var selectedAttendees = attendeesSelect.val();
if (!selectedAttendees || selectedAttendees.length === 0) {
// Show validation error and expand attendees section
alert_float('danger', "<?= _l('appointment_attendees_required'); ?>");
// Automatically expand attendees section
var $attendeesSection = $('.appointment-attendees');
var $attendeesHeader = $attendeesSection.prev('.appointment-collapsible-header');
$attendeesSection.show();
$attendeesHeader.find('.appointment-toggle-plus').text('-');
// Focus on attendees select
attendeesSelect.focus();
return;
}
}
if (typeof isOutlookLoggedIn !== 'function' || !isOutlookLoggedIn()) {
if (typeof signInToOutlook === 'function') {
signInToOutlook();
} else {
alert_float('danger', "<?= _l('appointment_outlook_auth_error'); ?>");
}
return;
}
var $btn = $(button);
$btn.prop('disabled', true)
.html('<i class="fa fa-refresh fa-spin fa-fw"></i> <?= _l("appointment_calendar_adding_to_outlook") ?>');
addToOutlookNewEventFromUpdate(appointmentId);
}
// Delete Google Calendar integration from appointment
function deleteGoogleIntegration(appointmentId, googleEventId) {
if (!appointmentId || !googleEventId) {
alert_float('danger', "<?= addslashes(_l('appointment_missing_required_fields')); ?>");
return;
}
if (confirm("<?= addslashes(_l('appointment_confirm_remove_google_integration')); ?>")) {
$.ajax({
url: admin_url + 'appointly/appointments/deleteGoogleEvent',
type: 'POST',
data: {
appointment_id: appointmentId,
google_event_id: googleEventId,
<?= $this->security->get_csrf_token_name(); ?>: '<?= $this->security->get_csrf_hash(); ?>'
},
beforeSend: function() {
// Disable the button to prevent multiple clicks
$('button[onclick*="deleteGoogleIntegration(' + appointmentId + ')"]').prop('disabled', true);
},
success: function(response) {
try {
if (typeof response === 'string') {
response = JSON.parse(response);
}
if (response.success) {
alert_float('success', response.message);
// Reload the page to reflect changes
setTimeout(function() {
window.location.reload();
}, 1000);
} else {
alert_float('danger', response.message || "<?= addslashes(_l('appointment_google_removal_failed')); ?>");
}
} catch (e) {
console.error('Error parsing response:', e);
alert_float('danger', "<?= addslashes(_l('something_went_wrong')); ?>");
}
},
error: function(xhr, status, error) {
alert_float('danger', "<?= addslashes(_l('request_failed')); ?>: " + error);
},
complete: function() {
// Re-enable the button
$('button[onclick*="deleteGoogleIntegration(' + appointmentId + ')"]').prop('disabled', false);
}
});
}
}
// Delete Outlook Calendar integration from appointment
function deleteOutlookIntegration(appointmentId, outlookEventId) {
if (!appointmentId || !outlookEventId) {
alert_float('danger', "<?= addslashes(_l('appointment_missing_required_fields')); ?>");
return;
}
// Check if user is authenticated with Outlook
var isAuthenticated = false;
var authMessage = "";
if (typeof isOutlookLoggedIn === 'function') {
isAuthenticated = isOutlookLoggedIn();
if (!isAuthenticated) {
authMessage = "<?= addslashes(_l('appointment_outlook_not_authenticated_warning')); ?>";
}
} else {
authMessage = "<?= addslashes(_l('appointment_outlook_not_available_warning')); ?>";
}
// Show confirmation with authentication warning if needed
var confirmMessage = "<?= addslashes(_l('appointment_confirm_remove_outlook_integration')); ?>";
if (authMessage) {
confirmMessage = authMessage + "\n\n" + confirmMessage;
}
if (confirm(confirmMessage)) {
// First try to delete from Outlook Calendar via API if authenticated
if (isAuthenticated && typeof deleteOutlookEvent === 'function') {
try {
// Call the function to delete from Outlook Calendar (without showing alert)
deleteOutlookEvent(outlookEventId, false);
} catch (e) {
console.warn('Failed to delete from Outlook Calendar:', e);
}
}
// Always remove from local appointment
$.ajax({
url: admin_url + 'appointly/appointments/deleteOutlookEvent',
type: 'POST',
data: {
appointment_id: appointmentId,
outlook_event_id: outlookEventId,
<?= $this->security->get_csrf_token_name(); ?>: '<?= $this->security->get_csrf_hash(); ?>'
},
beforeSend: function() {
// Disable the button to prevent multiple clicks
$('button[onclick*="deleteOutlookIntegration(' + appointmentId + ')"]').prop('disabled', true);
},
success: function(response) {
try {
if (typeof response === 'string') {
response = JSON.parse(response);
}
if (response.success) {
// Show appropriate success message based on authentication status
var successMessage = response.message;
if (!isAuthenticated) {
successMessage = "<?= addslashes(_l('appointment_outlook_integration_removed_local_only')); ?>";
}
alert_float('success', successMessage);
// Reload the page to reflect changes
setTimeout(function() {
window.location.reload();
}, 1000);
} else {
alert_float('danger', response.message || "<?= addslashes(_l('appointment_outlook_removal_failed')); ?>");
}
} catch (e) {
console.error('Error parsing response:', e);
alert_float('danger', "<?= addslashes(_l('something_went_wrong')); ?>");
}
},
error: function(xhr, status, error) {
alert_float('danger', "<?= addslashes(_l('request_failed')); ?>: " + error);
},
complete: function() {
// Re-enable the button
$('button[onclick*="deleteOutlookIntegration(' + appointmentId + ')"]').prop('disabled', false);
}
});
}
}
function showAppointmentLoadingOverlay() {
$('.panel-body.tw-p-4.sm\\:tw-p-6').addClass('appointly-loading');
}
function hideAppointmentLoadingOverlay() {
$('.panel-body.tw-p-4.sm\\:tw-p-6').removeClass('appointly-loading');
}
</script>