/home/edulekha/crm.edulekha.com/modules/appointly/assets/js/tables_appointment_js.php
<script>
// Language variables
var appointly_lang_finished = "<?= _l('appointment_marked_as_finished'); ?>";
var appointly_lang_cancelled = "<?= _l('appointment_is_cancelled'); ?>";
var appointly_mark_as_ongoing = "<?= _l('appointment_marked_as_ongoing'); ?>";
var appointment_are_you_sure_mark_as_ongoing = "<?= _l('appointment_are_you_sure_to_mark_as_ongoing') ?>";
var appointly_are_you_sure_mark_as_cancelled = "<?= _l('appointment_are_you_sure_to_cancel') ?>";
var appointly_are_you_early_reminders = "<?= _l('appointly_are_you_early_reminders') ?>";
var appointly_reminders_sent = "<?= _l('appointly_reminders_sent') ?>";
var appointly_please_wait = "<?= _l('appointment_please_wait'); ?>";
// body class
$(function() {
let el = $('body').find('#google_not_set');
setTimeout(() => {
if ($(el).is(":visible")) $(el).fadeOut();
}, 5000)
$("body").addClass("single_view_board");
});
// Set active menu state
$('li.menu-item-appointly').addClass('active');
$('li.menu-item-appointly > ul').addClass('in');
$('li.sub-menu-item-appointly-user-dashboard').addClass('active');
// Gather staff attendee emails for Google Meet
var attendees = window.google_meet_attendees || [];
$(".modal").on("hidden.bs.modal", function(e) {
tinymce.remove("textarea[name=\"google_meet_notify_message\"]");
$(this).removeData();
});
// Mark appointment as finished
function markAppointmentAsFinished() {
var url = window.location.search;
var id = url.split("=")[1];
$.ajax({
url: "change_appointment_status",
type: "POST",
dataType: "json",
data: {
id: id,
status: 'completed'
},
beforeSend: function() {
disableButtonsAfterPost($("#markAsFinished"));
},
success: function(response) {
if (response && response.success === true) {
alert_float("success", appointly_lang_finished);
// If on the appointments table, refresh it
if ($('#appointments_table').length) {
$('#appointments_table').DataTable().ajax.reload();
} else {
// Otherwise reload the page
reloadlocation(800);
}
} else {
var message = response && response.message ? response.message : "Status change failed";
alert_float("warning", message);
console.error("Error marking as finished:", response);
}
},
});
}
// Cancel appointment
function cancelAppointment() {
var url = window.location.search;
var id = url.split("=")[1];
if (confirm(appointly_are_you_sure_mark_as_cancelled)) {
$.ajax({
url: "change_appointment_status",
type: "POST",
dataType: "json",
data: {
id: id,
status: 'cancelled'
},
beforeSend: function() {
disableButtonsAfterPost($("#cancelAppointment"));
},
success: function(response) {
if (response && response.success === true) {
alert_float("success", appointly_lang_cancelled);
// If on the appointments table, refresh it
if ($('#appointments_table').length) {
$('#appointments_table').DataTable().ajax.reload();
} else {
// Otherwise reload the page
reloadlocation(800);
}
} else {
var message = response && response.message ? response.message : "Status change failed";
alert_float("warning", message);
console.error("Error cancelling appointment:", response);
}
},
});
}
}
// Mark appointment as ongoing if marked as cancelled
function markAppointmentAsOngoing() {
var url = window.location.search;
var id = url.split("=")[1];
if (confirm(appointment_are_you_sure_mark_as_ongoing)) {
$.ajax({
url: "change_appointment_status",
type: "POST",
dataType: "json",
data: {
id: id,
status: 'in-progress'
},
beforeSend: function() {
disableButtonsAfterPost($("#markAppointmentAsOngoing"));
},
success: function(response) {
if (response && response.success === true) {
alert_float("success", appointly_mark_as_ongoing);
// If on the appointments table, refresh it
if ($('#appointments_table').length) {
$('#appointments_table').DataTable().ajax.reload();
} else {
// Otherwise reload the page
reloadlocation(800);
}
} else {
var message = response && response.message ? response.message : "Status change failed";
alert_float("warning", message);
console.error("Error in markAppointmentAsOngoing:", response);
}
},
});
}
}
// Mark appointment as no-show
function markAppointmentAsNoShow() {
var url = window.location.search;
var id = url.split("=")[1];
$.ajax({
url: "change_appointment_status",
type: "POST",
dataType: "json",
data: {
id: id,
status: 'no-show'
},
beforeSend: function() {
disableButtonsAfterPost($("#markAsNoShow"));
},
success: function(response) {
if (response && response.success === true) {
alert_float("success", response.message || "Appointment marked as no-show");
// If on the appointments table, refresh it
if ($('#appointments_table').length) {
$('#appointments_table').DataTable().ajax.reload();
} else {
// Otherwise reload the page
reloadlocation(800);
}
} else {
var message = response && response.message ? response.message : "Status change failed";
alert_float("warning", message);
console.error("Error marking as no-show:", response);
}
},
});
}
// Trigger appointment reminders
function sendAppointmentReminders() {
var url = window.location.search;
var id = url.split("=")[1];
if (confirm(appointly_are_you_early_reminders)) {
$.post("send_appointment_early_reminders", {
id: id,
beforeSend: function() {
disableButtonsAfterPost($("#sendAppointmentReminders"));
},
}).done(function(r) {
r = JSON.parse(r);
if (r.success == true) {
alert_float("success", appointly_reminders_sent);
reloadlocation(2000);
}
});
}
}
function sendAppointmentRemindersEmail() {
var message = $("#google_meet_notify_message").val();
if ($.trim(message) == "") {
alert_float("warning", "<?= _l('appointment_google_meet_message_required'); ?>");
return false;
}
$("#submit_google_meet_email_btn").html("<i class=\"fa fa-refresh fa-spin fa-fw\"></i> <?= _l('sending'); ?>").attr("disabled", true);
// Use the global attendees variable set in the view
var attendees = window.google_meet_attendees || [];
var notifyAttendees = $("#notify_attendees_checkbox").is(':checked');
// Debug: Log the attendees to verify they're being used correctly
var emailData = {
message: message,
client_external_url: "<?= isset($appointment['public_url']) ? $appointment['public_url'] : ''; ?>",
google_meet_link: "<?= isset($appointment['google_meet_link']) ? $appointment['google_meet_link'] : ''; ?>",
to: "<?= isset($appointment['email']) ? $appointment['email'] : ''; ?>", // client email
attendees: notifyAttendees ? attendees : [] // only send to attendees if checkbox is checked
};
$.post("send_appointment_custom_email", emailData).done(function(r) {
// Handle both string/boolean and JSON responses
try {
if (typeof r === 'string') {
r = JSON.parse(r);
}
} catch (e) {
// If parsing fails, treat as legacy boolean response
r = {
success: r === true || r === 'true'
};
}
if (r.success) {
// Show detailed success message if available
var message = r.message || "<?= _l('appointment_meeting_request_sent'); ?>";
if (r.total_sent && r.total_sent > 1) {
message += " (" + r.total_sent + " recipients)";
}
alert_float("success", message);
$("#customEmailModal").modal('hide');
reloadlocation(1500);
} else {
var errorMessage = r.message || "<?= _l('appointment_email_send_failed'); ?>";
alert_float("warning", errorMessage);
$("#submit_google_meet_email_btn").html("<?= _l('send'); ?>").attr("disabled", false);
}
}).fail(function() {
alert_float("danger", "<?= _l('appointment_email_send_failed'); ?>");
$("#submit_google_meet_email_btn").html("<?= _l('send'); ?>").attr("disabled", false);
});
}
// Disable buttons
function disableButtonsAfterPost(button) {
$("#markAsFinished").attr("disabled", true);
$("#confirmDelete").attr("disabled", true);
$("#cancelAppointment").attr("disabled", true);
$("#markAppointmentAsOngoing").attr("disabled", true);
$(".btn-primary-google").attr("disabled", true);
button.html("" + appointly_please_wait + "<i class=\"fa fa-refresh fa-spin fa-fw\"></i>");
}
// Disable delete button used in view as function
function disableButtonsAfterDelete() {
$("button").attr("disabled", true);
$("a").addClass("disabled");
return false;
}
// Simple reload
function reloadlocation(timer) {
setTimeout(function() {
location.reload();
}, timer);
}
// Initialize editor
init_editor('textarea[name="notes"]', {
menubar: true,
});
// Save notes function
function save_appointment_notes() {
var $btn = $('#privNotesBtn');
var notes = tinymce.get('notes').getContent();
var appointment_id = '<?php echo $appointment['id']; ?>';
var csrf_token_name = $('input[name="<?= $this->security->get_csrf_token_name(); ?>"]').attr('name');
var csrf_token_value = $('input[name="<?= $this->security->get_csrf_token_name(); ?>"]').val();
$btn.prop('disabled', true).html($btn.data('loading-text'));
var data = {
appointment_id: appointment_id,
notes: notes
};
// Add CSRF token
data[csrf_token_name] = csrf_token_value;
$.post(admin_url + 'appointly/appointments/save_notes_from_appointment_view', data)
.done(function(response) {
response = JSON.parse(response);
if (response.success) {
alert_float('success', "<?= _l('appointment_notes_updated'); ?>");
}
})
.fail(function() {
alert_float('danger', "<?= _l('appointment_notes_update_failed'); ?>");
})
.always(function() {
$btn.prop('disabled', false).html("<?= _l('save'); ?>");
});
}
// Initialize tabs
$('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
var target = $(e.target).attr("href");
if (target === '#tab_notes') {
// Reinitialize editor if needed
if (typeof(tinymce) != 'undefined') {
init_editor('#notes');
}
}
});
// Initialize editor for notes
if (typeof(tinymce) != 'undefined') {
init_editor('#notes', {
menubar: false,
height: 150
});
}
// Description editing functions
function editDescription() {
$('#description_display').hide();
$('#description_placeholder').hide();
$('#description_edit_btn').hide();
$('#description_editor').show();
// Initialize TinyMCE editor
init_editor('#appointment_description', {
menubar: false,
height: 200,
setup: function(editor) {
editor.on('init', function() {
editor.focus();
});
}
});
}
function saveDescription() {
var content = tinymce.get('appointment_description').getContent();
var appointmentId = $('#appointment_description').data('appointment-id');
$.post(admin_url + 'appointly/appointments/save_appointment_description', {
appointment_id: appointmentId,
description: content
})
.done(function(response) {
try {
response = JSON.parse(response);
if (response.success) {
alert_float('success', response.message);
// Update display
if (content.trim() === '') {
$('#description_display').hide();
$('#description_placeholder').show();
$('#description_edit_btn').hide();
} else {
$('#description_display').html(content).show();
$('#description_placeholder').hide();
$('#description_edit_btn').show();
}
cancelDescription();
} else {
alert_float('danger', response.message);
}
} catch (e) {
alert_float('danger', 'Error parsing response');
}
})
.fail(function() {
alert_float('danger', 'Failed to save description');
});
}
function cancelDescription() {
tinymce.remove('#appointment_description');
$('#description_editor').hide();
// Show appropriate elements
var hasContent = $('#description_display').text().trim() !== '';
if (hasContent) {
$('#description_display').show();
$('#description_edit_btn').show();
} else {
$('#description_placeholder').show();
}
}
// Notes editing functions
function editNotes() {
$('#notes_display').hide();
$('#notes_placeholder').hide();
$('#notes_edit_btn').hide();
$('#notes_editor').show();
// Initialize TinyMCE editor
init_editor('#appointment_notes', {
menubar: false,
height: 200,
setup: function(editor) {
editor.on('init', function() {
editor.focus();
});
}
});
}
function saveNotes() {
var content = tinymce.get('appointment_notes').getContent();
var appointmentId = $('#appointment_notes').data('appointment-id');
$.post(admin_url + 'appointly/appointments/save_notes_from_appointment_view', {
appointment_id: appointmentId,
notes: content
})
.done(function(response) {
try {
response = JSON.parse(response);
if (response.success) {
alert_float('success', response.message);
// Update display
if (content.trim() === '') {
$('#notes_display').hide();
$('#notes_placeholder').show();
$('#notes_edit_btn').hide();
} else {
$('#notes_display').html(content).show();
$('#notes_placeholder').hide();
$('#notes_edit_btn').show();
}
cancelNotes();
} else {
alert_float('danger', response.message);
}
} catch (e) {
alert_float('danger', 'Error parsing response');
}
})
.fail(function() {
alert_float('danger', 'Failed to save notes');
});
}
function cancelNotes() {
tinymce.remove('#appointment_notes');
$('#notes_editor').hide();
// Show appropriate elements
var hasContent = $('#notes_display').text().trim() !== '';
if (hasContent) {
$('#notes_display').show();
$('#notes_edit_btn').show();
} else {
$('#notes_placeholder').show();
}
}
// Cleanup TinyMCE when modal closes
$("#customEmailModal").on("hidden.bs.modal", function() {
tinymce.remove("textarea[name='google_meet_notify_message']");
$(this).removeData();
});
// Hide "Google API Key not set" alert after 5s
let el = $('#google_not_set');
if (el.length) {
setTimeout(function() {
el.fadeOut();
}, 5000);
}
// Enhanced Google Meet Functions
// Copy Google Meet link to clipboard
function copyGoogleMeetLink(meetLink) {
// Use modern clipboard API
navigator.clipboard.writeText(meetLink).then(function(r) {
// Show success message in modal if available
const successMsg = document.getElementById('copySuccessMessage');
if (successMsg) {
successMsg.classList.remove('hidden');
successMsg.style.display = 'block';
setTimeout(() => {
successMsg.classList.add('hidden');
successMsg.style.display = 'none';
}, 3000);
alert_float('success', "<?= _l('appointment_google_meet_link_copied'); ?>");
}
})
}
/**
* Approve cancellation request
* @param {number} appointment_id - The appointment ID to approve cancellation for
*/
function approveCancellation(appointment_id) {
if (!appointment_id) {
alert_float('danger', "<?= addslashes(_l('invalid_appointment_id')); ?>");
return;
}
if (!confirm("<?= _l('appointment_are_you_sure_to_cancel') ?>")) {
return;
}
$.ajax({
url: admin_url + 'appointly/appointments/cancel_appointment',
type: 'POST',
data: {
id: appointment_id
},
success: function(response) {
var result = typeof response === 'string' ? JSON.parse(response) : response;
if (result.success) {
alert_float('success', (typeof _l !== 'undefined' ? _l('appointment_cancellation_approved') : 'Cancellation approved'));
setTimeout(function() {
window.location.reload();
}, 1500);
} else {
alert_float('danger', "<?= addslashes(_l('appointment_cancellation_approval_failed')); ?>: " + (result.message || "<?= addslashes(_l('unknown_error')); ?>"));
}
},
error: function(xhr, status, error) {
alert_float('danger', "<?= addslashes(_l('request_failed')); ?>: " + error);
}
});
}
// Request feedback from client
function requestFeedback(appointmentId) {
if (confirm("<?= _l('appointments_are_you_sure_request_feedback'); ?>")) {
$.post(admin_url + 'appointly/appointments/requestAppointmentFeedback', {
appointment_id: appointmentId
}).done(function(response) {
try {
// Make sure response is properly parsed as JSON
if (typeof response === 'string') {
response = JSON.parse(response);
}
if (response.success) {
alert_float('success', "<?= addslashes(_l('appointment_feedback_requested_alert')); ?>");
} else {
alert_float('warning', response.message || "<?= addslashes(_l('something_went_wrong')); ?>");
}
} catch (e) {
console.error('Error parsing response:', e);
alert_float('danger', "<?= addslashes(_l('something_went_wrong')); ?>");
}
}).fail(function() {
alert_float('danger', "<?= addslashes(_l('something_went_wrong')); ?>");
});
}
}
// Convert appointment to invoice
function convertAppointmentToInvoice(appointmentId, viewAfterConvert = false) {
if (confirm("<?= addslashes(_l('appointment_are_you_sure_convert_to_invoice')); ?>")) {
$.post(admin_url + 'appointly/appointments/convert_to_invoice', {
appointment_id: appointmentId
}).done(function(response) {
try {
response = JSON.parse(response);
} catch (e) {
response = response;
}
if (response.success) {
alert_float('success', response.message);
if (viewAfterConvert && response.invoice_id) {
// Redirect to view the invoice
window.location.href = admin_url + 'invoices/invoice/' + response.invoice_id;
} else {
reloadlocation(1000);
}
} else {
alert_float('danger', response.message || "<?= addslashes(_l('appointment_convert_to_invoice_failed')); ?>");
}
}).fail(function(xhr, textStatus, errorThrown) {
alert_float('danger', "<?= addslashes(_l('appointment_convert_to_invoice_failed')); ?>");
console.error(xhr.responseText || errorThrown);
});
}
}
// 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);
}
});
}
}
// Add event to Google Calendar from appointment view
function addEventToGoogleCalendarFromView(appointmentId) {
var $btn = $('button[onclick="addEventToGoogleCalendarFromView(' + appointmentId + ')"]');
// Get attendees from appointment data if available
var attendees = [];
if (window.appointment_data && window.appointment_data.attendees) {
attendees = window.appointment_data.attendees;
}
$.ajax({
url: "<?= admin_url('appointly/appointments/addEventToGoogleCalendar'); ?>",
type: "POST",
data: {
appointment_id: appointmentId,
attendees: attendees,
'<?= $this->security->get_csrf_token_name(); ?>': '<?= $this->security->get_csrf_hash(); ?>'
},
beforeSend: function() {
$btn.prop('disabled', true);
$btn.html('<i class="fa fa-refresh fa-spin fa-fw"></i>');
},
success: function(response) {
if (response && response.result === 'success') {
alert_float('success', "<?= _l('appointments_added_to_google_calendar'); ?>");
location.reload();
} else {
alert_float('warning', response.message || "<?= _l('appointment_error_adding_to_calendar'); ?>");
$btn.prop('disabled', false);
$btn.html('<i class="fab fa-google"></i>');
}
},
error: function() {
alert_float('danger', "<?= _l('appointment_error_adding_to_calendar'); ?>");
$btn.prop('disabled', false);
$btn.html('<i class="fab fa-google"></i>');
}
});
}
// Add event to Outlook Calendar from appointment view
function addEventToOutlookCalendarFromView(appointmentId) {
var $btn = $('button[onclick="addEventToOutlookCalendarFromView(' + appointmentId + ')"]');
// Check if Outlook is authenticated
if (typeof isOutlookLoggedIn !== 'function' || !isOutlookLoggedIn()) {
if (typeof signInToOutlook === 'function') {
signInToOutlook();
} else {
alert_float('danger', "<?= _l('appointment_outlook_auth_error'); ?>");
}
return;
}
$btn.prop('disabled', true);
$btn.html('<i class="fa fa-refresh fa-spin fa-fw"></i>');
// Use the appointment view specific Outlook integration
addToOutlookFromAppointmentView(appointmentId, $btn);
}
// Custom Outlook integration function for appointment view (mimics update page approach)
function addToOutlookFromAppointmentView(appointmentId, $btn) {
if (!isOutlookLoggedIn()) {
signInToOutlook();
return false;
}
// Get appointment data from window object
var appointment = window.appointment_data;
if (!appointment || appointment.id != appointmentId) {
alert_float('danger', "Appointment data not available");
$btn.prop('disabled', false).html('<i class="fab fa-microsoft"></i>');
return;
}
// Get fresh token
myMSALObj.acquireTokenSilent(outlookConf.requestObj)
.then(function(tokenResponse) {
var accessToken = tokenResponse.accessToken;
// Get attendee data if we have staff IDs
var outlook_attendees = appointment.attendees || [];
if (outlook_attendees.length > 0) {
$.post(site_url + 'appointly/appointments/getAttendeeData', {
ids: outlook_attendees,
<?= $this->security->get_csrf_token_name() ?>: '<?= $this->security->get_csrf_hash() ?>'
}).done(function(attendees) {
try {
if (typeof attendees === 'string') {
attendees = JSON.parse(attendees);
}
} catch (e) {
attendees = [];
}
createOutlookEventFromView(appointment, attendees, accessToken, $btn);
}).fail(function(xhr, status, error) {
createOutlookEventFromView(appointment, [], accessToken, $btn);
});
} else {
createOutlookEventFromView(appointment, [], accessToken, $btn);
}
})
.catch(function(error) {
if (requiresInteraction(error.errorCode)) {
myMSALObj.acquireTokenPopup(outlookConf.requestObj)
.then(function(tokenResponse) {
addToOutlookFromAppointmentView(appointmentId, $btn);
})
.catch(function(error) {
alert_float('danger', "<?= _l('appointment_outlook_auth_error') ?>");
$btn.prop('disabled', false).html('<i class="fab fa-microsoft"></i>');
});
} else {
alert_float('danger', "<?= _l('appointment_outlook_auth_error') ?>");
$btn.prop('disabled', false).html('<i class="fab fa-microsoft"></i>');
}
});
}
// Create Outlook event from appointment view data
function createOutlookEventFromView(appointment, attendees, accessToken, $btn) {
// Prepare attendees for Outlook
var outlookAttendees = [];
if (attendees && attendees.length > 0) {
attendees.forEach(function(attendee) {
// The attendees model returns data in this format:
// [{ emailAddress: { address: "email", name: "name" } }]
if (attendee.emailAddress && attendee.emailAddress.address) {
outlookAttendees.push({
emailAddress: {
address: attendee.emailAddress.address.trim(),
name: attendee.emailAddress.name.trim()
}
});
}
// Fallback for other data formats
else if (attendee.email && attendee.email.trim()) {
var name = '';
if (attendee.firstname && attendee.lastname) {
name = attendee.firstname + ' ' + attendee.lastname;
} else if (attendee.full_name) {
name = attendee.full_name;
} else if (attendee.name) {
name = attendee.name;
} else {
name = attendee.email;
}
outlookAttendees.push({
emailAddress: {
address: attendee.email.trim(),
name: name.trim()
}
});
}
});
}
// Parse date and time
var startDateTime = moment(appointment.date + ' ' + appointment.start_hour, 'YYYY-MM-DD HH:mm');
var endDateTime;
if (appointment.end_hour) {
endDateTime = moment(appointment.date + ' ' + appointment.end_hour, 'YYYY-MM-DD HH:mm');
} else {
// Default to 1 hour duration
endDateTime = startDateTime.clone().add(1, 'hour');
}
// Prepare event data for Outlook
var event = {
subject: appointment.subject,
body: {
contentType: 'HTML',
content: appointment.description || ''
},
start: {
dateTime: startDateTime.toISOString(),
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
},
end: {
dateTime: endDateTime.toISOString(),
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
},
location: {
displayName: appointment.address || ''
},
attendees: outlookAttendees
};
// Create event in Outlook
$.ajax({
url: 'https://graph.microsoft.com/v1.0/me/events',
type: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + accessToken,
},
data: JSON.stringify(event)
}).done(function(response) {
if (response.id) {
// Update appointment with Outlook event details
var updateData = {
appointment_id: appointment.id,
outlook_event_id: response.id,
outlook_calendar_link: response.webLink || response.htmlLink || '',
<?= $this->security->get_csrf_token_name() ?>: '<?= $this->security->get_csrf_hash() ?>'
};
$.post(site_url + 'appointly/appointments/update_outlook_event', updateData)
.done(function(updateResponse) {
try {
var parsedResponse = typeof updateResponse === 'string' ? JSON.parse(updateResponse) : updateResponse;
if (parsedResponse && parsedResponse.result === true) {
alert_float('success', "<?= _l('appointments_added_to_outlook_calendar') ?>");
location.reload();
} else {
alert_float('warning', "Event created in Outlook but failed to update appointment record");
location.reload();
}
} catch (e) {
alert_float('warning', "Event created in Outlook but failed to update appointment record");
location.reload();
}
}).fail(function(xhr, status, error) {
alert_float('warning', "Event created in Outlook but failed to update appointment record");
location.reload();
});
}
}).fail(function(xhr) {
if (xhr.status === 401) {
myMSALObj.acquireTokenPopup(outlookConf.requestObj)
.then(function(newTokenResponse) {
createOutlookEventFromView(appointment, attendees, newTokenResponse.accessToken, $btn);
})
.catch(function(error) {
alert_float('danger', "<?= _l('appointment_outlook_auth_error') ?>");
$btn.prop('disabled', false).html('<i class="fab fa-microsoft"></i>');
});
} else {
console.error('Outlook API error:', xhr.responseText);
alert_float('danger', "<?= _l('appointment_outlook_error') ?>");
$btn.prop('disabled', false).html('<i class="fab fa-microsoft"></i>');
}
});
}
</script>
<style>
.mce-widget.mce-btn.mce-menubtn.mce-flow-layout-item.mce-last.mce-btn-has-text,
.mce-widget.mce-btn.mce-splitbtn.mce-colorbutton.mce-last,
.mce-widget.mce-btn.mce-splitbtn.mce-colorbutton.mce-first {
display: none;
}
</style>