2025-08-06 15:25:47 -07:00
|
|
|
jQuery(document).ready(function($) {
|
|
|
|
// Schedule Management
|
|
|
|
window.openScheduleModal = function() {
|
|
|
|
$('#schedule-form')[0].reset();
|
|
|
|
$('#schedule-id').val('');
|
|
|
|
$('#schedule-modal-title').text('Add New Schedule');
|
|
|
|
|
|
|
|
// Reset form fields to defaults
|
|
|
|
$('[name="days_of_week[]"]').prop('checked', false);
|
|
|
|
$('[name="is_active"]').prop('checked', true);
|
|
|
|
$('#after-hours-forward').hide();
|
|
|
|
$('#after-hours-workflow').hide();
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
tb_show('Add New Schedule', '#TB_inline?inlineId=schedule-modal&width=650&height=600');
|
2025-08-06 15:25:47 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
window.closeScheduleModal = function() {
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_remove();
|
2025-08-06 15:25:47 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
window.editSchedule = function(id) {
|
|
|
|
// Load schedule data and populate form
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_schedule',
|
|
|
|
schedule_id: id,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
var schedule = response.data;
|
|
|
|
$('#schedule-modal-title').text('Edit Schedule');
|
|
|
|
$('#schedule-id').val(schedule.id);
|
|
|
|
$('[name="schedule_name"]').val(schedule.schedule_name);
|
|
|
|
$('[name="start_time"]').val(schedule.start_time);
|
|
|
|
$('[name="end_time"]').val(schedule.end_time);
|
|
|
|
$('[name="workflow_id"]').val(schedule.workflow_id);
|
2025-08-11 20:31:48 -07:00
|
|
|
$('[name="holiday_dates"]').val(schedule.holiday_dates || '');
|
2025-08-06 15:25:47 -07:00
|
|
|
$('[name="is_active"]').prop('checked', schedule.is_active == '1');
|
|
|
|
|
|
|
|
// Set days of week checkboxes
|
|
|
|
var days = schedule.days_of_week ? schedule.days_of_week.split(',') : [];
|
|
|
|
$('[name="days_of_week[]"]').prop('checked', false);
|
|
|
|
days.forEach(function(day) {
|
|
|
|
$('[name="days_of_week[]"][value="' + day.trim() + '"]').prop('checked', true);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Set after hours action
|
|
|
|
if (schedule.after_hours_action) {
|
|
|
|
$('[name="after_hours_action"]').val(schedule.after_hours_action);
|
|
|
|
toggleAfterHoursFields($('[name="after_hours_action"]')[0]);
|
|
|
|
|
|
|
|
if (schedule.after_hours_action === 'forward' && schedule.after_hours_forward_number) {
|
|
|
|
$('[name="after_hours_forward_number"]').val(schedule.after_hours_forward_number);
|
|
|
|
} else if (schedule.after_hours_action === 'workflow' && schedule.after_hours_workflow_id) {
|
|
|
|
$('[name="after_hours_workflow_id"]').val(schedule.after_hours_workflow_id);
|
|
|
|
}
|
|
|
|
} else if (schedule.forward_number) {
|
|
|
|
// Legacy support for old forward_number field
|
|
|
|
$('[name="after_hours_action"]').val('forward');
|
|
|
|
$('[name="after_hours_forward_number"]').val(schedule.forward_number);
|
|
|
|
toggleAfterHoursFields($('[name="after_hours_action"]')[0]);
|
|
|
|
} else {
|
|
|
|
$('[name="after_hours_action"]').val('workflow');
|
|
|
|
toggleAfterHoursFields($('[name="after_hours_action"]')[0]);
|
|
|
|
}
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_show('Edit Schedule: ' + schedule.schedule_name, '#TB_inline?inlineId=schedule-modal&width=650&height=600');
|
2025-08-06 15:25:47 -07:00
|
|
|
} else {
|
|
|
|
alert('Error loading schedule data');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
window.deleteSchedule = function(id) {
|
|
|
|
if (confirm('Are you sure you want to delete this schedule?')) {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_delete_schedule',
|
|
|
|
schedule_id: id,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
location.reload();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
$('#schedule-form').on('submit', function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
// Get form data excluding days_of_week (we'll handle manually to avoid duplicates)
|
|
|
|
var formData = $(this).serializeArray();
|
|
|
|
var filteredData = [];
|
|
|
|
|
|
|
|
// Filter out days_of_week from serialized data to avoid duplicates
|
|
|
|
formData.forEach(function(item) {
|
|
|
|
if (item.name !== 'days_of_week[]') {
|
|
|
|
filteredData.push(item.name + '=' + encodeURIComponent(item.value));
|
|
|
|
}
|
|
|
|
});
|
2025-08-06 15:25:47 -07:00
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
var formDataString = filteredData.join('&');
|
|
|
|
|
|
|
|
// Collect days of week checkboxes manually
|
2025-08-06 15:25:47 -07:00
|
|
|
var daysOfWeek = [];
|
2025-08-11 20:31:48 -07:00
|
|
|
$('[name="days_of_week[]"]:checked').each(function() {
|
2025-08-06 15:25:47 -07:00
|
|
|
daysOfWeek.push($(this).val());
|
|
|
|
});
|
|
|
|
|
|
|
|
// Add days to form data if any selected
|
|
|
|
if (daysOfWeek.length > 0) {
|
2025-08-11 20:31:48 -07:00
|
|
|
formDataString += '&days_of_week[]=' + daysOfWeek.join('&days_of_week[]=');
|
2025-08-06 15:25:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add after-hours fields
|
|
|
|
var afterHoursAction = $('select[name="after_hours_action"]').val();
|
|
|
|
if (afterHoursAction) {
|
2025-08-11 20:31:48 -07:00
|
|
|
formDataString += '&after_hours_action=' + encodeURIComponent(afterHoursAction);
|
2025-08-06 15:25:47 -07:00
|
|
|
|
|
|
|
if (afterHoursAction === 'workflow') {
|
|
|
|
var afterHoursWorkflow = $('select[name="after_hours_workflow_id"]').val();
|
|
|
|
if (afterHoursWorkflow) {
|
2025-08-11 20:31:48 -07:00
|
|
|
formDataString += '&after_hours_workflow_id=' + encodeURIComponent(afterHoursWorkflow);
|
2025-08-06 15:25:47 -07:00
|
|
|
}
|
|
|
|
} else if (afterHoursAction === 'forward') {
|
|
|
|
var afterHoursForward = $('input[name="after_hours_forward_number"]').val();
|
|
|
|
if (afterHoursForward) {
|
2025-08-11 20:31:48 -07:00
|
|
|
formDataString += '&after_hours_forward_number=' + encodeURIComponent(afterHoursForward);
|
2025-08-06 15:25:47 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
formDataString += '&action=twp_save_schedule&nonce=' + twp_ajax.nonce;
|
2025-08-06 15:25:47 -07:00
|
|
|
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Submitting schedule form data:', formDataString);
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, formDataString, function(response) {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Schedule save response:', response);
|
2025-08-06 15:25:47 -07:00
|
|
|
if (response.success) {
|
|
|
|
closeScheduleModal();
|
|
|
|
location.reload();
|
|
|
|
} else {
|
|
|
|
alert('Error saving schedule: ' + (response.data || 'Unknown error'));
|
|
|
|
}
|
2025-08-11 20:31:48 -07:00
|
|
|
}).fail(function(xhr, status, error) {
|
|
|
|
console.error('Schedule save failed:', xhr, status, error);
|
|
|
|
alert('Network error saving schedule: ' + error);
|
2025-08-06 15:25:47 -07:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// Toggle after hours fields
|
|
|
|
window.toggleAfterHoursFields = function(select) {
|
|
|
|
var value = $(select).val();
|
|
|
|
$('#after-hours-forward').hide();
|
|
|
|
$('#after-hours-workflow').hide();
|
|
|
|
|
|
|
|
if (value === 'forward') {
|
|
|
|
$('#after-hours-forward').show();
|
|
|
|
} else if (value === 'workflow') {
|
|
|
|
$('#after-hours-workflow').show();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
window.toggleActionFields = function(select) {
|
|
|
|
if ($(select).val() === 'forward') {
|
|
|
|
$('#forward-fields').show();
|
|
|
|
$('#workflow-fields').hide();
|
|
|
|
} else {
|
|
|
|
$('#forward-fields').hide();
|
|
|
|
$('#workflow-fields').show();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Workflow Builder
|
|
|
|
var workflowSteps = [];
|
|
|
|
var currentWorkflowId = null;
|
|
|
|
|
|
|
|
window.openWorkflowBuilder = function() {
|
|
|
|
$('#workflow-modal-title').text('Create New Workflow');
|
|
|
|
workflowSteps = [];
|
|
|
|
currentWorkflowId = null;
|
|
|
|
$('#workflow-basic-info')[0].reset();
|
|
|
|
loadPhoneNumbers();
|
|
|
|
updateWorkflowDisplay();
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_show('Create New Workflow', '#TB_inline?inlineId=workflow-builder&width=900&height=700');
|
2025-08-06 15:25:47 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
window.closeWorkflowBuilder = function() {
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_remove();
|
2025-08-06 15:25:47 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
window.closeStepConfigModal = function() {
|
2025-08-11 20:31:48 -07:00
|
|
|
// Hide the step config modal and remove show class
|
|
|
|
$('#step-config-modal').hide().removeClass('show');
|
2025-08-06 15:25:47 -07:00
|
|
|
};
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
function loadPhoneNumbers(callback) {
|
2025-08-06 15:25:47 -07:00
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_phone_numbers',
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
var options = '<option value="">Select a phone number...</option>';
|
|
|
|
response.data.forEach(function(number) {
|
|
|
|
options += '<option value="' + number.phone_number + '">' + number.phone_number + ' (' + number.friendly_name + ')</option>';
|
|
|
|
});
|
|
|
|
$('#workflow-phone').html(options);
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Call the callback if provided
|
|
|
|
if (typeof callback === 'function') {
|
|
|
|
callback();
|
|
|
|
}
|
2025-08-06 15:25:47 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step type button handlers
|
|
|
|
$(document).on('click', '.step-btn', function() {
|
|
|
|
var stepType = $(this).data('step-type');
|
|
|
|
addWorkflowStep(stepType);
|
|
|
|
});
|
|
|
|
|
|
|
|
function addWorkflowStep(stepType) {
|
|
|
|
var step = {
|
|
|
|
id: Date.now(),
|
|
|
|
type: stepType,
|
|
|
|
data: getDefaultStepData(stepType)
|
|
|
|
};
|
|
|
|
|
|
|
|
workflowSteps.push(step);
|
|
|
|
updateWorkflowDisplay();
|
|
|
|
|
|
|
|
// Open configuration modal
|
|
|
|
openStepConfigModal(step.id, stepType);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDefaultStepData(stepType) {
|
|
|
|
switch(stepType) {
|
|
|
|
case 'greeting':
|
|
|
|
return { message: '', use_tts: true };
|
|
|
|
case 'ivr_menu':
|
|
|
|
return { message: '', options: {}, num_digits: 1, timeout: 10 };
|
|
|
|
case 'forward':
|
|
|
|
return { forward_number: '', timeout: 30 };
|
|
|
|
case 'queue':
|
2025-08-11 20:31:48 -07:00
|
|
|
return { queue_id: '', announce_message: '' };
|
2025-08-06 15:25:47 -07:00
|
|
|
case 'voicemail':
|
|
|
|
return { greeting_message: '', max_length: 120 };
|
|
|
|
case 'schedule_check':
|
2025-08-11 20:31:48 -07:00
|
|
|
return { schedule_id: '', after_hours_steps: [] };
|
2025-08-06 15:25:47 -07:00
|
|
|
case 'sms':
|
|
|
|
return { to_number: '', message: '' };
|
|
|
|
default:
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function openStepConfigModal(stepId, stepType) {
|
|
|
|
var step = workflowSteps.find(function(s) { return s.id === stepId; });
|
|
|
|
if (!step) return;
|
|
|
|
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Opening step config modal for step ID:', stepId, 'type:', stepType);
|
2025-08-11 20:31:48 -07:00
|
|
|
|
2025-08-06 15:25:47 -07:00
|
|
|
$('#step-id').val(stepId);
|
|
|
|
$('#step-type').val(stepType);
|
|
|
|
$('#step-config-title').text('Configure ' + getStepTypeName(stepType) + ' Step');
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
var configContent = generateStepConfigForm(stepType, step.data || {});
|
2025-08-06 15:25:47 -07:00
|
|
|
$('#step-config-content').html(configContent);
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
// Move the modal outside of ThickBox if needed
|
|
|
|
if ($('#step-config-modal').closest('#TB_window').length > 0) {
|
|
|
|
$('#step-config-modal').appendTo('body');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Show as overlay with proper flexbox positioning
|
|
|
|
$('#step-config-modal').css('display', 'flex').addClass('show');
|
|
|
|
|
2025-08-12 09:12:54 -07:00
|
|
|
// Additional debugging: Log all voice selects and their data-current values
|
|
|
|
setTimeout(function() {
|
|
|
|
$('#step-config-modal .voice-select').each(function() {
|
|
|
|
var $select = $(this);
|
|
|
|
// console.log('Voice select found in modal - data-current:', $select.data('current'), 'current val:', $select.val());
|
|
|
|
});
|
|
|
|
}, 100);
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
// If it's a schedule step, load the schedules
|
|
|
|
if (stepType === 'schedule_check') {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Setting up schedule step - current data:', step.data);
|
2025-08-11 20:31:48 -07:00
|
|
|
setTimeout(function() {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('About to load schedules, current select element:', $('#schedule-select'));
|
2025-08-11 20:31:48 -07:00
|
|
|
loadSchedulesForStep();
|
|
|
|
}, 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If it's a queue step, load the queues
|
|
|
|
if (stepType === 'queue') {
|
|
|
|
setTimeout(function() {
|
|
|
|
var $queueSelect = $('#step-config-modal .queue-select');
|
|
|
|
if ($queueSelect.length) {
|
|
|
|
loadQueuesForSelect($queueSelect);
|
|
|
|
}
|
|
|
|
}, 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If it's an IVR step, load queues for any existing queue actions
|
|
|
|
if (stepType === 'ivr_menu') {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Setting up IVR step - current data:', step.data);
|
2025-08-11 20:31:48 -07:00
|
|
|
setTimeout(function() {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Loading queues for IVR step...');
|
2025-08-11 20:31:48 -07:00
|
|
|
$('#step-config-modal .target-queue').each(function() {
|
|
|
|
var $select = $(this);
|
2025-08-12 09:12:54 -07:00
|
|
|
var currentValue = $select.data('current');
|
|
|
|
// console.log('Loading queue for select with current value:', currentValue);
|
|
|
|
loadQueuesForSelect($select);
|
2025-08-11 20:31:48 -07:00
|
|
|
});
|
2025-08-12 09:12:54 -07:00
|
|
|
|
|
|
|
// Also load voices if TTS is selected for the step
|
|
|
|
if (step.data && step.data.audio_type === 'tts') {
|
|
|
|
// console.log('Loading voices for TTS step...');
|
|
|
|
var $voiceSelect = $('#step-config-modal .voice-select');
|
|
|
|
if ($voiceSelect.length) {
|
|
|
|
// Ensure the data-current attribute is properly set
|
|
|
|
if (!$voiceSelect.data('current') && step.data.voice_id) {
|
|
|
|
$voiceSelect.attr('data-current', step.data.voice_id);
|
|
|
|
$voiceSelect.data('current', step.data.voice_id);
|
|
|
|
// console.log('Manually set data-current to:', step.data.voice_id);
|
|
|
|
}
|
|
|
|
// console.log('Voice select found, loading voices with current:', $voiceSelect.data('current'));
|
|
|
|
loadWorkflowVoices($voiceSelect[0]);
|
|
|
|
} else {
|
|
|
|
// console.log('No voice select found');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, 500); // Increased timeout even more
|
|
|
|
}
|
|
|
|
|
|
|
|
// For greeting and voicemail steps, auto-load voices if TTS is selected
|
|
|
|
if ((stepType === 'greeting' || stepType === 'voicemail') && step.data && step.data.audio_type === 'tts') {
|
|
|
|
// console.log('Setting up', stepType, 'step with TTS - voice_id:', step.data.voice_id);
|
|
|
|
setTimeout(function() {
|
|
|
|
var $voiceSelect = $('#step-config-modal .voice-select');
|
|
|
|
if ($voiceSelect.length) {
|
|
|
|
// Ensure the data-current attribute is properly set
|
|
|
|
if (!$voiceSelect.data('current') && step.data.voice_id) {
|
|
|
|
$voiceSelect.attr('data-current', step.data.voice_id);
|
|
|
|
$voiceSelect.data('current', step.data.voice_id);
|
|
|
|
// console.log('Manually set data-current to:', step.data.voice_id);
|
|
|
|
}
|
|
|
|
// console.log('Voice select found for', stepType, 'with data-current:', $voiceSelect.data('current'));
|
|
|
|
loadWorkflowVoices($voiceSelect[0]);
|
|
|
|
} else {
|
|
|
|
// console.log('No voice select found for', stepType);
|
|
|
|
}
|
|
|
|
}, 300);
|
2025-08-11 20:31:48 -07:00
|
|
|
}
|
2025-08-06 15:25:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function getStepTypeName(stepType) {
|
|
|
|
var names = {
|
|
|
|
'greeting': 'Greeting',
|
|
|
|
'ivr_menu': 'IVR Menu',
|
|
|
|
'forward': 'Forward Call',
|
|
|
|
'queue': 'Call Queue',
|
|
|
|
'voicemail': 'Voicemail',
|
|
|
|
'schedule_check': 'Schedule Check',
|
|
|
|
'sms': 'SMS Notification'
|
|
|
|
};
|
|
|
|
return names[stepType] || stepType;
|
|
|
|
}
|
|
|
|
|
|
|
|
function generateStepConfigForm(stepType, data) {
|
|
|
|
var html = '';
|
|
|
|
|
|
|
|
switch(stepType) {
|
|
|
|
case 'greeting':
|
|
|
|
html += '<div class="step-config-section">';
|
|
|
|
html += '<h4>Greeting Message</h4>';
|
|
|
|
html += '<label>Message:</label>';
|
|
|
|
html += '<textarea name="message" rows="3" placeholder="Welcome to our phone system...">' + (data.message || '') + '</textarea>';
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Audio playback options
|
|
|
|
html += '<div style="margin-top: 15px;">';
|
|
|
|
html += '<h4>Audio Options</h4>';
|
|
|
|
html += '<label><input type="radio" name="audio_type" value="say" ' + (!data.audio_type || data.audio_type === 'say' ? 'checked' : '') + '> Use Default Voice (Say)</label><br>';
|
|
|
|
html += '<label><input type="radio" name="audio_type" value="tts" ' + (data.audio_type === 'tts' ? 'checked' : '') + '> Use Text-to-Speech (ElevenLabs)</label><br>';
|
|
|
|
html += '<label><input type="radio" name="audio_type" value="audio" ' + (data.audio_type === 'audio' ? 'checked' : '') + '> Use Audio File (MP3)</label>';
|
|
|
|
|
|
|
|
// TTS Options
|
|
|
|
html += '<div class="tts-options" style="margin-top: 10px; ' + (data.audio_type === 'tts' ? '' : 'display:none;') + '">';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '<label>Voice:</label>';
|
|
|
|
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
|
|
|
html += '<option value="">Default voice</option>';
|
2025-08-12 09:12:54 -07:00
|
|
|
// If we have a saved voice, show it
|
|
|
|
if (data.voice_id && data.voice_name) {
|
|
|
|
html += '<option value="' + data.voice_id + '" selected>' + data.voice_name + '</option>';
|
|
|
|
}
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '</select>';
|
2025-08-12 09:12:54 -07:00
|
|
|
html += '<input type="hidden" name="voice_name" value="' + (data.voice_name || '') + '">';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '<button type="button" class="button button-small" onclick="loadWorkflowVoices(this)">Load Voices</button>';
|
|
|
|
html += '</div>';
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Audio File Options
|
|
|
|
html += '<div class="audio-options" style="margin-top: 10px; ' + (data.audio_type === 'audio' ? '' : 'display:none;') + '">';
|
|
|
|
html += '<label>Audio File URL:</label>';
|
|
|
|
html += '<input type="url" name="audio_url" placeholder="https://example.com/audio.mp3" value="' + (data.audio_url || '') + '">';
|
|
|
|
html += '<p class="description">Enter the direct URL to an MP3 file. The file must be publicly accessible.</p>';
|
|
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '</div>';
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'ivr_menu':
|
|
|
|
html += '<div class="step-config-section">';
|
|
|
|
html += '<h4>Menu Settings</h4>';
|
|
|
|
html += '<label>Menu Message:</label>';
|
|
|
|
html += '<textarea name="message" rows="3" placeholder="Press 1 for sales, 2 for support...">' + (data.message || '') + '</textarea>';
|
|
|
|
html += '<label>Number of Digits:</label>';
|
|
|
|
html += '<input type="number" name="num_digits" min="1" max="5" value="' + (data.num_digits || 1) + '">';
|
|
|
|
html += '<label>Timeout (seconds):</label>';
|
|
|
|
html += '<input type="number" name="timeout" min="5" max="60" value="' + (data.timeout || 10) + '">';
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Audio playback options
|
|
|
|
html += '<div style="margin-top: 15px;">';
|
|
|
|
html += '<h4>Audio Options</h4>';
|
|
|
|
html += '<label><input type="radio" name="audio_type" value="say" ' + (!data.audio_type || data.audio_type === 'say' ? 'checked' : '') + '> Use Default Voice (Say)</label><br>';
|
|
|
|
html += '<label><input type="radio" name="audio_type" value="tts" ' + (data.audio_type === 'tts' ? 'checked' : '') + '> Use Text-to-Speech (ElevenLabs)</label><br>';
|
|
|
|
html += '<label><input type="radio" name="audio_type" value="audio" ' + (data.audio_type === 'audio' ? 'checked' : '') + '> Use Audio File (MP3)</label>';
|
|
|
|
|
|
|
|
// TTS Options
|
|
|
|
html += '<div class="tts-options" style="margin-top: 10px; ' + (data.audio_type === 'tts' ? '' : 'display:none;') + '">';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '<label>Voice:</label>';
|
|
|
|
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
|
|
|
html += '<option value="">Default voice</option>';
|
2025-08-12 09:12:54 -07:00
|
|
|
// If we have a saved voice, show it
|
|
|
|
if (data.voice_id && data.voice_name) {
|
|
|
|
html += '<option value="' + data.voice_id + '" selected>' + data.voice_name + '</option>';
|
|
|
|
}
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '</select>';
|
2025-08-12 09:12:54 -07:00
|
|
|
html += '<input type="hidden" name="voice_name" value="' + (data.voice_name || '') + '">';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '<button type="button" class="button button-small" onclick="loadWorkflowVoices(this)">Load Voices</button>';
|
|
|
|
html += '</div>';
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Audio File Options
|
|
|
|
html += '<div class="audio-options" style="margin-top: 10px; ' + (data.audio_type === 'audio' ? '' : 'display:none;') + '">';
|
|
|
|
html += '<label>Audio File URL:</label>';
|
|
|
|
html += '<input type="url" name="audio_url" placeholder="https://example.com/audio.mp3" value="' + (data.audio_url || '') + '">';
|
|
|
|
html += '<p class="description">Enter the direct URL to an MP3 file. The file must be publicly accessible.</p>';
|
|
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '</div>';
|
|
|
|
|
|
|
|
html += '<div class="step-config-section">';
|
|
|
|
html += '<h4>Menu Options</h4>';
|
|
|
|
html += '<div class="ivr-options" id="ivr-options-list">';
|
|
|
|
|
|
|
|
if (data.options && Object.keys(data.options).length > 0) {
|
|
|
|
Object.keys(data.options).forEach(function(digit) {
|
|
|
|
html += generateIvrOptionHtml(digit, data.options[digit]);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
html += generateIvrOptionHtml('1', { action: 'forward', number: '' });
|
|
|
|
}
|
|
|
|
|
|
|
|
html += '</div>';
|
|
|
|
html += '<div class="add-ivr-option" onclick="addIvrOption()">+ Add Option</div>';
|
|
|
|
html += '</div>';
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'forward':
|
|
|
|
html += '<div class="step-config-section">';
|
|
|
|
html += '<h4>Forward Settings</h4>';
|
|
|
|
html += '<label>Forward to Number:</label>';
|
|
|
|
html += '<input type="text" name="forward_number" placeholder="+1234567890" value="' + (data.forward_number || '') + '">';
|
|
|
|
html += '<label>Ring Timeout (seconds):</label>';
|
|
|
|
html += '<input type="number" name="timeout" min="5" max="120" value="' + (data.timeout || 30) + '">';
|
|
|
|
html += '</div>';
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'queue':
|
|
|
|
html += '<div class="step-config-section">';
|
|
|
|
html += '<h4>Queue Settings</h4>';
|
|
|
|
html += '<label>Queue Name:</label>';
|
2025-08-11 20:31:48 -07:00
|
|
|
html += '<select name="queue_id" class="queue-select" data-current="' + (data.queue_id || '') + '"><option value="">Select queue...</option></select>';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '<button type="button" class="button button-small" onclick="loadQueues(this)">Load Queues</button>';
|
|
|
|
html += '<label>Announcement Message:</label>';
|
|
|
|
html += '<textarea name="announce_message" rows="2" placeholder="Please hold while we connect you...">' + (data.announce_message || '') + '</textarea>';
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Audio playback options
|
|
|
|
html += '<div style="margin-top: 15px;">';
|
|
|
|
html += '<h4>Audio Options</h4>';
|
|
|
|
html += '<label><input type="radio" name="audio_type" value="say" ' + (!data.audio_type || data.audio_type === 'say' ? 'checked' : '') + '> Use Default Voice (Say)</label><br>';
|
|
|
|
html += '<label><input type="radio" name="audio_type" value="tts" ' + (data.audio_type === 'tts' ? 'checked' : '') + '> Use Text-to-Speech (ElevenLabs)</label><br>';
|
|
|
|
html += '<label><input type="radio" name="audio_type" value="audio" ' + (data.audio_type === 'audio' ? 'checked' : '') + '> Use Audio File (MP3)</label>';
|
|
|
|
|
|
|
|
// TTS Options
|
|
|
|
html += '<div class="tts-options" style="margin-top: 10px; ' + (data.audio_type === 'tts' ? '' : 'display:none;') + '">';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '<label>Voice:</label>';
|
|
|
|
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
|
|
|
html += '<option value="">Default voice</option>';
|
2025-08-12 09:12:54 -07:00
|
|
|
// If we have a saved voice, show it
|
|
|
|
if (data.voice_id && data.voice_name) {
|
|
|
|
html += '<option value="' + data.voice_id + '" selected>' + data.voice_name + '</option>';
|
|
|
|
}
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '</select>';
|
2025-08-12 09:12:54 -07:00
|
|
|
html += '<input type="hidden" name="voice_name" value="' + (data.voice_name || '') + '">';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '<button type="button" class="button button-small" onclick="loadWorkflowVoices(this)">Load Voices</button>';
|
|
|
|
html += '</div>';
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Audio File Options
|
|
|
|
html += '<div class="audio-options" style="margin-top: 10px; ' + (data.audio_type === 'audio' ? '' : 'display:none;') + '">';
|
|
|
|
html += '<label>Audio File URL:</label>';
|
|
|
|
html += '<input type="url" name="audio_url" placeholder="https://example.com/audio.mp3" value="' + (data.audio_url || '') + '">';
|
|
|
|
html += '<p class="description">Enter the direct URL to an MP3 file. The file must be publicly accessible.</p>';
|
|
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '</div>';
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'voicemail':
|
|
|
|
html += '<div class="step-config-section">';
|
|
|
|
html += '<h4>Voicemail Settings</h4>';
|
|
|
|
html += '<label>Greeting Message:</label>';
|
|
|
|
html += '<textarea name="greeting_message" rows="2" placeholder="Please leave a message after the beep...">' + (data.greeting_message || '') + '</textarea>';
|
|
|
|
html += '<label>Maximum Length (seconds):</label>';
|
|
|
|
html += '<input type="number" name="max_length" min="10" max="600" value="' + (data.max_length || 120) + '">';
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Audio playback options
|
|
|
|
html += '<div style="margin-top: 15px;">';
|
|
|
|
html += '<h4>Audio Options</h4>';
|
|
|
|
html += '<label><input type="radio" name="audio_type" value="say" ' + (!data.audio_type || data.audio_type === 'say' ? 'checked' : '') + '> Use Default Voice (Say)</label><br>';
|
|
|
|
html += '<label><input type="radio" name="audio_type" value="tts" ' + (data.audio_type === 'tts' ? 'checked' : '') + '> Use Text-to-Speech (ElevenLabs)</label><br>';
|
|
|
|
html += '<label><input type="radio" name="audio_type" value="audio" ' + (data.audio_type === 'audio' ? 'checked' : '') + '> Use Audio File (MP3)</label>';
|
|
|
|
|
|
|
|
// TTS Options
|
|
|
|
html += '<div class="tts-options" style="margin-top: 10px; ' + (data.audio_type === 'tts' ? '' : 'display:none;') + '">';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '<label>Voice:</label>';
|
|
|
|
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
|
|
|
html += '<option value="">Default voice</option>';
|
2025-08-12 09:12:54 -07:00
|
|
|
// If we have a saved voice, show it
|
|
|
|
if (data.voice_id && data.voice_name) {
|
|
|
|
html += '<option value="' + data.voice_id + '" selected>' + data.voice_name + '</option>';
|
|
|
|
}
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '</select>';
|
2025-08-12 09:12:54 -07:00
|
|
|
html += '<input type="hidden" name="voice_name" value="' + (data.voice_name || '') + '">';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '<button type="button" class="button button-small" onclick="loadWorkflowVoices(this)">Load Voices</button>';
|
|
|
|
html += '</div>';
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Audio File Options
|
|
|
|
html += '<div class="audio-options" style="margin-top: 10px; ' + (data.audio_type === 'audio' ? '' : 'display:none;') + '">';
|
|
|
|
html += '<label>Audio File URL:</label>';
|
|
|
|
html += '<input type="url" name="audio_url" placeholder="https://example.com/audio.mp3" value="' + (data.audio_url || '') + '">';
|
|
|
|
html += '<p class="description">Enter the direct URL to an MP3 file. The file must be publicly accessible.</p>';
|
|
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '</div>';
|
|
|
|
break;
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
case 'schedule_check':
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Generating schedule_check form with data:', data);
|
2025-08-11 20:31:48 -07:00
|
|
|
html += '<div class="step-config-section">';
|
|
|
|
html += '<h4>Schedule Check Settings</h4>';
|
|
|
|
html += '<label>Select Schedule:</label>';
|
|
|
|
html += '<select name="schedule_id" id="schedule-select" data-current="' + (data.schedule_id || '') + '" value="' + (data.schedule_id || '') + '">';
|
|
|
|
html += '<option value="">Loading schedules...</option>';
|
|
|
|
html += '</select>';
|
|
|
|
html += '<p class="description">Uses WordPress timezone: ' + twp_ajax.timezone + '</p>';
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Schedule ID being set:', (data.schedule_id || ''));
|
2025-08-11 20:31:48 -07:00
|
|
|
html += '</div>';
|
|
|
|
|
|
|
|
html += '<div class="step-config-section">';
|
|
|
|
html += '<h4>After-Hours Actions</h4>';
|
|
|
|
html += '<p class="description">Define what happens when calls come in outside business hours:</p>';
|
|
|
|
html += '<div id="after-hours-steps" class="after-hours-steps-container">';
|
|
|
|
html += '<div class="after-hours-step-list">';
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Generating after-hours steps HTML, data:', data.after_hours_steps);
|
2025-08-11 20:31:48 -07:00
|
|
|
if (data.after_hours_steps && data.after_hours_steps.length > 0) {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Found', data.after_hours_steps.length, 'after-hours steps');
|
2025-08-11 20:31:48 -07:00
|
|
|
data.after_hours_steps.forEach(function(step, index) {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Generating HTML for after-hours step', index, ':', step);
|
2025-08-11 20:31:48 -07:00
|
|
|
html += generateAfterHoursStepHtml(step, index);
|
|
|
|
});
|
|
|
|
} else {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('No after-hours steps found');
|
2025-08-11 20:31:48 -07:00
|
|
|
html += '<p class="no-steps-message">No after-hours steps configured. Add steps below.</p>';
|
|
|
|
}
|
|
|
|
html += '</div>';
|
|
|
|
html += '<div class="add-after-hours-step">';
|
|
|
|
html += '<label>Add After-Hours Step:</label>';
|
|
|
|
html += '<select id="after-hours-step-type">';
|
|
|
|
html += '<option value="">Select step type...</option>';
|
|
|
|
html += '<option value="greeting">Play Greeting</option>';
|
|
|
|
html += '<option value="forward">Forward Call</option>';
|
|
|
|
html += '<option value="voicemail">Take Voicemail</option>';
|
|
|
|
html += '<option value="queue">Add to Queue</option>';
|
|
|
|
html += '<option value="sms">Send SMS</option>';
|
|
|
|
html += '</select>';
|
|
|
|
html += '<button type="button" class="button button-small" onclick="addAfterHoursStep()">Add Step</button>';
|
|
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
|
|
|
|
|
|
|
// Load schedules after form is rendered
|
|
|
|
html += '<script>setTimeout(function() { loadSchedulesForStep(); }, 50);</script>';
|
|
|
|
break;
|
|
|
|
|
2025-08-06 15:25:47 -07:00
|
|
|
case 'sms':
|
|
|
|
html += '<div class="step-config-section">';
|
|
|
|
html += '<h4>SMS Notification</h4>';
|
|
|
|
html += '<label>Send to Number:</label>';
|
|
|
|
html += '<input type="text" name="to_number" placeholder="+1234567890" value="' + (data.to_number || '') + '">';
|
|
|
|
html += '<label>Message:</label>';
|
|
|
|
html += '<textarea name="message" rows="3" placeholder="You have a missed call from {from}...">' + (data.message || '') + '</textarea>';
|
|
|
|
html += '<p class="description">Use {from}, {to}, {time} as placeholders</p>';
|
|
|
|
html += '</div>';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return html;
|
|
|
|
}
|
|
|
|
|
|
|
|
function generateIvrOptionHtml(digit, option) {
|
|
|
|
var html = '<div class="ivr-option">';
|
|
|
|
html += '<input type="text" name="digit[]" value="' + digit + '" maxlength="1" style="text-align:center;">';
|
|
|
|
html += '<input type="text" name="description[]" placeholder="Description" value="' + (option.description || '') + '">';
|
2025-08-11 20:31:48 -07:00
|
|
|
html += '<select name="action[]" class="ivr-action-select">';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '<option value="forward" ' + (option.action === 'forward' ? 'selected' : '') + '>Forward</option>';
|
|
|
|
html += '<option value="queue" ' + (option.action === 'queue' ? 'selected' : '') + '>Queue</option>';
|
|
|
|
html += '<option value="voicemail" ' + (option.action === 'voicemail' ? 'selected' : '') + '>Voicemail</option>';
|
|
|
|
html += '<option value="message" ' + (option.action === 'message' ? 'selected' : '') + '>Message</option>';
|
|
|
|
html += '</select>';
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Target input - varies based on action type
|
|
|
|
html += '<div class="ivr-target-container">';
|
|
|
|
|
|
|
|
// Forward action - phone number input
|
|
|
|
html += '<input type="text" name="target[]" class="target-forward" placeholder="Phone Number" value="' +
|
|
|
|
(option.action === 'forward' ? (option.number || option.target || '') : '') +
|
2025-08-12 09:12:54 -07:00
|
|
|
'" style="display: ' + (option.action === 'forward' || !option.action ? 'block' : 'none') + ';" ' +
|
|
|
|
(option.action !== 'forward' && option.action ? 'disabled' : '') + '>';
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Queue action - queue dropdown
|
2025-08-12 09:12:54 -07:00
|
|
|
var queueCurrent = '';
|
|
|
|
if (option.action === 'queue') {
|
|
|
|
queueCurrent = option.queue_id || option.target || option.number || '';
|
|
|
|
}
|
|
|
|
html += '<select name="target[]" class="target-queue queue-select" data-current="' + queueCurrent +
|
|
|
|
'" style="display: ' + (option.action === 'queue' ? 'block' : 'none') + ';" ' +
|
|
|
|
(option.action !== 'queue' ? 'disabled' : '') + '>';
|
2025-08-11 20:31:48 -07:00
|
|
|
html += '<option value="">Select queue...</option>';
|
|
|
|
// Queue options will be populated by loadQueues function
|
|
|
|
html += '</select>';
|
|
|
|
|
|
|
|
// Voicemail action - message input
|
|
|
|
html += '<input type="text" name="target[]" class="target-voicemail" placeholder="Voicemail Message" value="' +
|
|
|
|
(option.action === 'voicemail' ? (option.message || option.target || '') : '') +
|
2025-08-12 09:12:54 -07:00
|
|
|
'" style="display: ' + (option.action === 'voicemail' ? 'block' : 'none') + ';" ' +
|
|
|
|
(option.action !== 'voicemail' ? 'disabled' : '') + '>';
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Message action - message input
|
|
|
|
html += '<input type="text" name="target[]" class="target-message" placeholder="Message Text" value="' +
|
|
|
|
(option.action === 'message' ? (option.message || option.target || '') : '') +
|
2025-08-12 09:12:54 -07:00
|
|
|
'" style="display: ' + (option.action === 'message' ? 'block' : 'none') + ';" ' +
|
|
|
|
(option.action !== 'message' ? 'disabled' : '') + '>';
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
html += '</div>';
|
|
|
|
html += '<button type="button" class="button button-small remove-ivr-option" onclick="removeIvrOption(this)">Remove</button>';
|
2025-08-06 15:25:47 -07:00
|
|
|
html += '</div>';
|
|
|
|
return html;
|
|
|
|
}
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
function generateAfterHoursStepHtml(step, index) {
|
|
|
|
var html = '<div class="after-hours-step" data-index="' + index + '">';
|
|
|
|
html += '<div class="step-header">';
|
|
|
|
html += '<span class="step-number">' + (index + 1) + '.</span>';
|
|
|
|
html += '<span class="step-type">' + getStepTypeName(step.type) + '</span>';
|
|
|
|
html += '<button type="button" class="button-link remove-step" onclick="removeAfterHoursStep(' + index + ')">Remove</button>';
|
|
|
|
html += '</div>';
|
|
|
|
html += '<div class="step-details">';
|
|
|
|
|
|
|
|
switch(step.type) {
|
|
|
|
case 'greeting':
|
|
|
|
html += '<textarea name="after_hours_steps[' + index + '][message]" placeholder="After-hours greeting message...">' + (step.message || '') + '</textarea>';
|
|
|
|
break;
|
|
|
|
case 'forward':
|
|
|
|
html += '<input type="text" name="after_hours_steps[' + index + '][number]" placeholder="+1234567890" value="' + (step.number || '') + '">';
|
|
|
|
break;
|
|
|
|
case 'voicemail':
|
|
|
|
html += '<textarea name="after_hours_steps[' + index + '][greeting]" placeholder="Voicemail greeting...">' + (step.greeting || '') + '</textarea>';
|
|
|
|
break;
|
|
|
|
case 'queue':
|
|
|
|
html += '<input type="text" name="after_hours_steps[' + index + '][queue_name]" placeholder="Queue name" value="' + (step.queue_name || '') + '">';
|
|
|
|
break;
|
|
|
|
case 'sms':
|
|
|
|
html += '<input type="text" name="after_hours_steps[' + index + '][to_number]" placeholder="+1234567890" value="' + (step.to_number || '') + '">';
|
|
|
|
html += '<textarea name="after_hours_steps[' + index + '][message]" placeholder="SMS message...">' + (step.message || '') + '</textarea>';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
html += '<input type="hidden" name="after_hours_steps[' + index + '][type]" value="' + step.type + '">';
|
|
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
|
|
|
return html;
|
|
|
|
}
|
|
|
|
|
|
|
|
window.addAfterHoursStep = function() {
|
|
|
|
var stepType = $('#after-hours-step-type').val();
|
|
|
|
if (!stepType) {
|
|
|
|
alert('Please select a step type');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the "no steps" message if it exists
|
|
|
|
$('.no-steps-message').remove();
|
|
|
|
|
|
|
|
var stepList = $('.after-hours-step-list');
|
|
|
|
var currentSteps = stepList.find('.after-hours-step').length;
|
|
|
|
|
|
|
|
var newStep = {
|
|
|
|
type: stepType,
|
|
|
|
message: '',
|
|
|
|
number: '',
|
|
|
|
greeting: '',
|
|
|
|
queue_name: '',
|
|
|
|
to_number: ''
|
|
|
|
};
|
|
|
|
|
|
|
|
stepList.append(generateAfterHoursStepHtml(newStep, currentSteps));
|
|
|
|
$('#after-hours-step-type').val('');
|
|
|
|
};
|
|
|
|
|
|
|
|
window.removeAfterHoursStep = function(index) {
|
|
|
|
$('.after-hours-step[data-index="' + index + '"]').remove();
|
|
|
|
|
|
|
|
// Reindex remaining steps
|
|
|
|
$('.after-hours-step').each(function(i) {
|
|
|
|
$(this).attr('data-index', i);
|
|
|
|
$(this).find('.step-number').text((i + 1) + '.');
|
|
|
|
$(this).find('input, textarea').each(function() {
|
|
|
|
var name = $(this).attr('name');
|
|
|
|
if (name) {
|
|
|
|
name = name.replace(/\[\d+\]/, '[' + i + ']');
|
|
|
|
$(this).attr('name', name);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
$(this).find('.remove-step').attr('onclick', 'removeAfterHoursStep(' + i + ')');
|
|
|
|
});
|
|
|
|
|
|
|
|
if ($('.after-hours-step').length === 0) {
|
|
|
|
$('.after-hours-step-list').html('<p class="no-steps-message">No after-hours steps configured. Add steps below.</p>');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
window.loadSchedulesForStep = function() {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Loading schedules for step modal');
|
2025-08-11 20:31:48 -07:00
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_schedules',
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Schedule response:', response);
|
2025-08-11 20:31:48 -07:00
|
|
|
if (response.success) {
|
|
|
|
var $select = $('#schedule-select');
|
|
|
|
var currentScheduleId = $select.data('current') || $select.val();
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Current schedule ID to select:', currentScheduleId);
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
var options = '<option value="">Select a schedule...</option>';
|
|
|
|
|
|
|
|
response.data.forEach(function(schedule) {
|
|
|
|
var selected = (schedule.id == currentScheduleId) ? ' selected' : '';
|
|
|
|
options += '<option value="' + schedule.id + '"' + selected + '>' + schedule.schedule_name + '</option>';
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Added schedule option:', schedule.schedule_name, 'ID:', schedule.id, 'Selected:', selected);
|
2025-08-11 20:31:48 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
$select.html(options);
|
|
|
|
|
|
|
|
// Ensure the current value is selected after DOM update
|
|
|
|
setTimeout(function() {
|
|
|
|
if (currentScheduleId) {
|
|
|
|
$select.val(currentScheduleId);
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Set schedule select value to:', currentScheduleId, 'Current value:', $select.val());
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Force trigger change event to ensure any listeners are notified
|
|
|
|
$select.trigger('change');
|
|
|
|
}
|
|
|
|
}, 10);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
console.error('Failed to load schedules:', response);
|
|
|
|
$('#schedule-select').html('<option value="">Error loading schedules</option>');
|
|
|
|
}
|
|
|
|
}).fail(function(xhr, status, error) {
|
|
|
|
console.error('AJAX error loading schedules:', error);
|
|
|
|
$('#schedule-select').html('<option value="">Failed to load schedules</option>');
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2025-08-06 15:25:47 -07:00
|
|
|
window.addIvrOption = function() {
|
|
|
|
var nextDigit = $('#ivr-options-list .ivr-option').length + 1;
|
|
|
|
var newOptionHtml = generateIvrOptionHtml(nextDigit.toString(), { action: 'forward', number: '' });
|
|
|
|
$('#ivr-options-list').append(newOptionHtml);
|
|
|
|
};
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
window.removeIvrOption = function(button) {
|
|
|
|
$(button).closest('.ivr-option').remove();
|
|
|
|
};
|
|
|
|
|
2025-08-06 15:25:47 -07:00
|
|
|
// Save step configuration
|
|
|
|
$('#save-step-btn').on('click', function() {
|
|
|
|
var stepId = parseInt($('#step-id').val());
|
|
|
|
var stepType = $('#step-type').val();
|
|
|
|
var formData = $('#step-config-form').serializeArray();
|
|
|
|
|
|
|
|
var step = workflowSteps.find(function(s) { return s.id === stepId; });
|
|
|
|
if (!step) return;
|
|
|
|
|
|
|
|
// Parse form data into step data
|
|
|
|
step.data = parseStepFormData(stepType, formData);
|
|
|
|
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Saved step data:', step);
|
2025-08-11 20:31:48 -07:00
|
|
|
if (stepType === 'schedule_check') {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Saved schedule step data:', step.data);
|
2025-08-11 20:31:48 -07:00
|
|
|
}
|
|
|
|
|
2025-08-06 15:25:47 -07:00
|
|
|
updateWorkflowDisplay();
|
|
|
|
closeStepConfigModal();
|
|
|
|
});
|
|
|
|
|
|
|
|
function parseStepFormData(stepType, formData) {
|
|
|
|
var data = {};
|
|
|
|
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Parsing form data for step type:', stepType);
|
2025-08-11 20:31:48 -07:00
|
|
|
|
2025-08-06 15:25:47 -07:00
|
|
|
formData.forEach(function(field) {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Processing field:', field.name, '=', field.value);
|
2025-08-11 20:31:48 -07:00
|
|
|
|
2025-08-06 15:25:47 -07:00
|
|
|
if (field.name === 'use_tts') {
|
|
|
|
data.use_tts = true;
|
2025-08-11 20:31:48 -07:00
|
|
|
} else if (field.name === 'audio_type') {
|
|
|
|
data.audio_type = field.value;
|
|
|
|
} else if (field.name === 'audio_url') {
|
|
|
|
data.audio_url = field.value;
|
2025-08-06 15:25:47 -07:00
|
|
|
} else if (field.name === 'voice_id') {
|
|
|
|
data.voice_id = field.value;
|
|
|
|
} else if (field.name.endsWith('[]')) {
|
|
|
|
var key = field.name.replace('[]', '');
|
|
|
|
if (!data[key]) data[key] = [];
|
|
|
|
data[key].push(field.value);
|
|
|
|
} else {
|
|
|
|
data[field.name] = field.value;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Parsed data object:', data);
|
|
|
|
|
|
|
|
// Debug voice_id field specifically for greeting/IVR steps
|
|
|
|
if ((stepType === 'greeting' || stepType === 'ivr_menu') && data.voice_id) {
|
|
|
|
// console.log('Found voice_id in', stepType, 'step:', data.voice_id);
|
|
|
|
}
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// For queue steps, also save the queue name for display purposes
|
|
|
|
if (stepType === 'queue' && data.queue_id) {
|
|
|
|
var selectedOption = $('#step-config-form select[name="queue_id"] option:selected');
|
|
|
|
if (selectedOption.length) {
|
|
|
|
data.queue_name = selectedOption.text();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// For schedule steps, also save the schedule name for display purposes
|
|
|
|
if (stepType === 'schedule_check' && data.schedule_id) {
|
|
|
|
var selectedOption = $('#step-config-form select[name="schedule_id"] option:selected');
|
|
|
|
if (selectedOption.length) {
|
|
|
|
data.schedule_name = selectedOption.text();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-08-06 15:25:47 -07:00
|
|
|
// Handle IVR options specially
|
|
|
|
if (stepType === 'ivr_menu' && data.digit) {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Processing IVR options - raw data:', data);
|
2025-08-06 15:25:47 -07:00
|
|
|
data.options = {};
|
|
|
|
for (var i = 0; i < data.digit.length; i++) {
|
2025-08-12 09:12:54 -07:00
|
|
|
var option = {
|
2025-08-06 15:25:47 -07:00
|
|
|
action: data.action[i],
|
|
|
|
description: data.description[i],
|
|
|
|
number: data.target[i],
|
|
|
|
queue_name: data.target[i],
|
|
|
|
message: data.target[i]
|
|
|
|
};
|
2025-08-12 09:12:54 -07:00
|
|
|
|
|
|
|
// console.log('Processing option', i, '- action:', data.action[i], 'target:', data.target[i]);
|
|
|
|
|
|
|
|
// For queue action, get the actual queue name from the select option text
|
|
|
|
if (data.action[i] === 'queue' && data.target[i]) {
|
|
|
|
var $queueSelect = $('#step-config-form .ivr-option:eq(' + i + ') .target-queue');
|
|
|
|
var selectedOption = $queueSelect.find('option:selected');
|
|
|
|
// console.log('Queue select for option', i, ':', $queueSelect.length ? 'found' : 'not found');
|
|
|
|
if (selectedOption.length && selectedOption.text() !== 'Select queue...') {
|
|
|
|
option.queue_name = selectedOption.text();
|
|
|
|
option.queue_id = data.target[i];
|
|
|
|
// console.log('Set queue_name to:', option.queue_name, 'queue_id to:', option.queue_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// console.log('Final option', i, ':', option);
|
|
|
|
data.options[data.digit[i]] = option;
|
2025-08-06 15:25:47 -07:00
|
|
|
}
|
|
|
|
delete data.digit;
|
|
|
|
delete data.action;
|
|
|
|
delete data.description;
|
|
|
|
delete data.target;
|
|
|
|
}
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
// Handle schedule check after-hours steps
|
|
|
|
if (stepType === 'schedule_check') {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Parsing schedule_check step data:', data);
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
var afterHoursSteps = [];
|
|
|
|
|
|
|
|
// First, remove any after_hours_steps fields from the main data object
|
|
|
|
var fieldsToRemove = [];
|
|
|
|
for (var key in data) {
|
|
|
|
if (key.startsWith('after_hours_steps[')) {
|
|
|
|
fieldsToRemove.push(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fieldsToRemove.forEach(function(key) {
|
|
|
|
delete data[key];
|
|
|
|
});
|
|
|
|
|
|
|
|
// Find all after_hours_steps fields
|
|
|
|
formData.forEach(function(field) {
|
|
|
|
var match = field.name.match(/after_hours_steps\[(\d+)\]\[(\w+)\]/);
|
|
|
|
if (match) {
|
|
|
|
var index = parseInt(match[1]);
|
|
|
|
var key = match[2];
|
|
|
|
|
|
|
|
if (!afterHoursSteps[index]) {
|
|
|
|
afterHoursSteps[index] = {};
|
|
|
|
}
|
|
|
|
afterHoursSteps[index][key] = field.value;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Filter out empty entries and reassign
|
|
|
|
data.after_hours_steps = afterHoursSteps.filter(function(step) {
|
|
|
|
return step && step.type;
|
|
|
|
});
|
|
|
|
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Parsed schedule_check data:', data);
|
2025-08-11 20:31:48 -07:00
|
|
|
}
|
|
|
|
|
2025-08-06 15:25:47 -07:00
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
window.removeWorkflowStep = function(stepId) {
|
|
|
|
workflowSteps = workflowSteps.filter(function(step) {
|
|
|
|
return step.id !== stepId;
|
|
|
|
});
|
|
|
|
updateWorkflowDisplay();
|
|
|
|
};
|
|
|
|
|
|
|
|
window.editWorkflowStep = function(stepId) {
|
|
|
|
var step = workflowSteps.find(function(s) { return s.id === stepId; });
|
|
|
|
if (step) {
|
|
|
|
openStepConfigModal(stepId, step.type);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function updateWorkflowDisplay() {
|
|
|
|
var stepsHtml = '';
|
|
|
|
|
|
|
|
workflowSteps.forEach(function(step, index) {
|
|
|
|
stepsHtml += '<div class="workflow-step" data-step-id="' + step.id + '">';
|
|
|
|
stepsHtml += '<div class="step-header">';
|
|
|
|
stepsHtml += '<div class="step-type">';
|
|
|
|
stepsHtml += '<span class="step-number">' + (index + 1) + '</span>';
|
|
|
|
stepsHtml += getStepTypeName(step.type);
|
|
|
|
stepsHtml += '</div>';
|
|
|
|
stepsHtml += '<div class="step-actions">';
|
|
|
|
stepsHtml += '<button class="button button-small" onclick="editWorkflowStep(' + step.id + ')">Edit</button>';
|
|
|
|
stepsHtml += '<button class="button button-small" onclick="removeWorkflowStep(' + step.id + ')">Remove</button>';
|
|
|
|
stepsHtml += '</div>';
|
|
|
|
stepsHtml += '</div>';
|
|
|
|
|
|
|
|
var description = getStepDescription(step);
|
|
|
|
if (description) {
|
|
|
|
stepsHtml += '<div class="workflow-step-content">' + description + '</div>';
|
|
|
|
}
|
|
|
|
|
|
|
|
stepsHtml += '</div>';
|
|
|
|
});
|
|
|
|
|
|
|
|
$('#workflow-steps-list').html(stepsHtml);
|
|
|
|
|
|
|
|
// Update preview
|
|
|
|
updateWorkflowPreview();
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateWorkflowPreview() {
|
|
|
|
var previewHtml = '';
|
|
|
|
|
|
|
|
workflowSteps.forEach(function(step, index) {
|
|
|
|
previewHtml += '<div class="flow-step">';
|
|
|
|
previewHtml += '<strong>' + (index + 1) + '. ' + getStepTypeName(step.type) + '</strong><br>';
|
|
|
|
previewHtml += '<small>' + getStepDescription(step) + '</small>';
|
|
|
|
previewHtml += '</div>';
|
|
|
|
});
|
|
|
|
|
|
|
|
$('#flow-steps').html(previewHtml);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getStepDescription(step) {
|
|
|
|
switch(step.type) {
|
|
|
|
case 'greeting':
|
|
|
|
return step.data.message ? '"' + step.data.message.substring(0, 50) + '..."' : 'No message set';
|
|
|
|
case 'ivr_menu':
|
|
|
|
var optionCount = step.data.options ? Object.keys(step.data.options).length : 0;
|
|
|
|
return 'Menu with ' + optionCount + ' options';
|
|
|
|
case 'forward':
|
|
|
|
return step.data.forward_number ? 'Forward to ' + step.data.forward_number : 'No number set';
|
|
|
|
case 'queue':
|
2025-08-11 20:31:48 -07:00
|
|
|
// Display the queue name if available, otherwise show ID
|
|
|
|
if (step.data.queue_name) {
|
|
|
|
return 'Add to queue: ' + step.data.queue_name;
|
|
|
|
} else if (step.data.queue_id) {
|
|
|
|
return 'Add to queue (ID: ' + step.data.queue_id + ')';
|
|
|
|
} else {
|
|
|
|
return 'No queue selected';
|
|
|
|
}
|
2025-08-06 15:25:47 -07:00
|
|
|
case 'voicemail':
|
|
|
|
return 'Record voicemail (max ' + (step.data.max_length || 120) + 's)';
|
|
|
|
case 'schedule_check':
|
2025-08-11 20:31:48 -07:00
|
|
|
var scheduleInfo = 'No schedule selected';
|
|
|
|
if (step.data.schedule_id) {
|
|
|
|
var scheduleName = step.data.schedule_name || ('Schedule ID: ' + step.data.schedule_id);
|
|
|
|
scheduleInfo = 'Schedule: ' + scheduleName;
|
|
|
|
}
|
|
|
|
var afterHoursCount = step.data.after_hours_steps ? step.data.after_hours_steps.length : 0;
|
|
|
|
return scheduleInfo + (afterHoursCount > 0 ? ' (' + afterHoursCount + ' after-hours steps)' : '');
|
2025-08-06 15:25:47 -07:00
|
|
|
case 'sms':
|
|
|
|
return step.data.to_number ? 'Send SMS to ' + step.data.to_number : 'No recipient set';
|
|
|
|
default:
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save workflow
|
|
|
|
$('#save-workflow-btn').on('click', function() {
|
|
|
|
var formData = $('#workflow-basic-info').serializeArray();
|
|
|
|
var workflowData = {};
|
|
|
|
|
|
|
|
formData.forEach(function(field) {
|
|
|
|
if (field.name === 'is_active') {
|
|
|
|
workflowData.is_active = true;
|
|
|
|
} else {
|
|
|
|
workflowData[field.name] = field.value;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!workflowData.workflow_name) {
|
|
|
|
alert('Please enter a workflow name');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!workflowData.phone_number) {
|
|
|
|
alert('Please select a phone number');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (workflowSteps.length === 0) {
|
|
|
|
alert('Please add at least one step to the workflow');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var payload = {
|
|
|
|
action: currentWorkflowId ? 'twp_update_workflow' : 'twp_save_workflow',
|
|
|
|
workflow_name: workflowData.workflow_name,
|
|
|
|
phone_number: workflowData.phone_number,
|
|
|
|
workflow_data: JSON.stringify({
|
|
|
|
steps: workflowSteps,
|
|
|
|
conditions: [],
|
|
|
|
actions: []
|
|
|
|
}),
|
|
|
|
is_active: workflowData.is_active ? 1 : 0,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
};
|
|
|
|
|
|
|
|
if (currentWorkflowId) {
|
|
|
|
payload.workflow_id = currentWorkflowId;
|
|
|
|
}
|
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, payload, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
closeWorkflowBuilder();
|
|
|
|
location.reload();
|
|
|
|
} else {
|
|
|
|
alert('Error saving workflow: ' + (response.data || 'Unknown error'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
window.editWorkflow = function(workflowId) {
|
|
|
|
// Load workflow data and open builder
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_workflow',
|
|
|
|
workflow_id: workflowId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
var workflow = response.data;
|
|
|
|
|
|
|
|
$('#workflow-modal-title').text('Edit Workflow: ' + workflow.workflow_name);
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_show('Edit Workflow: ' + workflow.workflow_name, '#TB_inline?inlineId=workflow-builder&width=900&height=700');
|
2025-08-06 15:25:47 -07:00
|
|
|
|
|
|
|
// Set basic info
|
|
|
|
$('#workflow-basic-info')[0].reset();
|
|
|
|
$('[name="workflow_name"]').val(workflow.workflow_name);
|
|
|
|
$('[name="is_active"]').prop('checked', workflow.is_active == '1');
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
// Store the phone number to select after options are loaded
|
|
|
|
var phoneNumberToSelect = workflow.phone_number;
|
|
|
|
|
2025-08-06 15:25:47 -07:00
|
|
|
// Load workflow data
|
|
|
|
currentWorkflowId = workflowId;
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Loading workflow data:', workflow.workflow_data);
|
2025-08-06 15:25:47 -07:00
|
|
|
if (workflow.workflow_data) {
|
|
|
|
try {
|
|
|
|
var workflowData = JSON.parse(workflow.workflow_data);
|
|
|
|
workflowSteps = workflowData.steps || [];
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Parsed workflow steps:', workflowSteps);
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Debug schedule steps specifically
|
|
|
|
workflowSteps.forEach(function(step, index) {
|
|
|
|
if (step.type === 'schedule_check') {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Found schedule step at index', index, ':', step);
|
2025-08-11 20:31:48 -07:00
|
|
|
}
|
|
|
|
});
|
2025-08-06 15:25:47 -07:00
|
|
|
} catch (e) {
|
|
|
|
console.error('Error parsing workflow data:', e);
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Raw workflow data that failed to parse:', workflow.workflow_data);
|
2025-08-06 15:25:47 -07:00
|
|
|
workflowSteps = [];
|
|
|
|
}
|
|
|
|
} else {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('No workflow data found');
|
2025-08-06 15:25:47 -07:00
|
|
|
workflowSteps = [];
|
|
|
|
}
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
// Load phone numbers and select the saved one
|
|
|
|
loadPhoneNumbers(function() {
|
|
|
|
$('#workflow-phone').val(phoneNumberToSelect);
|
|
|
|
// Trigger change event in case there are any listeners
|
|
|
|
$('#workflow-phone').trigger('change');
|
|
|
|
});
|
2025-08-06 15:25:47 -07:00
|
|
|
updateWorkflowDisplay();
|
|
|
|
} else {
|
|
|
|
alert('Error loading workflow: ' + (response.data || 'Unknown error'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
window.deleteWorkflow = function(workflowId) {
|
|
|
|
if (confirm('Are you sure you want to delete this workflow?')) {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_delete_workflow',
|
|
|
|
workflow_id: workflowId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
location.reload();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Queue Management
|
|
|
|
window.openQueueModal = function() {
|
|
|
|
$('#queue-form')[0].reset();
|
|
|
|
$('#queue-id').val('');
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_show('Create New Queue', '#TB_inline?inlineId=queue-modal&width=600&height=550');
|
2025-08-06 15:25:47 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
window.closeQueueModal = function() {
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_remove();
|
2025-08-06 15:25:47 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
window.editQueue = function(queueId) {
|
|
|
|
// Load queue data
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_queue',
|
|
|
|
queue_id: queueId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
var queue = response.data;
|
|
|
|
$('#queue-id').val(queue.id);
|
|
|
|
$('[name="queue_name"]').val(queue.queue_name);
|
2025-08-12 09:12:54 -07:00
|
|
|
$('[name="notification_number"]').val(queue.notification_number);
|
2025-08-11 20:31:48 -07:00
|
|
|
$('[name="agent_group_id"]').val(queue.agent_group_id);
|
2025-08-06 15:25:47 -07:00
|
|
|
$('[name="max_size"]').val(queue.max_size);
|
|
|
|
$('[name="timeout_seconds"]').val(queue.timeout_seconds);
|
|
|
|
$('[name="wait_music_url"]').val(queue.wait_music_url);
|
|
|
|
$('[name="tts_message"]').val(queue.tts_message);
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_show('Edit Queue: ' + queue.queue_name, '#TB_inline?inlineId=queue-modal&width=600&height=550');
|
2025-08-06 15:25:47 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
window.viewQueueDetails = function(queueId) {
|
|
|
|
// Show queue details in a proper modal
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_queue_details',
|
|
|
|
queue_id: queueId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
var details = response.data;
|
|
|
|
var queue = details.queue;
|
|
|
|
var waiting_calls = details.waiting_calls;
|
|
|
|
var avg_wait_time = details.avg_wait_time;
|
|
|
|
var calls = details.calls;
|
|
|
|
|
|
|
|
var detailsHtml = '<div class="queue-details">';
|
|
|
|
detailsHtml += '<h3>' + queue.queue_name + ' Details</h3>';
|
|
|
|
detailsHtml += '<div class="queue-stats">';
|
|
|
|
detailsHtml += '<div class="stat"><label>Waiting Calls:</label> ' + waiting_calls + '</div>';
|
|
|
|
detailsHtml += '<div class="stat"><label>Average Wait Time:</label> ' + avg_wait_time + '</div>';
|
|
|
|
detailsHtml += '<div class="stat"><label>Max Queue Size:</label> ' + queue.max_size + '</div>';
|
|
|
|
detailsHtml += '<div class="stat"><label>Timeout:</label> ' + queue.timeout_seconds + ' seconds</div>';
|
|
|
|
detailsHtml += '</div>';
|
|
|
|
|
|
|
|
if (calls && calls.length > 0) {
|
|
|
|
detailsHtml += '<h4>Current Calls in Queue</h4>';
|
|
|
|
detailsHtml += '<table class="wp-list-table widefat fixed striped">';
|
|
|
|
detailsHtml += '<thead><tr><th>Position</th><th>From</th><th>Wait Time</th><th>Status</th></tr></thead>';
|
|
|
|
detailsHtml += '<tbody>';
|
|
|
|
calls.forEach(function(call, index) {
|
|
|
|
detailsHtml += '<tr>';
|
|
|
|
detailsHtml += '<td>' + call.position + '</td>';
|
|
|
|
detailsHtml += '<td>' + call.from_number + '</td>';
|
|
|
|
detailsHtml += '<td>' + call.wait_time + '</td>';
|
|
|
|
detailsHtml += '<td>' + call.status + '</td>';
|
|
|
|
detailsHtml += '</tr>';
|
|
|
|
});
|
|
|
|
detailsHtml += '</tbody></table>';
|
|
|
|
} else {
|
|
|
|
detailsHtml += '<p>No calls currently in queue.</p>';
|
|
|
|
}
|
|
|
|
|
|
|
|
detailsHtml += '</div>';
|
|
|
|
|
|
|
|
// Create and show modal
|
|
|
|
showQueueDetailsModal(detailsHtml);
|
|
|
|
} else {
|
|
|
|
alert('Error loading queue details: ' + (response.data || 'Unknown error'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
function showQueueDetailsModal(content) {
|
|
|
|
// Remove existing modal if present
|
|
|
|
$('#queue-details-modal').remove();
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
// Create WordPress-style modal HTML
|
|
|
|
var modalHtml = `
|
|
|
|
<div id="queue-details-modal" style="display: none;">
|
|
|
|
<div class="twp-wp-modal-content">
|
|
|
|
<div class="twp-wp-modal-header">
|
|
|
|
<h2>📊 Queue Details</h2>
|
|
|
|
</div>
|
|
|
|
<div class="twp-wp-modal-body">
|
|
|
|
${content}
|
|
|
|
</div>
|
|
|
|
<div class="twp-wp-modal-footer">
|
|
|
|
<button class="button" onclick="tb_remove();">Close</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
|
|
|
|
// Add modal HTML to body
|
2025-08-06 15:25:47 -07:00
|
|
|
$('body').append(modalHtml);
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Use WordPress ThickBox
|
|
|
|
tb_show('Queue Details', '#TB_inline?inlineId=queue-details-modal&width=600&height=400');
|
2025-08-06 15:25:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
window.closeQueueDetailsModal = function() {
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_remove();
|
2025-08-06 15:25:47 -07:00
|
|
|
$('#queue-details-modal').remove();
|
|
|
|
};
|
|
|
|
|
|
|
|
window.deleteQueue = function(queueId) {
|
|
|
|
if (confirm('Are you sure you want to delete this queue? All waiting calls will be removed from the queue.')) {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_delete_queue',
|
|
|
|
queue_id: queueId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
location.reload();
|
|
|
|
} else {
|
|
|
|
alert('Error deleting queue: ' + (response.data || 'Unknown error'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
$('#queue-form').on('submit', function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
var formData = $(this).serialize();
|
|
|
|
formData += '&action=twp_save_queue&nonce=' + twp_ajax.nonce;
|
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, formData, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
closeQueueModal();
|
|
|
|
location.reload();
|
|
|
|
} else {
|
|
|
|
alert('Error saving queue');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// Dashboard Auto-refresh
|
|
|
|
if ($('#active-calls').length) {
|
|
|
|
setInterval(function() {
|
|
|
|
updateDashboardStats();
|
|
|
|
}, 5000); // Update every 5 seconds
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateDashboardStats() {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_dashboard_stats',
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
$('#active-calls').text(response.data.active_calls);
|
|
|
|
$('#queued-calls').text(response.data.queued_calls);
|
|
|
|
|
|
|
|
// Update recent calls table
|
|
|
|
if (response.data.recent_calls) {
|
|
|
|
var tableHtml = '';
|
|
|
|
response.data.recent_calls.forEach(function(call) {
|
|
|
|
tableHtml += '<tr>';
|
|
|
|
tableHtml += '<td>' + call.time + '</td>';
|
|
|
|
tableHtml += '<td>' + call.from + '</td>';
|
|
|
|
tableHtml += '<td>' + call.to + '</td>';
|
|
|
|
tableHtml += '<td>' + call.status + '</td>';
|
|
|
|
tableHtml += '<td>' + call.duration + '</td>';
|
|
|
|
tableHtml += '</tr>';
|
|
|
|
});
|
|
|
|
|
|
|
|
if (tableHtml === '') {
|
|
|
|
tableHtml = '<tr><td colspan="5">No recent calls</td></tr>';
|
|
|
|
}
|
|
|
|
|
|
|
|
$('#recent-calls').html(tableHtml);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Phone Numbers Management
|
|
|
|
window.refreshNumbers = function() {
|
|
|
|
$('#twp-numbers-list').html('<div class="twp-spinner"></div><p>Loading phone numbers...</p>');
|
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_phone_numbers',
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
displayPhoneNumbers(response.data);
|
|
|
|
} else {
|
|
|
|
$('#twp-numbers-list').html('<p>Error loading phone numbers: ' + response.data + '</p>');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
window.searchAvailableNumbers = function() {
|
|
|
|
$('#twp-available-numbers').show();
|
|
|
|
$('#search-results').html('');
|
|
|
|
};
|
|
|
|
|
|
|
|
window.searchNumbers = function() {
|
|
|
|
var countryCode = $('#country-code').val();
|
|
|
|
var areaCode = $('#area-code').val();
|
|
|
|
var contains = $('#contains').val();
|
|
|
|
|
|
|
|
$('#search-results').html('<div class="twp-spinner"></div><p>Searching available numbers...</p>');
|
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_search_available_numbers',
|
|
|
|
country_code: countryCode,
|
|
|
|
area_code: areaCode,
|
|
|
|
contains: contains,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
displayAvailableNumbers(response.data);
|
|
|
|
} else {
|
|
|
|
$('#search-results').html('<p>Error searching numbers: ' + response.data + '</p>');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
function displayPhoneNumbers(numbers) {
|
|
|
|
if (numbers.length === 0) {
|
|
|
|
$('#twp-numbers-list').html('<p>No phone numbers found. <a href="#" onclick="searchAvailableNumbers()">Buy your first number</a></p>');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var html = '<div class="twp-numbers-grid">';
|
|
|
|
|
|
|
|
numbers.forEach(function(number) {
|
|
|
|
html += '<div class="twp-number-card">';
|
|
|
|
html += '<div class="number">' + number.phone_number + '</div>';
|
|
|
|
html += '<div class="number-info">';
|
|
|
|
html += '<div><span class="label">Friendly Name:</span>' + (number.friendly_name || 'N/A') + '</div>';
|
|
|
|
html += '<div><span class="label">Voice URL:</span>' + (number.voice_url ? 'Configured' : 'Not set') + '</div>';
|
|
|
|
html += '<div><span class="label">SMS URL:</span>' + (number.sms_url ? 'Configured' : 'Not set') + '</div>';
|
|
|
|
html += '<div><span class="label">Capabilities:</span>';
|
|
|
|
var capabilities = [];
|
|
|
|
if (number.capabilities.voice) capabilities.push('Voice');
|
|
|
|
if (number.capabilities.sms) capabilities.push('SMS');
|
|
|
|
if (number.capabilities.mms) capabilities.push('MMS');
|
|
|
|
html += capabilities.join(', ') + '</div>';
|
|
|
|
html += '</div>';
|
|
|
|
html += '<div class="twp-number-actions">';
|
|
|
|
html += '<button class="button" onclick="configureNumber(\'' + number.sid + '\', \'' + number.phone_number + '\')">Configure</button>';
|
|
|
|
html += '<button class="button" onclick="releaseNumber(\'' + number.sid + '\', \'' + number.phone_number + '\')">Release</button>';
|
|
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
|
|
|
});
|
|
|
|
|
|
|
|
html += '</div>';
|
|
|
|
$('#twp-numbers-list').html(html);
|
|
|
|
}
|
|
|
|
|
|
|
|
function displayAvailableNumbers(numbers) {
|
|
|
|
if (numbers.length === 0) {
|
|
|
|
$('#search-results').html('<p>No numbers found matching your criteria. Try different search terms.</p>');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var html = '';
|
|
|
|
|
|
|
|
numbers.forEach(function(number) {
|
|
|
|
html += '<div class="available-number">';
|
|
|
|
html += '<div>';
|
|
|
|
html += '<div class="number">' + number.phone_number + '</div>';
|
|
|
|
html += '<div class="capabilities">';
|
|
|
|
var capabilities = [];
|
|
|
|
if (number.capabilities.voice) capabilities.push('Voice');
|
|
|
|
if (number.capabilities.sms) capabilities.push('SMS');
|
|
|
|
if (number.capabilities.mms) capabilities.push('MMS');
|
|
|
|
html += capabilities.join(', ');
|
|
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
|
|
|
html += '<div class="price">$1.00/month</div>';
|
|
|
|
html += '<button class="button button-primary" onclick="purchaseNumber(\'' + number.phone_number + '\')">Purchase</button>';
|
|
|
|
html += '</div>';
|
|
|
|
});
|
|
|
|
|
|
|
|
$('#search-results').html(html);
|
|
|
|
}
|
|
|
|
|
|
|
|
window.purchaseNumber = function(phoneNumber) {
|
|
|
|
if (confirm('Purchase ' + phoneNumber + ' for $1.00/month?')) {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_purchase_number',
|
|
|
|
phone_number: phoneNumber,
|
|
|
|
voice_url: twp_ajax.rest_url + 'twilio-webhook/v1/voice',
|
|
|
|
sms_url: twp_ajax.rest_url + 'twilio-webhook/v1/sms',
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
alert('Phone number purchased successfully!');
|
|
|
|
refreshNumbers();
|
|
|
|
$('#twp-available-numbers').hide();
|
|
|
|
} else {
|
|
|
|
alert('Error purchasing number: ' + response.error);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
window.configureNumber = function(numberSid, phoneNumber) {
|
|
|
|
$('#number-sid').val(numberSid);
|
|
|
|
$('#phone-number').val(phoneNumber);
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_show('Configure: ' + phoneNumber, '#TB_inline?inlineId=number-config-modal&width=600&height=400');
|
2025-08-06 15:25:47 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
window.closeNumberConfigModal = function() {
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_remove();
|
2025-08-06 15:25:47 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
$('#number-config-form').on('submit', function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
var formData = $(this).serialize();
|
|
|
|
formData += '&action=twp_configure_number&nonce=' + twp_ajax.nonce;
|
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, formData, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
alert('Phone number configured successfully!');
|
|
|
|
closeNumberConfigModal();
|
|
|
|
refreshNumbers();
|
|
|
|
} else {
|
|
|
|
alert('Error configuring number: ' + response.error);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
window.releaseNumber = function(numberSid, phoneNumber) {
|
|
|
|
if (confirm('Are you sure you want to release ' + phoneNumber + '? This action cannot be undone and you will lose this phone number.')) {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_release_number',
|
|
|
|
number_sid: numberSid,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
alert('Phone number released successfully!');
|
|
|
|
refreshNumbers();
|
|
|
|
} else {
|
|
|
|
alert('Error releasing number: ' + response.error);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Initialize phone numbers if on that page
|
|
|
|
if ($('#twp-numbers-list').length) {
|
|
|
|
refreshNumbers();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Queue management for workflow steps
|
|
|
|
window.loadQueues = function(button) {
|
|
|
|
var $button = $(button);
|
|
|
|
var $select = $button.prev('select.queue-select');
|
2025-08-12 09:12:54 -07:00
|
|
|
var currentValue = $select.attr('data-current') || $select.val() || '';
|
2025-08-06 15:25:47 -07:00
|
|
|
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('loadQueues - currentValue from data-current:', currentValue);
|
2025-08-06 15:25:47 -07:00
|
|
|
$button.text('Loading...').prop('disabled', true);
|
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_all_queues',
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
$button.text('Load Queues').prop('disabled', false);
|
|
|
|
|
|
|
|
if (response.success) {
|
|
|
|
var options = '<option value="">Select queue...</option>';
|
|
|
|
|
|
|
|
response.data.forEach(function(queue) {
|
2025-08-11 20:31:48 -07:00
|
|
|
var selected = queue.id == currentValue ? ' selected' : '';
|
|
|
|
options += '<option value="' + queue.id + '"' + selected + '>' + queue.queue_name + '</option>';
|
2025-08-06 15:25:47 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
$select.html(options);
|
|
|
|
} else {
|
|
|
|
alert('Error loading queues: ' + (response.data || 'Unknown error'));
|
|
|
|
}
|
|
|
|
}).fail(function() {
|
|
|
|
$button.text('Load Queues').prop('disabled', false);
|
|
|
|
alert('Failed to load queues');
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
// Load queues for a specific select element (used in IVR options)
|
|
|
|
function loadQueuesForSelect($select) {
|
2025-08-12 09:12:54 -07:00
|
|
|
var currentValue = $select.attr('data-current') || $select.val() || '';
|
|
|
|
// console.log('loadQueuesForSelect called with currentValue:', currentValue);
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_all_queues',
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Queue loading response:', response);
|
2025-08-11 20:31:48 -07:00
|
|
|
if (response.success) {
|
|
|
|
var options = '<option value="">Select queue...</option>';
|
|
|
|
|
|
|
|
response.data.forEach(function(queue) {
|
|
|
|
var selected = queue.id == currentValue ? ' selected' : '';
|
|
|
|
options += '<option value="' + queue.id + '"' + selected + '>' + queue.queue_name + '</option>';
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Added queue option:', queue.queue_name, 'ID:', queue.id, 'Selected:', selected);
|
2025-08-11 20:31:48 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
$select.html(options);
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Queue options loaded, final select value:', $select.val());
|
|
|
|
} else {
|
|
|
|
console.error('Failed to load queues:', response);
|
2025-08-11 20:31:48 -07:00
|
|
|
}
|
2025-08-12 09:12:54 -07:00
|
|
|
}).fail(function(xhr, status, error) {
|
|
|
|
console.error('Queue loading failed:', error);
|
2025-08-11 20:31:48 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-08-06 15:25:47 -07:00
|
|
|
// Voice management for workflow steps
|
2025-08-12 09:12:54 -07:00
|
|
|
window.loadWorkflowVoices = function(buttonOrSelect) {
|
|
|
|
var $button, $select;
|
|
|
|
|
|
|
|
// Check if we were passed a button or a select element
|
|
|
|
if ($(buttonOrSelect).is('button')) {
|
|
|
|
$button = $(buttonOrSelect);
|
|
|
|
$select = $button.prev('select.voice-select');
|
|
|
|
} else if ($(buttonOrSelect).is('select')) {
|
|
|
|
$select = $(buttonOrSelect);
|
|
|
|
$button = $select.next('button');
|
|
|
|
} else {
|
|
|
|
// Fallback - assume it's a button
|
|
|
|
$button = $(buttonOrSelect);
|
|
|
|
$select = $button.prev('select.voice-select');
|
|
|
|
}
|
2025-08-06 15:25:47 -07:00
|
|
|
|
2025-08-12 09:12:54 -07:00
|
|
|
// Read the current value - first check if there's a selected option with a value, then data-current attribute
|
|
|
|
var currentValue = '';
|
|
|
|
var selectedOption = $select.find('option:selected');
|
|
|
|
if (selectedOption.length && selectedOption.val()) {
|
|
|
|
currentValue = selectedOption.val();
|
|
|
|
} else {
|
|
|
|
currentValue = $select.attr('data-current') || '';
|
|
|
|
}
|
|
|
|
|
|
|
|
// console.log('loadWorkflowVoices - currentValue:', currentValue, 'from data-current:', $select.attr('data-current'));
|
|
|
|
|
|
|
|
if ($button.length) {
|
|
|
|
$button.text('Loading...').prop('disabled', true);
|
|
|
|
}
|
2025-08-06 15:25:47 -07:00
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_elevenlabs_voices',
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Voice loading response:', response);
|
|
|
|
if ($button.length) {
|
|
|
|
$button.text('Load Voices').prop('disabled', false);
|
|
|
|
}
|
2025-08-06 15:25:47 -07:00
|
|
|
|
|
|
|
if (response.success) {
|
|
|
|
var options = '<option value="">Default voice</option>';
|
|
|
|
|
|
|
|
response.data.forEach(function(voice) {
|
2025-08-12 09:12:54 -07:00
|
|
|
var selected = (voice.voice_id === currentValue) ? ' selected' : '';
|
2025-08-06 15:25:47 -07:00
|
|
|
var description = voice.labels ? Object.values(voice.labels).join(', ') : '';
|
|
|
|
var optionText = voice.name + (description ? ' (' + description + ')' : '');
|
2025-08-12 09:12:54 -07:00
|
|
|
options += '<option value="' + voice.voice_id + '" data-voice-name="' + voice.name + '"' + selected + '>' + optionText + '</option>';
|
|
|
|
|
|
|
|
if (selected) {
|
|
|
|
// console.log('Setting voice as selected:', voice.name, 'ID:', voice.voice_id);
|
|
|
|
}
|
2025-08-06 15:25:47 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
$select.html(options);
|
2025-08-12 09:12:54 -07:00
|
|
|
|
|
|
|
// If we had a current value, make sure it's selected
|
|
|
|
if (currentValue) {
|
|
|
|
$select.val(currentValue);
|
|
|
|
// Update the voice name field with the selected voice's name
|
|
|
|
var $voiceNameInput = $select.siblings('input[name="voice_name"]');
|
|
|
|
if ($voiceNameInput.length) {
|
|
|
|
var selectedVoice = $select.find('option:selected');
|
|
|
|
var voiceName = selectedVoice.data('voice-name') || selectedVoice.text() || '';
|
|
|
|
if (selectedVoice.val() === '') {
|
|
|
|
voiceName = '';
|
|
|
|
}
|
|
|
|
$voiceNameInput.val(voiceName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// console.log('Voice loaded and set to:', currentValue, '- Final select value:', $select.val());
|
2025-08-06 15:25:47 -07:00
|
|
|
} else {
|
|
|
|
var errorMessage = 'Error loading voices: ';
|
|
|
|
if (typeof response.data === 'string') {
|
|
|
|
errorMessage += response.data;
|
|
|
|
} else if (response.data && response.data.detail) {
|
|
|
|
errorMessage += response.data.detail;
|
|
|
|
} else if (response.data && response.data.error) {
|
|
|
|
errorMessage += response.data.error;
|
|
|
|
} else {
|
|
|
|
errorMessage += 'Unknown error occurred';
|
|
|
|
}
|
|
|
|
alert(errorMessage);
|
|
|
|
}
|
|
|
|
}).fail(function() {
|
2025-08-12 09:12:54 -07:00
|
|
|
if ($button.length) {
|
|
|
|
$button.text('Load Voices').prop('disabled', false);
|
|
|
|
}
|
2025-08-06 15:25:47 -07:00
|
|
|
alert('Failed to load voices. Please check your API key.');
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
// Toggle audio type options visibility
|
|
|
|
$(document).on('change', 'input[name="audio_type"]', function() {
|
|
|
|
var $container = $(this).closest('.step-config-section');
|
|
|
|
var $ttsOptions = $container.find('.tts-options');
|
|
|
|
var $audioOptions = $container.find('.audio-options');
|
|
|
|
var audioType = $(this).val();
|
|
|
|
|
|
|
|
// Hide all options first
|
|
|
|
$ttsOptions.hide();
|
|
|
|
$audioOptions.hide();
|
|
|
|
|
|
|
|
// Show the appropriate options based on selection
|
|
|
|
if (audioType === 'tts') {
|
|
|
|
$ttsOptions.show();
|
|
|
|
} else if (audioType === 'audio') {
|
|
|
|
$audioOptions.show();
|
|
|
|
}
|
|
|
|
// 'say' type doesn't need additional options
|
|
|
|
});
|
|
|
|
|
|
|
|
// Legacy support for old use_tts checkbox (in case old workflows exist)
|
2025-08-06 15:25:47 -07:00
|
|
|
$(document).on('change', 'input[name="use_tts"]', function() {
|
|
|
|
var $ttsOptions = $(this).closest('.step-config-section').find('.tts-options');
|
|
|
|
if ($(this).is(':checked')) {
|
|
|
|
$ttsOptions.show();
|
|
|
|
} else {
|
|
|
|
$ttsOptions.hide();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-08-12 09:12:54 -07:00
|
|
|
// Handle voice selection changes to update hidden voice_name field
|
|
|
|
$(document).on('change', 'select.voice-select', function() {
|
|
|
|
var $select = $(this);
|
|
|
|
var $voiceNameInput = $select.siblings('input[name="voice_name"]');
|
|
|
|
var selectedOption = $select.find('option:selected');
|
|
|
|
var voiceName = selectedOption.data('voice-name') || selectedOption.text() || '';
|
|
|
|
|
|
|
|
// If it's the default option, clear the name
|
|
|
|
if (selectedOption.val() === '') {
|
|
|
|
voiceName = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($voiceNameInput.length) {
|
|
|
|
$voiceNameInput.val(voiceName);
|
|
|
|
// console.log('Voice name updated to:', voiceName);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
// Handle IVR action changes to show/hide appropriate target inputs
|
|
|
|
$(document).on('change', '.ivr-action-select', function() {
|
|
|
|
var $option = $(this).closest('.ivr-option');
|
|
|
|
var $container = $option.find('.ivr-target-container');
|
|
|
|
var action = $(this).val();
|
|
|
|
|
2025-08-12 09:12:54 -07:00
|
|
|
// Hide and disable all target inputs first
|
|
|
|
$container.find('.target-forward, .target-queue, .target-voicemail, .target-message').hide().prop('disabled', true);
|
2025-08-11 20:31:48 -07:00
|
|
|
|
2025-08-12 09:12:54 -07:00
|
|
|
// Show and enable the appropriate input based on action
|
2025-08-11 20:31:48 -07:00
|
|
|
switch(action) {
|
|
|
|
case 'forward':
|
2025-08-12 09:12:54 -07:00
|
|
|
$container.find('.target-forward').show().prop('disabled', false);
|
2025-08-11 20:31:48 -07:00
|
|
|
break;
|
|
|
|
case 'queue':
|
2025-08-12 09:12:54 -07:00
|
|
|
$container.find('.target-queue').show().prop('disabled', false);
|
2025-08-11 20:31:48 -07:00
|
|
|
// Load queues if not already loaded
|
|
|
|
var $queueSelect = $container.find('.target-queue');
|
|
|
|
if ($queueSelect.find('option').length <= 1) {
|
|
|
|
loadQueuesForSelect($queueSelect);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'voicemail':
|
2025-08-12 09:12:54 -07:00
|
|
|
$container.find('.target-voicemail').show().prop('disabled', false);
|
2025-08-11 20:31:48 -07:00
|
|
|
break;
|
|
|
|
case 'message':
|
2025-08-12 09:12:54 -07:00
|
|
|
$container.find('.target-message').show().prop('disabled', false);
|
2025-08-11 20:31:48 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-08-06 15:25:47 -07:00
|
|
|
// Auto-load voices when step config modal opens if API key exists
|
|
|
|
$(document).on('click', '.step-btn', function() {
|
|
|
|
setTimeout(function() {
|
|
|
|
// Check if API key exists in main settings
|
|
|
|
if (twp_ajax.has_elevenlabs_key) {
|
|
|
|
$('.voice-select').each(function() {
|
|
|
|
if ($(this).find('option').length <= 1) {
|
|
|
|
var button = $(this).next('button');
|
|
|
|
if (button.length) {
|
|
|
|
loadWorkflowVoices(button[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}, 500);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Voicemail Management
|
|
|
|
window.playVoicemail = function(voicemailId, recordingUrl) {
|
2025-08-11 20:31:48 -07:00
|
|
|
// First load voicemail details
|
2025-08-06 15:25:47 -07:00
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_voicemail',
|
|
|
|
voicemail_id: voicemailId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
var voicemail = response.data;
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
// Update modal with voicemail details
|
|
|
|
$('#voicemail-from').text(voicemail.from_number || 'Unknown');
|
|
|
|
$('#voicemail-date').text(voicemail.created_at || '');
|
|
|
|
$('#voicemail-duration').text(voicemail.duration ? voicemail.duration + ' seconds' : 'Unknown');
|
|
|
|
$('#voicemail-player-modal').data('voicemail-id', voicemailId);
|
|
|
|
|
|
|
|
// Show modal using ThickBox
|
|
|
|
tb_show('Voicemail Player', '#TB_inline?inlineId=voicemail-player-modal&width=600&height=500');
|
|
|
|
|
|
|
|
// Now fetch the audio data
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_voicemail_audio',
|
|
|
|
voicemail_id: voicemailId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(audioResponse) {
|
|
|
|
if (audioResponse.success) {
|
|
|
|
var audio = document.getElementById('voicemail-audio');
|
|
|
|
if (audio) {
|
|
|
|
// Use the data URL directly
|
|
|
|
audio.src = audioResponse.data.audio_url;
|
|
|
|
|
|
|
|
// Show transcription
|
|
|
|
showVoicemail(voicemailId, audioResponse.data.audio_url, voicemail.transcription);
|
|
|
|
|
|
|
|
// Play after loading
|
|
|
|
audio.play().catch(function(error) {
|
2025-08-12 09:12:54 -07:00
|
|
|
// console.log('Audio play error:', error);
|
2025-08-11 20:31:48 -07:00
|
|
|
// If data URL fails, try direct Twilio URL as fallback
|
|
|
|
if (voicemail.recording_url) {
|
|
|
|
audio.src = voicemail.recording_url + '.mp3';
|
|
|
|
audio.play().catch(function(e) {
|
|
|
|
alert('Unable to play audio. The recording may require authentication.');
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
alert('Error loading audio: ' + (audioResponse.data || 'Unknown error'));
|
|
|
|
console.error('Audio load error:', audioResponse);
|
|
|
|
}
|
|
|
|
}).fail(function(xhr, status, error) {
|
|
|
|
alert('Failed to load audio: ' + error);
|
|
|
|
console.error('AJAX error:', xhr.responseText);
|
|
|
|
});
|
2025-08-06 15:25:47 -07:00
|
|
|
} else {
|
2025-08-11 20:31:48 -07:00
|
|
|
alert('Error loading voicemail: ' + (response.data || 'Unknown error'));
|
2025-08-06 15:25:47 -07:00
|
|
|
}
|
2025-08-11 20:31:48 -07:00
|
|
|
}).fail(function() {
|
|
|
|
alert('Failed to load voicemail details');
|
2025-08-06 15:25:47 -07:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
window.viewVoicemail = function(voicemailId) {
|
|
|
|
// Just use playVoicemail without auto-play
|
|
|
|
playVoicemail(voicemailId, '');
|
|
|
|
};
|
|
|
|
|
2025-08-06 15:25:47 -07:00
|
|
|
function showVoicemail(voicemailId, recordingUrl, transcription) {
|
2025-08-11 20:31:48 -07:00
|
|
|
// Set the audio source - already set by playVoicemail
|
2025-08-06 15:25:47 -07:00
|
|
|
|
|
|
|
// Set transcription text
|
|
|
|
var transcriptionDiv = document.getElementById('voicemail-transcription-text');
|
|
|
|
if (transcriptionDiv) {
|
|
|
|
if (transcription && transcription !== 'Transcription pending...' && transcription !== 'Transcription failed') {
|
|
|
|
transcriptionDiv.innerHTML = '<p>' + transcription + '</p>';
|
|
|
|
|
|
|
|
// Hide the generate transcription button if we have a transcription
|
|
|
|
var transcribeBtn = document.getElementById('transcribe-btn');
|
|
|
|
if (transcribeBtn) {
|
|
|
|
transcribeBtn.style.display = 'none';
|
|
|
|
}
|
|
|
|
} else if (transcription === 'Transcription failed') {
|
|
|
|
transcriptionDiv.innerHTML = '<p class="error">Transcription failed. Please try again.</p>';
|
|
|
|
} else {
|
2025-08-11 20:31:48 -07:00
|
|
|
transcriptionDiv.innerHTML = '<em>No transcription available.</em>';
|
2025-08-06 15:25:47 -07:00
|
|
|
|
|
|
|
// Show the generate transcription button
|
|
|
|
var transcribeBtn = document.getElementById('transcribe-btn');
|
|
|
|
if (transcribeBtn) {
|
|
|
|
transcribeBtn.style.display = 'inline-block';
|
2025-08-11 20:31:48 -07:00
|
|
|
transcribeBtn.setAttribute('data-voicemail-id', voicemailId);
|
2025-08-06 15:25:47 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store current voicemail ID for actions
|
|
|
|
window.currentVoicemailId = voicemailId;
|
|
|
|
window.currentRecordingUrl = recordingUrl;
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
// Modal is already shown by playVoicemail function
|
2025-08-06 15:25:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
window.closeVoicemailModal = function() {
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_remove();
|
2025-08-06 15:25:47 -07:00
|
|
|
|
|
|
|
// Stop audio playback
|
|
|
|
var audio = document.getElementById('voicemail-audio');
|
|
|
|
if (audio) {
|
|
|
|
audio.pause();
|
|
|
|
audio.currentTime = 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
window.downloadVoicemail = function() {
|
|
|
|
if (window.currentRecordingUrl) {
|
|
|
|
window.open(window.currentRecordingUrl, '_blank');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
window.deleteVoicemail = function() {
|
|
|
|
if (confirm('Are you sure you want to delete this voicemail?')) {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_delete_voicemail',
|
|
|
|
voicemail_id: window.currentVoicemailId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
closeVoicemailModal();
|
|
|
|
location.reload();
|
|
|
|
} else {
|
|
|
|
alert('Error deleting voicemail');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
window.deleteVoicemailConfirm = function(voicemailId) {
|
|
|
|
if (confirm('Are you sure you want to delete this voicemail?')) {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_delete_voicemail',
|
|
|
|
voicemail_id: voicemailId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
location.reload();
|
|
|
|
} else {
|
|
|
|
alert('Error deleting voicemail');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
window.transcribeVoicemail = function() {
|
2025-08-11 20:31:48 -07:00
|
|
|
var voicemailId = $('#transcribe-btn').data('voicemail-id') || window.currentVoicemailId;
|
|
|
|
|
|
|
|
if (!voicemailId) {
|
|
|
|
alert('No voicemail selected');
|
|
|
|
return;
|
2025-08-06 15:25:47 -07:00
|
|
|
}
|
2025-08-11 20:31:48 -07:00
|
|
|
|
|
|
|
$('#transcribe-btn').text('Generating...').prop('disabled', true);
|
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_transcribe_voicemail',
|
|
|
|
voicemail_id: voicemailId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
$('#voicemail-transcription-text').html('<p>' + response.data.transcription + '</p>');
|
|
|
|
$('#transcribe-btn').hide();
|
|
|
|
} else {
|
|
|
|
alert('Error generating transcription');
|
|
|
|
$('#transcribe-btn').text('Generate Transcription').prop('disabled', false);
|
|
|
|
}
|
|
|
|
});
|
2025-08-06 15:25:47 -07:00
|
|
|
};
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
window.filterVoicemails = function() {
|
|
|
|
// TODO: Implement filtering
|
|
|
|
alert('Filtering coming soon');
|
|
|
|
};
|
|
|
|
|
|
|
|
window.exportVoicemails = function() {
|
|
|
|
// TODO: Implement export
|
|
|
|
alert('Export coming soon');
|
|
|
|
};
|
|
|
|
|
|
|
|
// Removed modal click handler - ThickBox handles closing
|
2025-08-06 15:25:47 -07:00
|
|
|
|
|
|
|
// Agent Group Management Functions
|
|
|
|
window.openGroupModal = function() {
|
|
|
|
$('#group-form')[0].reset();
|
|
|
|
$('#group-id').val('');
|
|
|
|
$('#group-modal-title').text('Add New Group');
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_show('Add New Group', '#TB_inline?inlineId=group-modal&width=600&height=500');
|
2025-08-06 15:25:47 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
window.closeGroupModal = function() {
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_remove();
|
2025-08-06 15:25:47 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
window.saveGroup = function() {
|
|
|
|
var formData = $('#group-form').serialize();
|
|
|
|
formData += '&action=twp_save_group&nonce=' + twp_ajax.nonce;
|
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, formData, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
closeGroupModal();
|
|
|
|
location.reload();
|
|
|
|
} else {
|
|
|
|
alert('Error saving group: ' + (response.data || 'Unknown error'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
window.editGroup = function(groupId) {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_group',
|
|
|
|
group_id: groupId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
var group = response.data;
|
|
|
|
$('#group-modal-title').text('Edit Group');
|
|
|
|
$('#group-id').val(group.id);
|
|
|
|
$('[name="group_name"]').val(group.group_name);
|
|
|
|
$('[name="description"]').val(group.description);
|
|
|
|
$('[name="ring_strategy"]').val(group.ring_strategy);
|
|
|
|
$('[name="timeout_seconds"]').val(group.timeout_seconds);
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_show('Edit Group: ' + group.group_name, '#TB_inline?inlineId=group-modal&width=600&height=500');
|
2025-08-06 15:25:47 -07:00
|
|
|
} else {
|
|
|
|
alert('Error loading group: ' + (response.data || 'Unknown error'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
window.deleteGroup = function(groupId) {
|
|
|
|
if (confirm('Are you sure you want to delete this group? All members will be removed.')) {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_delete_group',
|
|
|
|
group_id: groupId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
location.reload();
|
|
|
|
} else {
|
|
|
|
alert('Error deleting group: ' + (response.data || 'Unknown error'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
window.manageGroupMembers = function(groupId) {
|
|
|
|
$('#current-group-id').val(groupId);
|
|
|
|
loadGroupMembers(groupId);
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_show('Manage Group Members', '#TB_inline?inlineId=members-modal&width=700&height=600');
|
2025-08-06 15:25:47 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
window.closeMembersModal = function() {
|
2025-08-11 20:31:48 -07:00
|
|
|
tb_remove();
|
2025-08-06 15:25:47 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
function loadGroupMembers(groupId) {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_group_members',
|
|
|
|
group_id: groupId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
var html = '';
|
|
|
|
response.data.forEach(function(member) {
|
|
|
|
html += '<tr>';
|
|
|
|
html += '<td>' + member.display_name + '</td>';
|
|
|
|
html += '<td>' + (member.phone_number || 'No phone set') + '</td>';
|
|
|
|
html += '<td>' + member.priority + '</td>';
|
|
|
|
html += '<td>' + (member.is_active ? 'Active' : 'Inactive') + '</td>';
|
|
|
|
html += '<td><button class="button" onclick="removeGroupMember(' + member.user_id + ')">Remove</button></td>';
|
|
|
|
html += '</tr>';
|
|
|
|
});
|
|
|
|
|
|
|
|
if (html === '') {
|
|
|
|
html = '<tr><td colspan="5">No members in this group</td></tr>';
|
|
|
|
}
|
|
|
|
|
|
|
|
$('#group-members-list').html(html);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
window.addGroupMember = function() {
|
|
|
|
var groupId = $('#current-group-id').val();
|
|
|
|
var userId = $('#add-member-select').val();
|
|
|
|
var priority = $('#add-member-priority').val() || 0;
|
|
|
|
|
|
|
|
if (!userId) {
|
|
|
|
alert('Please select a user to add');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_add_group_member',
|
|
|
|
group_id: groupId,
|
|
|
|
user_id: userId,
|
|
|
|
priority: priority,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
$('#add-member-select').val('');
|
|
|
|
$('#add-member-priority').val(0);
|
|
|
|
loadGroupMembers(groupId);
|
|
|
|
} else {
|
|
|
|
alert('Error adding member: ' + (response.data || 'Unknown error'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
window.removeGroupMember = function(userId) {
|
|
|
|
var groupId = $('#current-group-id').val();
|
|
|
|
|
|
|
|
if (confirm('Remove this member from the group?')) {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_remove_group_member',
|
|
|
|
group_id: groupId,
|
|
|
|
user_id: userId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
loadGroupMembers(groupId);
|
|
|
|
} else {
|
|
|
|
alert('Error removing member: ' + (response.data || 'Unknown error'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Agent Queue Functions
|
|
|
|
window.updateAgentStatus = function(status) {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_set_agent_status',
|
|
|
|
status: status,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (!response.success) {
|
|
|
|
alert('Error updating status: ' + (response.data || 'Unknown error'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
window.acceptCall = function(callId) {
|
|
|
|
var button = $('[onclick="acceptCall(' + callId + ')"]');
|
|
|
|
button.prop('disabled', true).text('Accepting...');
|
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_accept_call',
|
|
|
|
call_id: callId,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
2025-08-11 20:31:48 -07:00
|
|
|
// Show success modal instead of basic alert
|
|
|
|
showCallAcceptedModal();
|
2025-08-06 15:25:47 -07:00
|
|
|
refreshWaitingCalls();
|
|
|
|
} else {
|
2025-08-11 20:31:48 -07:00
|
|
|
// Show error modal
|
|
|
|
showErrorModal('Error accepting call: ' + response.data);
|
2025-08-06 15:25:47 -07:00
|
|
|
button.prop('disabled', false).text('Accept');
|
|
|
|
}
|
|
|
|
}).fail(function() {
|
2025-08-11 20:31:48 -07:00
|
|
|
showErrorModal('Failed to accept call. Please try again.');
|
2025-08-06 15:25:47 -07:00
|
|
|
button.prop('disabled', false).text('Accept');
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2025-08-11 20:31:48 -07:00
|
|
|
// Modal functions using WordPress built-in modal system
|
|
|
|
window.showCallAcceptedModal = function() {
|
|
|
|
// Create WordPress-style modal HTML
|
|
|
|
var modalHtml = `
|
|
|
|
<div id="twp-call-accepted-modal" style="display: none;">
|
|
|
|
<div class="twp-wp-modal-content">
|
|
|
|
<div class="twp-wp-modal-header">
|
|
|
|
<h2>📞 Call Accepted!</h2>
|
|
|
|
</div>
|
|
|
|
<div class="twp-wp-modal-body">
|
|
|
|
<p>✅ <strong>Call has been assigned to you successfully.</strong></p>
|
|
|
|
<p>📱 You should receive the call on your phone within the next few seconds.</p>
|
|
|
|
<p>🔗 The call will connect you directly to the customer.</p>
|
|
|
|
</div>
|
|
|
|
<div class="twp-wp-modal-footer">
|
|
|
|
<button class="button button-primary" onclick="tb_remove();">Got it!</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
|
|
|
|
// Add modal HTML to body if not already there
|
|
|
|
if (!document.getElementById('twp-call-accepted-modal')) {
|
|
|
|
$('body').append(modalHtml);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use WordPress ThickBox
|
|
|
|
tb_show('Call Accepted', '#TB_inline?inlineId=twp-call-accepted-modal&width=400&height=250');
|
|
|
|
|
|
|
|
// Auto close after 5 seconds
|
|
|
|
setTimeout(function() {
|
|
|
|
tb_remove();
|
|
|
|
}, 5000);
|
|
|
|
};
|
|
|
|
|
|
|
|
window.showErrorModal = function(message) {
|
|
|
|
// Create WordPress-style error modal HTML
|
|
|
|
var modalHtml = `
|
|
|
|
<div id="twp-error-modal" style="display: none;">
|
|
|
|
<div class="twp-wp-modal-content">
|
|
|
|
<div class="twp-wp-modal-header">
|
|
|
|
<h2>❌ Error</h2>
|
|
|
|
</div>
|
|
|
|
<div class="twp-wp-modal-body">
|
|
|
|
<p>${message}</p>
|
|
|
|
</div>
|
|
|
|
<div class="twp-wp-modal-footer">
|
|
|
|
<button class="button" onclick="tb_remove();">OK</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
|
|
|
|
// Remove existing error modal
|
|
|
|
$('#twp-error-modal').remove();
|
|
|
|
|
|
|
|
// Add modal HTML to body
|
|
|
|
$('body').append(modalHtml);
|
|
|
|
|
|
|
|
// Use WordPress ThickBox
|
|
|
|
tb_show('Error', '#TB_inline?inlineId=twp-error-modal&width=400&height=200');
|
|
|
|
};
|
|
|
|
|
|
|
|
// View call details function for Call Log page
|
|
|
|
window.viewCallDetails = function(callSid) {
|
|
|
|
// Fetch call details via AJAX
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_call_details',
|
|
|
|
call_sid: callSid,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
var call = response.data;
|
|
|
|
|
|
|
|
// Build detailed view HTML
|
|
|
|
var detailsHtml = `
|
|
|
|
<div class="call-details-content">
|
|
|
|
<h3>Call Information</h3>
|
|
|
|
<table class="widefat">
|
|
|
|
<tr><th>Call SID:</th><td>${call.call_sid || 'N/A'}</td></tr>
|
|
|
|
<tr><th>From:</th><td>${call.from_number || 'N/A'}</td></tr>
|
|
|
|
<tr><th>To:</th><td>${call.to_number || 'N/A'}</td></tr>
|
|
|
|
<tr><th>Status:</th><td>${call.status || 'N/A'}</td></tr>
|
|
|
|
<tr><th>Duration:</th><td>${call.duration ? call.duration + ' seconds' : 'N/A'}</td></tr>
|
|
|
|
<tr><th>Workflow:</th><td>${call.workflow_name || 'N/A'}</td></tr>
|
|
|
|
<tr><th>Queue Time:</th><td>${call.queue_time ? call.queue_time + ' seconds' : 'N/A'}</td></tr>
|
|
|
|
<tr><th>Created:</th><td>${call.created_at || 'N/A'}</td></tr>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
${call.actions_taken ? `
|
|
|
|
<h3>Actions Taken</h3>
|
|
|
|
<div class="actions-details">
|
|
|
|
<pre>${call.actions_taken}</pre>
|
|
|
|
</div>
|
|
|
|
` : ''}
|
|
|
|
|
|
|
|
${call.recording_url ? `
|
|
|
|
<h3>Recording</h3>
|
|
|
|
<audio controls>
|
|
|
|
<source src="${call.recording_url}" type="audio/mpeg">
|
|
|
|
Your browser does not support the audio element.
|
|
|
|
</audio>
|
|
|
|
` : ''}
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
|
|
|
|
// Show in WordPress modal
|
|
|
|
showCallDetailsModal(detailsHtml);
|
|
|
|
} else {
|
|
|
|
showErrorModal('Failed to load call details: ' + (response.data || 'Unknown error'));
|
|
|
|
}
|
|
|
|
}).fail(function() {
|
|
|
|
showErrorModal('Failed to load call details. Please try again.');
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// Show call details modal
|
|
|
|
function showCallDetailsModal(content) {
|
|
|
|
// Remove existing modal if present
|
|
|
|
$('#call-details-modal').remove();
|
|
|
|
|
|
|
|
// Create WordPress-style modal HTML
|
|
|
|
var modalHtml = `
|
|
|
|
<div id="call-details-modal" style="display: none;">
|
|
|
|
<div class="twp-wp-modal-content">
|
|
|
|
<div class="twp-wp-modal-header">
|
|
|
|
<h2>📞 Call Details</h2>
|
|
|
|
</div>
|
|
|
|
<div class="twp-wp-modal-body">
|
|
|
|
${content}
|
|
|
|
</div>
|
|
|
|
<div class="twp-wp-modal-footer">
|
|
|
|
<button class="button" onclick="tb_remove();">Close</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
|
|
|
|
// Add modal HTML to body
|
|
|
|
$('body').append(modalHtml);
|
|
|
|
|
|
|
|
// Use WordPress ThickBox
|
|
|
|
tb_show('Call Details', '#TB_inline?inlineId=call-details-modal&width=700&height=500');
|
|
|
|
}
|
|
|
|
|
2025-08-06 15:25:47 -07:00
|
|
|
window.refreshWaitingCalls = function() {
|
|
|
|
if (!$('#waiting-calls-list').length) return;
|
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_waiting_calls',
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
var html = '';
|
|
|
|
|
|
|
|
if (response.data.length === 0) {
|
|
|
|
html = '<tr><td colspan="5">No calls waiting</td></tr>';
|
|
|
|
} else {
|
|
|
|
response.data.forEach(function(call) {
|
|
|
|
var waitMinutes = Math.floor(call.wait_seconds / 60);
|
|
|
|
var waitSeconds = call.wait_seconds % 60;
|
|
|
|
var waitTime = waitMinutes > 0 ? waitMinutes + 'm ' + waitSeconds + 's' : waitSeconds + 's';
|
|
|
|
|
|
|
|
html += '<tr>';
|
|
|
|
html += '<td>' + call.position + '</td>';
|
|
|
|
html += '<td>' + call.queue_name + '</td>';
|
|
|
|
html += '<td>' + call.from_number + '</td>';
|
|
|
|
html += '<td>' + waitTime + '</td>';
|
|
|
|
html += '<td><button class="accept-btn" onclick="acceptCall(' + call.id + ')">Accept</button></td>';
|
|
|
|
html += '</tr>';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
$('#waiting-calls-list').html(html);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// Auto-refresh waiting calls every 5 seconds on agent queue page
|
|
|
|
if ($('#waiting-calls-list').length) {
|
|
|
|
setInterval(refreshWaitingCalls, 5000);
|
|
|
|
refreshWaitingCalls(); // Initial load
|
|
|
|
}
|
|
|
|
|
|
|
|
// Click-to-Call Functions
|
|
|
|
window.initiateCall = function() {
|
|
|
|
var toNumber = prompt('Enter number to call (e.g., +1234567890):');
|
|
|
|
|
|
|
|
if (toNumber) {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_initiate_outbound_call',
|
|
|
|
to_number: toNumber,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
alert('Call initiated! You should receive a call on your phone shortly, then the call will connect to ' + toNumber);
|
|
|
|
} else {
|
|
|
|
alert('Error initiating call: ' + (response.data || 'Unknown error'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Callback Management Functions
|
|
|
|
window.requestCallback = function() {
|
|
|
|
var phoneNumber = prompt('Enter phone number for callback (e.g., +1234567890):');
|
|
|
|
|
|
|
|
if (phoneNumber) {
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_request_callback',
|
|
|
|
phone_number: phoneNumber,
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
alert('Callback requested successfully! The customer will be called back soon.');
|
|
|
|
refreshCallbacks();
|
|
|
|
} else {
|
|
|
|
alert('Error requesting callback: ' + (response.data.message || 'Unknown error'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
window.refreshCallbacks = function() {
|
|
|
|
if (!$('#callbacks-list').length) return;
|
|
|
|
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
|
|
action: 'twp_get_callbacks',
|
|
|
|
nonce: twp_ajax.nonce
|
|
|
|
}, function(response) {
|
|
|
|
if (response.success) {
|
|
|
|
var html = '';
|
|
|
|
var callbacks = response.data.callbacks;
|
|
|
|
var stats = response.data.stats;
|
|
|
|
|
|
|
|
if (callbacks.length === 0) {
|
|
|
|
html = '<tr><td colspan="6">No pending callbacks</td></tr>';
|
|
|
|
} else {
|
|
|
|
callbacks.forEach(function(callback) {
|
|
|
|
var statusClass = 'status-' + callback.status;
|
|
|
|
var queueName = callback.queue_name || 'Direct';
|
|
|
|
|
|
|
|
html += '<tr>';
|
|
|
|
html += '<td>' + callback.phone_number + '</td>';
|
|
|
|
html += '<td>' + queueName + '</td>';
|
|
|
|
html += '<td>' + callback.requested_at + '</td>';
|
|
|
|
html += '<td>' + callback.wait_minutes + ' min</td>';
|
|
|
|
html += '<td><span class="' + statusClass + '">' + callback.status + '</span></td>';
|
|
|
|
html += '<td>' + callback.attempts + '</td>';
|
|
|
|
html += '</tr>';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
$('#callbacks-list').html(html);
|
|
|
|
|
|
|
|
// Update stats if available
|
|
|
|
if (stats) {
|
|
|
|
$('#callback-stats-total').text(stats.total_requests);
|
|
|
|
$('#callback-stats-completed').text(stats.completed);
|
|
|
|
$('#callback-stats-pending').text(stats.pending);
|
|
|
|
$('#callback-stats-success-rate').text(stats.success_rate + '%');
|
|
|
|
if (stats.avg_completion_time) {
|
|
|
|
$('#callback-stats-avg-time').text(Math.round(stats.avg_completion_time) + ' min');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// Auto-refresh callbacks every 10 seconds on callbacks page
|
|
|
|
if ($('#callbacks-list').length) {
|
|
|
|
setInterval(refreshCallbacks, 10000);
|
|
|
|
refreshCallbacks(); // Initial load
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize on load
|
|
|
|
updateDashboardStats();
|
|
|
|
});
|