Fixed critical bug where forward tasks in workflows would immediately disconnect calls instead of forwarding them properly. Changes: - Fixed append_twiml_element function to properly handle Dial elements with child Number elements - Enhanced create_forward_twiml to extract numbers from nested data structures - Added comprehensive error handling for missing forward numbers - Added detailed logging throughout workflow execution for debugging - Set default timeout of 30 seconds for forward operations The issue was caused by the Dial element being converted to string which lost all child Number elements, resulting in an empty dial that would immediately disconnect. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
2657 lines
118 KiB
JavaScript
2657 lines
118 KiB
JavaScript
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();
|
|
|
|
tb_show('Add New Schedule', '#TB_inline?inlineId=schedule-modal&width=650&height=600');
|
|
};
|
|
|
|
window.closeScheduleModal = function() {
|
|
tb_remove();
|
|
};
|
|
|
|
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);
|
|
$('[name="holiday_dates"]').val(schedule.holiday_dates || '');
|
|
$('[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]);
|
|
}
|
|
|
|
tb_show('Edit Schedule: ' + schedule.schedule_name, '#TB_inline?inlineId=schedule-modal&width=650&height=600');
|
|
} 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();
|
|
|
|
// 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));
|
|
}
|
|
});
|
|
|
|
var formDataString = filteredData.join('&');
|
|
|
|
// Collect days of week checkboxes manually
|
|
var daysOfWeek = [];
|
|
$('[name="days_of_week[]"]:checked').each(function() {
|
|
daysOfWeek.push($(this).val());
|
|
});
|
|
|
|
// Add days to form data if any selected
|
|
if (daysOfWeek.length > 0) {
|
|
formDataString += '&days_of_week[]=' + daysOfWeek.join('&days_of_week[]=');
|
|
}
|
|
|
|
// Add after-hours fields
|
|
var afterHoursAction = $('select[name="after_hours_action"]').val();
|
|
if (afterHoursAction) {
|
|
formDataString += '&after_hours_action=' + encodeURIComponent(afterHoursAction);
|
|
|
|
if (afterHoursAction === 'workflow') {
|
|
var afterHoursWorkflow = $('select[name="after_hours_workflow_id"]').val();
|
|
if (afterHoursWorkflow) {
|
|
formDataString += '&after_hours_workflow_id=' + encodeURIComponent(afterHoursWorkflow);
|
|
}
|
|
} else if (afterHoursAction === 'forward') {
|
|
var afterHoursForward = $('input[name="after_hours_forward_number"]').val();
|
|
if (afterHoursForward) {
|
|
formDataString += '&after_hours_forward_number=' + encodeURIComponent(afterHoursForward);
|
|
}
|
|
}
|
|
}
|
|
|
|
formDataString += '&action=twp_save_schedule&nonce=' + twp_ajax.nonce;
|
|
|
|
// console.log('Submitting schedule form data:', formDataString);
|
|
|
|
$.post(twp_ajax.ajax_url, formDataString, function(response) {
|
|
// console.log('Schedule save response:', response);
|
|
if (response.success) {
|
|
closeScheduleModal();
|
|
location.reload();
|
|
} else {
|
|
alert('Error saving schedule: ' + (response.data || 'Unknown error'));
|
|
}
|
|
}).fail(function(xhr, status, error) {
|
|
console.error('Schedule save failed:', xhr, status, error);
|
|
alert('Network error saving schedule: ' + error);
|
|
});
|
|
});
|
|
|
|
// 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();
|
|
tb_show('Create New Workflow', '#TB_inline?inlineId=workflow-builder&width=900&height=700');
|
|
};
|
|
|
|
window.closeWorkflowBuilder = function() {
|
|
tb_remove();
|
|
};
|
|
|
|
window.closeStepConfigModal = function() {
|
|
// Hide the step config modal and remove show class
|
|
$('#step-config-modal').hide().removeClass('show');
|
|
};
|
|
|
|
function loadPhoneNumbers(callback) {
|
|
$.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>';
|
|
});
|
|
|
|
// Update all workflow phone selects (legacy and new)
|
|
$('#workflow-phone, .workflow-phone-select').html(options);
|
|
|
|
// Call the callback if provided
|
|
if (typeof callback === 'function') {
|
|
callback();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadWorkflowPhoneNumbers(workflowId, callback) {
|
|
$.post(twp_ajax.ajax_url, {
|
|
action: 'twp_get_workflow_phone_numbers',
|
|
workflow_id: workflowId,
|
|
nonce: twp_ajax.nonce
|
|
}, function(response) {
|
|
if (response.success) {
|
|
callback(response.data);
|
|
} else {
|
|
callback([]);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 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':
|
|
return { queue_id: '', announce_message: '' };
|
|
case 'voicemail':
|
|
return { greeting_message: '', max_length: 120 };
|
|
case 'schedule_check':
|
|
return { schedule_id: '', after_hours_steps: [] };
|
|
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;
|
|
|
|
// console.log('Opening step config modal for step ID:', stepId, 'type:', stepType);
|
|
|
|
$('#step-id').val(stepId);
|
|
$('#step-type').val(stepType);
|
|
$('#step-config-title').text('Configure ' + getStepTypeName(stepType) + ' Step');
|
|
|
|
var configContent = generateStepConfigForm(stepType, step.data || {});
|
|
$('#step-config-content').html(configContent);
|
|
|
|
// 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');
|
|
|
|
// 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);
|
|
|
|
// If it's a schedule step, load the schedules
|
|
if (stepType === 'schedule_check') {
|
|
// console.log('Setting up schedule step - current data:', step.data);
|
|
setTimeout(function() {
|
|
// console.log('About to load schedules, current select element:', $('#schedule-select'));
|
|
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') {
|
|
// console.log('Setting up IVR step - current data:', step.data);
|
|
setTimeout(function() {
|
|
// console.log('Loading queues for IVR step...');
|
|
$('#step-config-modal .target-queue').each(function() {
|
|
var $select = $(this);
|
|
var currentValue = $select.data('current');
|
|
// console.log('Loading queue for select with current value:', currentValue);
|
|
loadQueuesForSelect($select);
|
|
});
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
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>';
|
|
|
|
// 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;') + '">';
|
|
html += '<label>Voice:</label>';
|
|
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
|
html += '<option value="">Default voice</option>';
|
|
// 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>';
|
|
}
|
|
html += '</select>';
|
|
html += '<input type="hidden" name="voice_name" value="' + (data.voice_name || '') + '">';
|
|
html += '<button type="button" class="button button-small" onclick="loadWorkflowVoices(this)">Load Voices</button>';
|
|
html += '<button type="button" class="button button-small" onclick="refreshWorkflowVoices(this)" title="Refresh voices">🔄</button>';
|
|
html += '</div>';
|
|
|
|
// 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>';
|
|
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) + '">';
|
|
|
|
// 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;') + '">';
|
|
html += '<label>Voice:</label>';
|
|
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
|
html += '<option value="">Default voice</option>';
|
|
// 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>';
|
|
}
|
|
html += '</select>';
|
|
html += '<input type="hidden" name="voice_name" value="' + (data.voice_name || '') + '">';
|
|
html += '<button type="button" class="button button-small" onclick="loadWorkflowVoices(this)">Load Voices</button>';
|
|
html += '<button type="button" class="button button-small" onclick="refreshWorkflowVoices(this)" title="Refresh voices">🔄</button>';
|
|
html += '</div>';
|
|
|
|
// 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>';
|
|
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>';
|
|
html += '<select name="queue_id" class="queue-select" data-current="' + (data.queue_id || '') + '"><option value="">Select queue...</option></select>';
|
|
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>';
|
|
|
|
// 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;') + '">';
|
|
html += '<label>Voice:</label>';
|
|
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
|
html += '<option value="">Default voice</option>';
|
|
// 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>';
|
|
}
|
|
html += '</select>';
|
|
html += '<input type="hidden" name="voice_name" value="' + (data.voice_name || '') + '">';
|
|
html += '<button type="button" class="button button-small" onclick="loadWorkflowVoices(this)">Load Voices</button>';
|
|
html += '<button type="button" class="button button-small" onclick="refreshWorkflowVoices(this)" title="Refresh voices">🔄</button>';
|
|
html += '</div>';
|
|
|
|
// 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>';
|
|
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) + '">';
|
|
|
|
// 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;') + '">';
|
|
html += '<label>Voice:</label>';
|
|
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
|
html += '<option value="">Default voice</option>';
|
|
// 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>';
|
|
}
|
|
html += '</select>';
|
|
html += '<input type="hidden" name="voice_name" value="' + (data.voice_name || '') + '">';
|
|
html += '<button type="button" class="button button-small" onclick="loadWorkflowVoices(this)">Load Voices</button>';
|
|
html += '<button type="button" class="button button-small" onclick="refreshWorkflowVoices(this)" title="Refresh voices">🔄</button>';
|
|
html += '</div>';
|
|
|
|
// 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>';
|
|
html += '</div>';
|
|
break;
|
|
|
|
case 'schedule_check':
|
|
// console.log('Generating schedule_check form with data:', data);
|
|
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>';
|
|
// console.log('Schedule ID being set:', (data.schedule_id || ''));
|
|
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">';
|
|
// console.log('Generating after-hours steps HTML, data:', data.after_hours_steps);
|
|
if (data.after_hours_steps && data.after_hours_steps.length > 0) {
|
|
// console.log('Found', data.after_hours_steps.length, 'after-hours steps');
|
|
data.after_hours_steps.forEach(function(step, index) {
|
|
// console.log('Generating HTML for after-hours step', index, ':', step);
|
|
html += generateAfterHoursStepHtml(step, index);
|
|
});
|
|
} else {
|
|
// console.log('No after-hours steps found');
|
|
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;
|
|
|
|
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 || '') + '">';
|
|
html += '<select name="action[]" class="ivr-action-select">';
|
|
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>';
|
|
|
|
// 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 || '') : '') +
|
|
'" style="display: ' + (option.action === 'forward' || !option.action ? 'block' : 'none') + ';" ' +
|
|
(option.action !== 'forward' && option.action ? 'disabled' : '') + '>';
|
|
|
|
// Queue action - queue dropdown
|
|
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' : '') + '>';
|
|
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 || '') : '') +
|
|
'" style="display: ' + (option.action === 'voicemail' ? 'block' : 'none') + ';" ' +
|
|
(option.action !== 'voicemail' ? 'disabled' : '') + '>';
|
|
|
|
// Message action - message input
|
|
html += '<input type="text" name="target[]" class="target-message" placeholder="Message Text" value="' +
|
|
(option.action === 'message' ? (option.message || option.target || '') : '') +
|
|
'" style="display: ' + (option.action === 'message' ? 'block' : 'none') + ';" ' +
|
|
(option.action !== 'message' ? 'disabled' : '') + '>';
|
|
|
|
html += '</div>';
|
|
html += '<button type="button" class="button button-small remove-ivr-option" onclick="removeIvrOption(this)">Remove</button>';
|
|
html += '</div>';
|
|
return html;
|
|
}
|
|
|
|
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() {
|
|
// console.log('Loading schedules for step modal');
|
|
$.post(twp_ajax.ajax_url, {
|
|
action: 'twp_get_schedules',
|
|
nonce: twp_ajax.nonce
|
|
}, function(response) {
|
|
// console.log('Schedule response:', response);
|
|
if (response.success) {
|
|
var $select = $('#schedule-select');
|
|
var currentScheduleId = $select.data('current') || $select.val();
|
|
// console.log('Current schedule ID to select:', currentScheduleId);
|
|
|
|
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>';
|
|
// console.log('Added schedule option:', schedule.schedule_name, 'ID:', schedule.id, 'Selected:', selected);
|
|
});
|
|
|
|
$select.html(options);
|
|
|
|
// Ensure the current value is selected after DOM update
|
|
setTimeout(function() {
|
|
if (currentScheduleId) {
|
|
$select.val(currentScheduleId);
|
|
// console.log('Set schedule select value to:', currentScheduleId, 'Current value:', $select.val());
|
|
|
|
// 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>');
|
|
});
|
|
};
|
|
|
|
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);
|
|
};
|
|
|
|
window.removeIvrOption = function(button) {
|
|
$(button).closest('.ivr-option').remove();
|
|
};
|
|
|
|
// 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);
|
|
|
|
// console.log('Saved step data:', step);
|
|
if (stepType === 'schedule_check') {
|
|
// console.log('Saved schedule step data:', step.data);
|
|
}
|
|
|
|
updateWorkflowDisplay();
|
|
closeStepConfigModal();
|
|
});
|
|
|
|
function parseStepFormData(stepType, formData) {
|
|
var data = {};
|
|
|
|
// console.log('Parsing form data for step type:', stepType);
|
|
|
|
formData.forEach(function(field) {
|
|
// console.log('Processing field:', field.name, '=', field.value);
|
|
|
|
if (field.name === 'use_tts') {
|
|
data.use_tts = true;
|
|
} else if (field.name === 'audio_type') {
|
|
data.audio_type = field.value;
|
|
} else if (field.name === 'audio_url') {
|
|
data.audio_url = field.value;
|
|
} 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;
|
|
}
|
|
});
|
|
|
|
// 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);
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
// Handle IVR options specially
|
|
if (stepType === 'ivr_menu' && data.digit) {
|
|
// console.log('Processing IVR options - raw data:', data);
|
|
data.options = {};
|
|
for (var i = 0; i < data.digit.length; i++) {
|
|
// Get the appropriate target value based on action type
|
|
var targetValue = data.target[i];
|
|
var option = {
|
|
action: data.action[i],
|
|
description: data.description[i],
|
|
number: '',
|
|
queue_name: '',
|
|
message: ''
|
|
};
|
|
|
|
// Set the appropriate field based on action type
|
|
switch(data.action[i]) {
|
|
case 'forward':
|
|
option.number = targetValue;
|
|
break;
|
|
case 'queue':
|
|
option.queue_name = targetValue;
|
|
break;
|
|
case 'voicemail':
|
|
option.message = targetValue;
|
|
break;
|
|
case 'message':
|
|
option.message = targetValue;
|
|
break;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
delete data.digit;
|
|
delete data.action;
|
|
delete data.description;
|
|
delete data.target;
|
|
}
|
|
|
|
// Handle schedule check after-hours steps
|
|
if (stepType === 'schedule_check') {
|
|
// console.log('Parsing schedule_check step data:', data);
|
|
|
|
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;
|
|
});
|
|
|
|
// console.log('Parsed schedule_check data:', data);
|
|
}
|
|
|
|
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':
|
|
// 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';
|
|
}
|
|
case 'voicemail':
|
|
return 'Record voicemail (max ' + (step.data.max_length || 120) + 's)';
|
|
case 'schedule_check':
|
|
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)' : '');
|
|
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;
|
|
}
|
|
|
|
// Check if at least one phone number is selected
|
|
var hasPhoneNumbers = false;
|
|
$('#workflow-phone-numbers select[name="phone_numbers[]"]').each(function() {
|
|
if ($(this).val()) {
|
|
hasPhoneNumbers = true;
|
|
return false; // break out of loop
|
|
}
|
|
});
|
|
|
|
if (!hasPhoneNumbers) {
|
|
alert('Please select at least one phone number');
|
|
return;
|
|
}
|
|
|
|
if (workflowSteps.length === 0) {
|
|
alert('Please add at least one step to the workflow');
|
|
return;
|
|
}
|
|
|
|
// Collect phone numbers from all selects
|
|
var phoneNumbers = [];
|
|
$('#workflow-phone-numbers select[name="phone_numbers[]"]').each(function() {
|
|
var phoneNumber = $(this).val();
|
|
if (phoneNumber && phoneNumbers.indexOf(phoneNumber) === -1) {
|
|
phoneNumbers.push(phoneNumber);
|
|
}
|
|
});
|
|
|
|
var payload = {
|
|
action: currentWorkflowId ? 'twp_update_workflow' : 'twp_save_workflow',
|
|
workflow_name: workflowData.workflow_name,
|
|
phone_numbers: phoneNumbers,
|
|
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);
|
|
tb_show('Edit Workflow: ' + workflow.workflow_name, '#TB_inline?inlineId=workflow-builder&width=900&height=700');
|
|
|
|
// Set basic info
|
|
$('#workflow-basic-info')[0].reset();
|
|
$('[name="workflow_name"]').val(workflow.workflow_name);
|
|
$('[name="is_active"]').prop('checked', workflow.is_active == '1');
|
|
|
|
// Store the phone number to select after options are loaded
|
|
var phoneNumberToSelect = workflow.phone_number;
|
|
|
|
// Load workflow data
|
|
currentWorkflowId = workflowId;
|
|
// console.log('Loading workflow data:', workflow.workflow_data);
|
|
if (workflow.workflow_data) {
|
|
try {
|
|
var workflowData = JSON.parse(workflow.workflow_data);
|
|
workflowSteps = workflowData.steps || [];
|
|
// console.log('Parsed workflow steps:', workflowSteps);
|
|
|
|
// Debug schedule steps specifically
|
|
workflowSteps.forEach(function(step, index) {
|
|
if (step.type === 'schedule_check') {
|
|
// console.log('Found schedule step at index', index, ':', step);
|
|
}
|
|
});
|
|
} catch (e) {
|
|
console.error('Error parsing workflow data:', e);
|
|
// console.log('Raw workflow data that failed to parse:', workflow.workflow_data);
|
|
workflowSteps = [];
|
|
}
|
|
} else {
|
|
// console.log('No workflow data found');
|
|
workflowSteps = [];
|
|
}
|
|
|
|
// Load phone numbers and set up the workflow
|
|
loadPhoneNumbers(function() {
|
|
// Load assigned phone numbers for this workflow
|
|
loadWorkflowPhoneNumbers(workflowId, function(phoneNumbers) {
|
|
if (phoneNumbers && phoneNumbers.length > 0) {
|
|
// Clear existing phone number inputs
|
|
$('#workflow-phone-numbers').empty();
|
|
|
|
// Add phone number rows
|
|
phoneNumbers.forEach(function(phoneNumber, index) {
|
|
if (index === 0) {
|
|
// First row with Add button
|
|
var firstRow = '<div class="phone-number-row">' +
|
|
'<select name="phone_numbers[]" class="workflow-phone-select" required></select>' +
|
|
'<button type="button" class="button add-phone-number" style="margin-left: 10px;">' +
|
|
(phoneNumbers.length === 1 ? 'Add Number' : 'Add Another Number') + '</button>' +
|
|
'</div>';
|
|
$('#workflow-phone-numbers').append(firstRow);
|
|
} else {
|
|
// Additional rows with Remove button
|
|
var additionalRow = '<div class="phone-number-row" style="margin-top: 10px;">' +
|
|
'<select name="phone_numbers[]" class="workflow-phone-select"></select>' +
|
|
'<button type="button" class="button remove-phone-number" style="margin-left: 10px; background: #d63638; color: white;">Remove</button>' +
|
|
'</div>';
|
|
$('#workflow-phone-numbers').append(additionalRow);
|
|
}
|
|
});
|
|
|
|
// Repopulate selects and set values
|
|
loadPhoneNumbers(function() {
|
|
phoneNumbers.forEach(function(phoneNumber, index) {
|
|
$('#workflow-phone-numbers .phone-number-row').eq(index).find('select').val(phoneNumber);
|
|
});
|
|
});
|
|
} else {
|
|
// Fallback to legacy phone number field
|
|
$('#workflow-phone').val(phoneNumberToSelect);
|
|
$('#workflow-phone').trigger('change');
|
|
}
|
|
});
|
|
});
|
|
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('');
|
|
tb_show('Create New Queue', '#TB_inline?inlineId=queue-modal&width=600&height=550');
|
|
};
|
|
|
|
window.closeQueueModal = function() {
|
|
tb_remove();
|
|
};
|
|
|
|
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);
|
|
$('[name="notification_number"]').val(queue.notification_number);
|
|
$('[name="agent_group_id"]').val(queue.agent_group_id);
|
|
$('[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);
|
|
tb_show('Edit Queue: ' + queue.queue_name, '#TB_inline?inlineId=queue-modal&width=600&height=550');
|
|
}
|
|
});
|
|
};
|
|
|
|
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();
|
|
|
|
// 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
|
|
$('body').append(modalHtml);
|
|
|
|
// Use WordPress ThickBox
|
|
tb_show('Queue Details', '#TB_inline?inlineId=queue-details-modal&width=600&height=400');
|
|
}
|
|
|
|
window.closeQueueDetailsModal = function() {
|
|
tb_remove();
|
|
$('#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);
|
|
tb_show('Configure: ' + phoneNumber, '#TB_inline?inlineId=number-config-modal&width=600&height=400');
|
|
};
|
|
|
|
window.closeNumberConfigModal = function() {
|
|
tb_remove();
|
|
};
|
|
|
|
$('#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');
|
|
var currentValue = $select.attr('data-current') || $select.val() || '';
|
|
|
|
// console.log('loadQueues - currentValue from data-current:', currentValue);
|
|
$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) {
|
|
var selected = queue.id == currentValue ? ' selected' : '';
|
|
options += '<option value="' + queue.id + '"' + selected + '>' + queue.queue_name + '</option>';
|
|
});
|
|
|
|
$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');
|
|
});
|
|
};
|
|
|
|
// Load queues for a specific select element (used in IVR options)
|
|
function loadQueuesForSelect($select) {
|
|
var currentValue = $select.attr('data-current') || $select.val() || '';
|
|
// console.log('loadQueuesForSelect called with currentValue:', currentValue);
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
action: 'twp_get_all_queues',
|
|
nonce: twp_ajax.nonce
|
|
}, function(response) {
|
|
// console.log('Queue loading response:', response);
|
|
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>';
|
|
// console.log('Added queue option:', queue.queue_name, 'ID:', queue.id, 'Selected:', selected);
|
|
});
|
|
|
|
$select.html(options);
|
|
// console.log('Queue options loaded, final select value:', $select.val());
|
|
} else {
|
|
console.error('Failed to load queues:', response);
|
|
}
|
|
}).fail(function(xhr, status, error) {
|
|
console.error('Queue loading failed:', error);
|
|
});
|
|
}
|
|
|
|
// Voice management for workflow steps
|
|
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');
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
action: 'twp_get_elevenlabs_voices',
|
|
nonce: twp_ajax.nonce
|
|
}, function(response) {
|
|
// console.log('Voice loading response:', response);
|
|
if ($button.length) {
|
|
$button.text('Load Voices').prop('disabled', false);
|
|
}
|
|
|
|
if (response.success) {
|
|
var options = '<option value="">Default voice</option>';
|
|
|
|
response.data.forEach(function(voice) {
|
|
var selected = (voice.voice_id === currentValue) ? ' selected' : '';
|
|
var description = voice.labels ? Object.values(voice.labels).join(', ') : '';
|
|
var optionText = voice.name + (description ? ' (' + description + ')' : '');
|
|
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);
|
|
}
|
|
});
|
|
|
|
$select.html(options);
|
|
|
|
// 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());
|
|
} 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() {
|
|
if ($button.length) {
|
|
$button.text('Load Voices').prop('disabled', false);
|
|
}
|
|
alert('Failed to load voices. Please check your API key.');
|
|
});
|
|
};
|
|
|
|
// Refresh voices function for workflow
|
|
window.refreshWorkflowVoices = 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');
|
|
}
|
|
|
|
// Read the current value
|
|
var currentValue = '';
|
|
var selectedOption = $select.find('option:selected');
|
|
if (selectedOption.length && selectedOption.val()) {
|
|
currentValue = selectedOption.val();
|
|
} else {
|
|
currentValue = $select.attr('data-current') || '';
|
|
}
|
|
|
|
if ($button.length) {
|
|
$button.text('Refreshing...').prop('disabled', true);
|
|
}
|
|
|
|
$.post(twp_ajax.ajax_url, {
|
|
action: 'twp_refresh_elevenlabs_voices',
|
|
nonce: twp_ajax.nonce
|
|
}, function(response) {
|
|
if ($button.length) {
|
|
$button.text('🔄').prop('disabled', false);
|
|
}
|
|
|
|
if (response.success) {
|
|
var options = '<option value="">Default voice</option>';
|
|
|
|
response.data.forEach(function(voice) {
|
|
var selected = (voice.voice_id === currentValue) ? ' selected' : '';
|
|
var description = voice.labels ? Object.values(voice.labels).join(', ') : '';
|
|
var optionText = voice.name + (description ? ' (' + description + ')' : '');
|
|
options += '<option value="' + voice.voice_id + '" data-voice-name="' + voice.name + '"' + selected + '>' + optionText + '</option>';
|
|
});
|
|
|
|
$select.html(options);
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
// Show success message
|
|
var $statusMsg = $('<span style="color: green; font-size: 12px; margin-left: 5px;">Refreshed!</span>');
|
|
$button.after($statusMsg);
|
|
setTimeout(function() {
|
|
$statusMsg.remove();
|
|
}, 3000);
|
|
|
|
} else {
|
|
var errorMessage = 'Error refreshing voices: ';
|
|
if (typeof response.data === 'string') {
|
|
errorMessage += response.data;
|
|
} else if (response.data && response.data.message) {
|
|
errorMessage += response.data.message;
|
|
} else {
|
|
errorMessage += 'Unknown error';
|
|
}
|
|
alert(errorMessage);
|
|
}
|
|
}).fail(function() {
|
|
if ($button.length) {
|
|
$button.text('🔄').prop('disabled', false);
|
|
}
|
|
alert('Failed to refresh voices. Please check your API key.');
|
|
});
|
|
};
|
|
|
|
// 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();
|
|
|
|
// Auto-load voices if they haven't been loaded yet
|
|
var $voiceSelect = $ttsOptions.find('.voice-select');
|
|
if ($voiceSelect.length && $voiceSelect.find('option').length <= 1) {
|
|
// Check if API key exists before auto-loading
|
|
if (twp_ajax.has_elevenlabs_key) {
|
|
loadWorkflowVoices($voiceSelect[0]);
|
|
}
|
|
}
|
|
} 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)
|
|
$(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();
|
|
}
|
|
});
|
|
|
|
// 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);
|
|
}
|
|
});
|
|
|
|
// 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();
|
|
|
|
// Hide and disable all target inputs first
|
|
$container.find('.target-forward, .target-queue, .target-voicemail, .target-message').hide().prop('disabled', true);
|
|
|
|
// Show and enable the appropriate input based on action
|
|
switch(action) {
|
|
case 'forward':
|
|
$container.find('.target-forward').show().prop('disabled', false);
|
|
break;
|
|
case 'queue':
|
|
$container.find('.target-queue').show().prop('disabled', false);
|
|
// Load queues if not already loaded
|
|
var $queueSelect = $container.find('.target-queue');
|
|
if ($queueSelect.find('option').length <= 1) {
|
|
loadQueuesForSelect($queueSelect);
|
|
}
|
|
break;
|
|
case 'voicemail':
|
|
$container.find('.target-voicemail').show().prop('disabled', false);
|
|
break;
|
|
case 'message':
|
|
$container.find('.target-message').show().prop('disabled', false);
|
|
break;
|
|
}
|
|
});
|
|
|
|
// 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) {
|
|
// First load voicemail details
|
|
$.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;
|
|
|
|
// 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) {
|
|
// console.log('Audio play error:', error);
|
|
// 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);
|
|
});
|
|
} else {
|
|
alert('Error loading voicemail: ' + (response.data || 'Unknown error'));
|
|
}
|
|
}).fail(function() {
|
|
alert('Failed to load voicemail details');
|
|
});
|
|
};
|
|
|
|
window.viewVoicemail = function(voicemailId) {
|
|
// Just use playVoicemail without auto-play
|
|
playVoicemail(voicemailId, '');
|
|
};
|
|
|
|
function showVoicemail(voicemailId, recordingUrl, transcription) {
|
|
// Set the audio source - already set by playVoicemail
|
|
|
|
// 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 {
|
|
transcriptionDiv.innerHTML = '<em>No transcription available.</em>';
|
|
|
|
// Show the generate transcription button
|
|
var transcribeBtn = document.getElementById('transcribe-btn');
|
|
if (transcribeBtn) {
|
|
transcribeBtn.style.display = 'inline-block';
|
|
transcribeBtn.setAttribute('data-voicemail-id', voicemailId);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store current voicemail ID for actions
|
|
window.currentVoicemailId = voicemailId;
|
|
window.currentRecordingUrl = recordingUrl;
|
|
|
|
// Modal is already shown by playVoicemail function
|
|
}
|
|
|
|
window.closeVoicemailModal = function() {
|
|
tb_remove();
|
|
|
|
// 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() {
|
|
var voicemailId = $('#transcribe-btn').data('voicemail-id') || window.currentVoicemailId;
|
|
|
|
if (!voicemailId) {
|
|
alert('No voicemail selected');
|
|
return;
|
|
}
|
|
|
|
$('#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);
|
|
}
|
|
});
|
|
};
|
|
|
|
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
|
|
|
|
// Agent Group Management Functions
|
|
window.openGroupModal = function() {
|
|
$('#group-form')[0].reset();
|
|
$('#group-id').val('');
|
|
$('#group-modal-title').text('Add New Group');
|
|
tb_show('Add New Group', '#TB_inline?inlineId=group-modal&width=600&height=500');
|
|
};
|
|
|
|
window.closeGroupModal = function() {
|
|
tb_remove();
|
|
};
|
|
|
|
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);
|
|
tb_show('Edit Group: ' + group.group_name, '#TB_inline?inlineId=group-modal&width=600&height=500');
|
|
} 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);
|
|
tb_show('Manage Group Members', '#TB_inline?inlineId=members-modal&width=700&height=600');
|
|
};
|
|
|
|
window.closeMembersModal = function() {
|
|
tb_remove();
|
|
};
|
|
|
|
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) {
|
|
// Show success modal instead of basic alert
|
|
showCallAcceptedModal();
|
|
refreshWaitingCalls();
|
|
} else {
|
|
// Show error modal
|
|
showErrorModal('Error accepting call: ' + response.data);
|
|
button.prop('disabled', false).text('Accept');
|
|
}
|
|
}).fail(function() {
|
|
showErrorModal('Failed to accept call. Please try again.');
|
|
button.prop('disabled', false).text('Accept');
|
|
});
|
|
};
|
|
|
|
// 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');
|
|
}
|
|
|
|
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'));
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
// Phone Number Management for Workflows
|
|
$(document).on('click', '.add-phone-number', function() {
|
|
var phoneNumbers = [];
|
|
var $container = $('#workflow-phone-numbers');
|
|
|
|
// Get available phone numbers from first select
|
|
var $firstSelect = $container.find('select').first();
|
|
var availableOptions = $firstSelect.html();
|
|
|
|
var newRow = '<div class="phone-number-row" style="margin-top: 10px;">' +
|
|
'<select name="phone_numbers[]" class="workflow-phone-select">' + availableOptions + '</select>' +
|
|
'<button type="button" class="button remove-phone-number" style="margin-left: 10px; background: #d63638; color: white;">Remove</button>' +
|
|
'</div>';
|
|
|
|
$container.append(newRow);
|
|
|
|
// Update button text on first row
|
|
$container.find('.add-phone-number').first().text('Add Another Number');
|
|
});
|
|
|
|
$(document).on('click', '.remove-phone-number', function() {
|
|
var $container = $('#workflow-phone-numbers');
|
|
$(this).closest('.phone-number-row').remove();
|
|
|
|
// Update button text if only one row remains
|
|
if ($container.find('.phone-number-row').length === 1) {
|
|
$container.find('.add-phone-number').first().text('Add Number');
|
|
}
|
|
});
|
|
|
|
// 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();
|
|
}); |