Files
twilio-wp-plugin/assets/js/admin.js

2436 lines
107 KiB
JavaScript
Raw Normal View History

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();
});