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

1591 lines
64 KiB
JavaScript
Raw Normal View History

2025-08-06 15:25:47 -07:00
jQuery(document).ready(function($) {
// Schedule Management
window.openScheduleModal = function() {
$('#schedule-modal').show();
$('#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();
};
window.closeScheduleModal = function() {
$('#schedule-modal').hide();
};
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="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]);
}
$('#schedule-modal').show();
} 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();
var formData = $(this).serialize();
// Collect days of week checkboxes
var daysOfWeek = [];
$('.days-checkbox:checked').each(function() {
daysOfWeek.push($(this).val());
});
// Add days to form data if any selected
if (daysOfWeek.length > 0) {
formData += '&days_of_week[]=' + daysOfWeek.join('&days_of_week[]=');
}
// Add after-hours fields
var afterHoursAction = $('select[name="after_hours_action"]').val();
if (afterHoursAction) {
formData += '&after_hours_action=' + encodeURIComponent(afterHoursAction);
if (afterHoursAction === 'workflow') {
var afterHoursWorkflow = $('select[name="after_hours_workflow_id"]').val();
if (afterHoursWorkflow) {
formData += '&after_hours_workflow_id=' + encodeURIComponent(afterHoursWorkflow);
}
} else if (afterHoursAction === 'forward') {
var afterHoursForward = $('input[name="after_hours_forward_number"]').val();
if (afterHoursForward) {
formData += '&after_hours_forward_number=' + encodeURIComponent(afterHoursForward);
}
}
}
formData += '&action=twp_save_schedule&nonce=' + twp_ajax.nonce;
$.post(twp_ajax.ajax_url, formData, function(response) {
if (response.success) {
closeScheduleModal();
location.reload();
} else {
alert('Error saving schedule: ' + (response.data || 'Unknown 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-builder').show();
$('#workflow-modal-title').text('Create New Workflow');
workflowSteps = [];
currentWorkflowId = null;
$('#workflow-basic-info')[0].reset();
loadPhoneNumbers();
updateWorkflowDisplay();
};
window.closeWorkflowBuilder = function() {
$('#workflow-builder').hide();
};
window.closeStepConfigModal = function() {
$('#step-config-modal').hide();
};
function loadPhoneNumbers() {
$.post(twp_ajax.ajax_url, {
action: 'twp_get_phone_numbers',
nonce: twp_ajax.nonce
}, function(response) {
if (response.success) {
var options = '<option value="">Select a phone number...</option>';
response.data.forEach(function(number) {
options += '<option value="' + number.phone_number + '">' + number.phone_number + ' (' + number.friendly_name + ')</option>';
});
$('#workflow-phone').html(options);
}
});
}
// 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_name: '', announce_message: '' };
case 'voicemail':
return { greeting_message: '', max_length: 120 };
case 'schedule_check':
return { schedule_id: '', in_hours_action: {}, after_hours_action: {} };
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;
$('#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);
$('#step-config-modal').show();
}
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>';
html += '<label><input type="checkbox" name="use_tts" ' + (data.use_tts ? 'checked' : '') + '> Use Text-to-Speech</label>';
html += '<div class="tts-options" style="margin-top: 10px; ' + (data.use_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>';
html += '</select>';
html += '<button type="button" class="button button-small" onclick="loadWorkflowVoices(this)">Load Voices</button>';
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) + '">';
html += '<label><input type="checkbox" name="use_tts" ' + (data.use_tts ? 'checked' : '') + '> Use Text-to-Speech</label>';
html += '<div class="tts-options" style="margin-top: 10px; ' + (data.use_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>';
html += '</select>';
html += '<button type="button" class="button button-small" onclick="loadWorkflowVoices(this)">Load Voices</button>';
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_name" class="queue-select" data-current="' + (data.queue_name || '') + '"><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>';
html += '<label><input type="checkbox" name="use_tts" ' + (data.use_tts ? 'checked' : '') + '> Use Text-to-Speech</label>';
html += '<div class="tts-options" style="margin-top: 10px; ' + (data.use_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>';
html += '</select>';
html += '<button type="button" class="button button-small" onclick="loadWorkflowVoices(this)">Load Voices</button>';
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) + '">';
html += '<label><input type="checkbox" name="use_tts" ' + (data.use_tts ? 'checked' : '') + '> Use Text-to-Speech</label>';
html += '<div class="tts-options" style="margin-top: 10px; ' + (data.use_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>';
html += '</select>';
html += '<button type="button" class="button button-small" onclick="loadWorkflowVoices(this)">Load Voices</button>';
html += '</div>';
html += '</div>';
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[]">';
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>';
html += '<input type="text" name="target[]" placeholder="Number/Queue" value="' + (option.number || option.queue_name || option.message || '') + '">';
html += '</div>';
return html;
}
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);
};
// 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);
updateWorkflowDisplay();
closeStepConfigModal();
});
function parseStepFormData(stepType, formData) {
var data = {};
formData.forEach(function(field) {
if (field.name === 'use_tts') {
data.use_tts = true;
} 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;
}
});
// Handle IVR options specially
if (stepType === 'ivr_menu' && data.digit) {
data.options = {};
for (var i = 0; i < data.digit.length; i++) {
data.options[data.digit[i]] = {
action: data.action[i],
description: data.description[i],
number: data.target[i],
queue_name: data.target[i],
message: data.target[i]
};
}
delete data.digit;
delete data.action;
delete data.description;
delete data.target;
}
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':
return step.data.queue_name ? 'Add to queue: ' + step.data.queue_name : 'No queue selected';
case 'voicemail':
return 'Record voicemail (max ' + (step.data.max_length || 120) + 's)';
case 'schedule_check':
return 'Route based on business hours';
case 'sms':
return step.data.to_number ? 'Send SMS to ' + step.data.to_number : 'No recipient set';
default:
return '';
}
}
// Save workflow
$('#save-workflow-btn').on('click', function() {
var formData = $('#workflow-basic-info').serializeArray();
var workflowData = {};
formData.forEach(function(field) {
if (field.name === 'is_active') {
workflowData.is_active = true;
} else {
workflowData[field.name] = field.value;
}
});
if (!workflowData.workflow_name) {
alert('Please enter a workflow name');
return;
}
if (!workflowData.phone_number) {
alert('Please select a phone number');
return;
}
if (workflowSteps.length === 0) {
alert('Please add at least one step to the workflow');
return;
}
var payload = {
action: currentWorkflowId ? 'twp_update_workflow' : 'twp_save_workflow',
workflow_name: workflowData.workflow_name,
phone_number: workflowData.phone_number,
workflow_data: JSON.stringify({
steps: workflowSteps,
conditions: [],
actions: []
}),
is_active: workflowData.is_active ? 1 : 0,
nonce: twp_ajax.nonce
};
if (currentWorkflowId) {
payload.workflow_id = currentWorkflowId;
}
$.post(twp_ajax.ajax_url, payload, function(response) {
if (response.success) {
closeWorkflowBuilder();
location.reload();
} else {
alert('Error saving workflow: ' + (response.data || 'Unknown error'));
}
});
});
window.editWorkflow = function(workflowId) {
// Load workflow data and open builder
$.post(twp_ajax.ajax_url, {
action: 'twp_get_workflow',
workflow_id: workflowId,
nonce: twp_ajax.nonce
}, function(response) {
if (response.success) {
var workflow = response.data;
$('#workflow-builder').show();
$('#workflow-modal-title').text('Edit Workflow: ' + workflow.workflow_name);
// Set basic info
$('#workflow-basic-info')[0].reset();
$('[name="workflow_name"]').val(workflow.workflow_name);
$('[name="phone_number"]').val(workflow.phone_number);
$('[name="is_active"]').prop('checked', workflow.is_active == '1');
// Load workflow data
currentWorkflowId = workflowId;
if (workflow.workflow_data) {
try {
var workflowData = JSON.parse(workflow.workflow_data);
workflowSteps = workflowData.steps || [];
} catch (e) {
console.error('Error parsing workflow data:', e);
workflowSteps = [];
}
} else {
workflowSteps = [];
}
loadPhoneNumbers();
updateWorkflowDisplay();
} else {
alert('Error loading workflow: ' + (response.data || 'Unknown error'));
}
});
};
window.testWorkflow = function(workflowId) {
var testNumber = prompt('Enter phone number to test (e.g., +1234567890):');
if (testNumber) {
$.post(twp_ajax.ajax_url, {
action: 'twp_test_call',
to_number: testNumber,
workflow_id: workflowId,
nonce: twp_ajax.nonce
}, function(response) {
if (response.success && response.data.success) {
alert('Test call initiated successfully!');
} else {
alert('Error initiating test call: ' + (response.data.error || '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-modal').show();
$('#queue-form')[0].reset();
$('#queue-id').val('');
};
window.closeQueueModal = function() {
$('#queue-modal').hide();
};
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="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);
$('#queue-modal').show();
}
});
};
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 modal HTML
var modalHtml = '<div id="queue-details-modal" class="twp-modal" style="display: flex;">';
modalHtml += '<div class="twp-modal-content" style="max-width: 800px;">';
modalHtml += '<div class="twp-modal-header">';
modalHtml += '<button class="twp-modal-close" onclick="closeQueueDetailsModal()">&times;</button>';
modalHtml += '</div>';
modalHtml += '<div class="twp-modal-body">' + content + '</div>';
modalHtml += '<div class="twp-modal-footer">';
modalHtml += '<button class="button" onclick="closeQueueDetailsModal()">Close</button>';
modalHtml += '</div>';
modalHtml += '</div>';
modalHtml += '</div>';
// Add modal to page
$('body').append(modalHtml);
}
window.closeQueueDetailsModal = function() {
$('#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);
$('#number-config-modal').show();
};
window.closeNumberConfigModal = function() {
$('#number-config-modal').hide();
};
$('#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.data('current');
$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.queue_name === currentValue ? ' selected' : '';
options += '<option value="' + queue.queue_name + '"' + 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');
});
};
// Voice management for workflow steps
window.loadWorkflowVoices = function(button) {
var $button = $(button);
var $select = $button.prev('select.voice-select');
var currentValue = $select.data('current');
$button.text('Loading...').prop('disabled', true);
$.post(twp_ajax.ajax_url, {
action: 'twp_get_elevenlabs_voices',
nonce: twp_ajax.nonce
}, function(response) {
$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 + '"' + selected + '>' + optionText + '</option>';
});
$select.html(options);
} 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() {
$button.text('Load Voices').prop('disabled', false);
alert('Failed to load voices. Please check your API key.');
});
};
// Toggle TTS options visibility
$(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();
}
});
// 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) {
var audio = document.getElementById('voicemail-audio');
if (audio) {
audio.src = recordingUrl;
audio.play();
// Show voicemail modal and load transcription
showVoicemail(voicemailId, recordingUrl);
}
};
window.viewVoicemail = function(voicemailId) {
// Load voicemail details via AJAX
$.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;
showVoicemail(voicemail.id, voicemail.recording_url, voicemail.transcription);
} else {
alert('Error loading voicemail details');
}
});
};
function showVoicemail(voicemailId, recordingUrl, transcription) {
// Set the audio source
var audio = document.getElementById('voicemail-audio');
if (audio && recordingUrl) {
audio.src = recordingUrl;
}
// 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>Transcription pending... This will be updated automatically when ready.</em>';
// Show the generate transcription button
var transcribeBtn = document.getElementById('transcribe-btn');
if (transcribeBtn) {
transcribeBtn.style.display = 'inline-block';
}
}
}
// Store current voicemail ID for actions
window.currentVoicemailId = voicemailId;
window.currentRecordingUrl = recordingUrl;
// Show modal
var modal = document.getElementById('voicemail-modal');
if (modal) {
modal.style.display = 'flex';
}
}
window.closeVoicemailModal = function() {
var modal = document.getElementById('voicemail-modal');
if (modal) {
modal.style.display = 'none';
}
// 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() {
if (window.currentVoicemailId) {
var transcribeBtn = document.getElementById('transcribe-btn');
if (transcribeBtn) {
transcribeBtn.innerHTML = 'Generating...';
transcribeBtn.disabled = true;
}
$.post(twp_ajax.ajax_url, {
action: 'twp_transcribe_voicemail',
voicemail_id: window.currentVoicemailId,
nonce: twp_ajax.nonce
}, function(response) {
if (response.success) {
var transcriptionDiv = document.getElementById('voicemail-transcription-text');
if (transcriptionDiv) {
transcriptionDiv.innerHTML = '<p>' + response.data.transcription + '</p>';
}
if (transcribeBtn) {
transcribeBtn.style.display = 'none';
}
} else {
alert('Error generating transcription: ' + response.data);
if (transcribeBtn) {
transcribeBtn.innerHTML = 'Generate Transcription';
transcribeBtn.disabled = false;
}
}
});
}
};
// Close modal when clicking outside
$(document).on('click', '#voicemail-modal', function(e) {
if (e.target.id === 'voicemail-modal') {
closeVoicemailModal();
}
});
// Agent Group Management Functions
window.openGroupModal = function() {
$('#group-modal').show();
$('#group-form')[0].reset();
$('#group-id').val('');
$('#group-modal-title').text('Add New Group');
};
window.closeGroupModal = function() {
$('#group-modal').hide();
};
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);
$('#group-modal').show();
} 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);
$('#members-modal').show();
};
window.closeMembersModal = function() {
$('#members-modal').hide();
};
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) {
alert('Call accepted! You should receive the call shortly.');
refreshWaitingCalls();
} else {
alert('Error accepting call: ' + response.data);
button.prop('disabled', false).text('Accept');
}
}).fail(function() {
alert('Failed to accept call. Please try again.');
button.prop('disabled', false).text('Accept');
});
};
window.refreshWaitingCalls = function() {
if (!$('#waiting-calls-list').length) return;
$.post(twp_ajax.ajax_url, {
action: 'twp_get_waiting_calls',
nonce: twp_ajax.nonce
}, function(response) {
if (response.success) {
var html = '';
if (response.data.length === 0) {
html = '<tr><td colspan="5">No calls waiting</td></tr>';
} else {
response.data.forEach(function(call) {
var waitMinutes = Math.floor(call.wait_seconds / 60);
var waitSeconds = call.wait_seconds % 60;
var waitTime = waitMinutes > 0 ? waitMinutes + 'm ' + waitSeconds + 's' : waitSeconds + 's';
html += '<tr>';
html += '<td>' + call.position + '</td>';
html += '<td>' + call.queue_name + '</td>';
html += '<td>' + call.from_number + '</td>';
html += '<td>' + waitTime + '</td>';
html += '<td><button class="accept-btn" onclick="acceptCall(' + call.id + ')">Accept</button></td>';
html += '</tr>';
});
}
$('#waiting-calls-list').html(html);
}
});
};
// Auto-refresh waiting calls every 5 seconds on agent queue page
if ($('#waiting-calls-list').length) {
setInterval(refreshWaitingCalls, 5000);
refreshWaitingCalls(); // Initial load
}
// Click-to-Call Functions
window.initiateCall = function() {
var toNumber = prompt('Enter number to call (e.g., +1234567890):');
if (toNumber) {
$.post(twp_ajax.ajax_url, {
action: 'twp_initiate_outbound_call',
to_number: toNumber,
nonce: twp_ajax.nonce
}, function(response) {
if (response.success) {
alert('Call initiated! You should receive a call on your phone shortly, then the call will connect to ' + toNumber);
} else {
alert('Error initiating call: ' + (response.data || 'Unknown error'));
}
});
}
};
// Callback Management Functions
window.requestCallback = function() {
var phoneNumber = prompt('Enter phone number for callback (e.g., +1234567890):');
if (phoneNumber) {
$.post(twp_ajax.ajax_url, {
action: 'twp_request_callback',
phone_number: phoneNumber,
nonce: twp_ajax.nonce
}, function(response) {
if (response.success) {
alert('Callback requested successfully! The customer will be called back soon.');
refreshCallbacks();
} else {
alert('Error requesting callback: ' + (response.data.message || 'Unknown error'));
}
});
}
};
window.refreshCallbacks = function() {
if (!$('#callbacks-list').length) return;
$.post(twp_ajax.ajax_url, {
action: 'twp_get_callbacks',
nonce: twp_ajax.nonce
}, function(response) {
if (response.success) {
var html = '';
var callbacks = response.data.callbacks;
var stats = response.data.stats;
if (callbacks.length === 0) {
html = '<tr><td colspan="6">No pending callbacks</td></tr>';
} else {
callbacks.forEach(function(callback) {
var statusClass = 'status-' + callback.status;
var queueName = callback.queue_name || 'Direct';
html += '<tr>';
html += '<td>' + callback.phone_number + '</td>';
html += '<td>' + queueName + '</td>';
html += '<td>' + callback.requested_at + '</td>';
html += '<td>' + callback.wait_minutes + ' min</td>';
html += '<td><span class="' + statusClass + '">' + callback.status + '</span></td>';
html += '<td>' + callback.attempts + '</td>';
html += '</tr>';
});
}
$('#callbacks-list').html(html);
// Update stats if available
if (stats) {
$('#callback-stats-total').text(stats.total_requests);
$('#callback-stats-completed').text(stats.completed);
$('#callback-stats-pending').text(stats.pending);
$('#callback-stats-success-rate').text(stats.success_rate + '%');
if (stats.avg_completion_time) {
$('#callback-stats-avg-time').text(Math.round(stats.avg_completion_time) + ' min');
}
}
}
});
};
// Auto-refresh callbacks every 10 seconds on callbacks page
if ($('#callbacks-list').length) {
setInterval(refreshCallbacks, 10000);
refreshCallbacks(); // Initial load
}
// Initialize on load
updateDashboardStats();
});