testing progress
This commit is contained in:
@@ -1582,8 +1582,8 @@ class TWP_Admin {
|
|||||||
<h3><?php echo esc_html($queue->queue_name); ?></h3>
|
<h3><?php echo esc_html($queue->queue_name); ?></h3>
|
||||||
<div class="queue-stats">
|
<div class="queue-stats">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<span class="label">Phone Number:</span>
|
<span class="label">Notification Number:</span>
|
||||||
<span class="value"><?php echo esc_html($queue->phone_number ?: 'Not set'); ?></span>
|
<span class="value"><?php echo esc_html($queue->notification_number ?: 'Not set'); ?></span>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
// Get agent group name
|
// Get agent group name
|
||||||
@@ -1635,8 +1635,8 @@ class TWP_Admin {
|
|||||||
<label>Queue Name:</label>
|
<label>Queue Name:</label>
|
||||||
<input type="text" name="queue_name" required>
|
<input type="text" name="queue_name" required>
|
||||||
|
|
||||||
<label>Phone Number:</label>
|
<label>SMS Notification Number:</label>
|
||||||
<select name="phone_number" id="queue-phone-number" class="regular-text">
|
<select name="notification_number" id="queue-notification-number" class="regular-text">
|
||||||
<option value="">Select a Twilio number...</option>
|
<option value="">Select a Twilio number...</option>
|
||||||
<?php
|
<?php
|
||||||
try {
|
try {
|
||||||
@@ -3082,7 +3082,7 @@ class TWP_Admin {
|
|||||||
|
|
||||||
$data = array(
|
$data = array(
|
||||||
'queue_name' => sanitize_text_field($_POST['queue_name']),
|
'queue_name' => sanitize_text_field($_POST['queue_name']),
|
||||||
'phone_number' => sanitize_text_field($_POST['phone_number']),
|
'notification_number' => sanitize_text_field($_POST['notification_number']),
|
||||||
'agent_group_id' => !empty($_POST['agent_group_id']) ? intval($_POST['agent_group_id']) : null,
|
'agent_group_id' => !empty($_POST['agent_group_id']) ? intval($_POST['agent_group_id']) : null,
|
||||||
'max_size' => intval($_POST['max_size']),
|
'max_size' => intval($_POST['max_size']),
|
||||||
'wait_music_url' => esc_url_raw($_POST['wait_music_url']),
|
'wait_music_url' => esc_url_raw($_POST['wait_music_url']),
|
||||||
|
@@ -131,10 +131,10 @@ jQuery(document).ready(function($) {
|
|||||||
|
|
||||||
formDataString += '&action=twp_save_schedule&nonce=' + twp_ajax.nonce;
|
formDataString += '&action=twp_save_schedule&nonce=' + twp_ajax.nonce;
|
||||||
|
|
||||||
console.log('Submitting schedule form data:', formDataString);
|
// console.log('Submitting schedule form data:', formDataString);
|
||||||
|
|
||||||
$.post(twp_ajax.ajax_url, formDataString, function(response) {
|
$.post(twp_ajax.ajax_url, formDataString, function(response) {
|
||||||
console.log('Schedule save response:', response);
|
// console.log('Schedule save response:', response);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
closeScheduleModal();
|
closeScheduleModal();
|
||||||
location.reload();
|
location.reload();
|
||||||
@@ -258,9 +258,7 @@ jQuery(document).ready(function($) {
|
|||||||
var step = workflowSteps.find(function(s) { return s.id === stepId; });
|
var step = workflowSteps.find(function(s) { return s.id === stepId; });
|
||||||
if (!step) return;
|
if (!step) return;
|
||||||
|
|
||||||
console.log('Opening step config modal for step ID:', stepId, 'type:', stepType);
|
// console.log('Opening step config modal for step ID:', stepId, 'type:', stepType);
|
||||||
console.log('Step data:', step);
|
|
||||||
console.log('Step data.data:', step.data);
|
|
||||||
|
|
||||||
$('#step-id').val(stepId);
|
$('#step-id').val(stepId);
|
||||||
$('#step-type').val(stepType);
|
$('#step-type').val(stepType);
|
||||||
@@ -277,11 +275,19 @@ jQuery(document).ready(function($) {
|
|||||||
// Show as overlay with proper flexbox positioning
|
// Show as overlay with proper flexbox positioning
|
||||||
$('#step-config-modal').css('display', 'flex').addClass('show');
|
$('#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 it's a schedule step, load the schedules
|
||||||
if (stepType === 'schedule_check') {
|
if (stepType === 'schedule_check') {
|
||||||
console.log('Setting up schedule step - current data:', step.data);
|
// console.log('Setting up schedule step - current data:', step.data);
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
console.log('About to load schedules, current select element:', $('#schedule-select'));
|
// console.log('About to load schedules, current select element:', $('#schedule-select'));
|
||||||
loadSchedulesForStep();
|
loadSchedulesForStep();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
@@ -298,14 +304,54 @@ jQuery(document).ready(function($) {
|
|||||||
|
|
||||||
// If it's an IVR step, load queues for any existing queue actions
|
// If it's an IVR step, load queues for any existing queue actions
|
||||||
if (stepType === 'ivr_menu') {
|
if (stepType === 'ivr_menu') {
|
||||||
|
// console.log('Setting up IVR step - current data:', step.data);
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
|
// console.log('Loading queues for IVR step...');
|
||||||
$('#step-config-modal .target-queue').each(function() {
|
$('#step-config-modal .target-queue').each(function() {
|
||||||
var $select = $(this);
|
var $select = $(this);
|
||||||
if ($select.is(':visible')) {
|
var currentValue = $select.data('current');
|
||||||
loadQueuesForSelect($select);
|
// console.log('Loading queue for select with current value:', currentValue);
|
||||||
}
|
loadQueuesForSelect($select);
|
||||||
});
|
});
|
||||||
}, 100);
|
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,7 +390,12 @@ jQuery(document).ready(function($) {
|
|||||||
html += '<label>Voice:</label>';
|
html += '<label>Voice:</label>';
|
||||||
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
||||||
html += '<option value="">Default voice</option>';
|
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 += '</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="loadWorkflowVoices(this)">Load Voices</button>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
@@ -380,7 +431,12 @@ jQuery(document).ready(function($) {
|
|||||||
html += '<label>Voice:</label>';
|
html += '<label>Voice:</label>';
|
||||||
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
||||||
html += '<option value="">Default voice</option>';
|
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 += '</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="loadWorkflowVoices(this)">Load Voices</button>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
@@ -441,7 +497,12 @@ jQuery(document).ready(function($) {
|
|||||||
html += '<label>Voice:</label>';
|
html += '<label>Voice:</label>';
|
||||||
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
||||||
html += '<option value="">Default voice</option>';
|
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 += '</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="loadWorkflowVoices(this)">Load Voices</button>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
@@ -475,7 +536,12 @@ jQuery(document).ready(function($) {
|
|||||||
html += '<label>Voice:</label>';
|
html += '<label>Voice:</label>';
|
||||||
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
html += '<select name="voice_id" class="voice-select" data-current="' + (data.voice_id || '') + '">';
|
||||||
html += '<option value="">Default voice</option>';
|
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 += '</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="loadWorkflowVoices(this)">Load Voices</button>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
@@ -490,7 +556,7 @@ jQuery(document).ready(function($) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'schedule_check':
|
case 'schedule_check':
|
||||||
console.log('Generating schedule_check form with data:', data);
|
// console.log('Generating schedule_check form with data:', data);
|
||||||
html += '<div class="step-config-section">';
|
html += '<div class="step-config-section">';
|
||||||
html += '<h4>Schedule Check Settings</h4>';
|
html += '<h4>Schedule Check Settings</h4>';
|
||||||
html += '<label>Select Schedule:</label>';
|
html += '<label>Select Schedule:</label>';
|
||||||
@@ -498,7 +564,7 @@ jQuery(document).ready(function($) {
|
|||||||
html += '<option value="">Loading schedules...</option>';
|
html += '<option value="">Loading schedules...</option>';
|
||||||
html += '</select>';
|
html += '</select>';
|
||||||
html += '<p class="description">Uses WordPress timezone: ' + twp_ajax.timezone + '</p>';
|
html += '<p class="description">Uses WordPress timezone: ' + twp_ajax.timezone + '</p>';
|
||||||
console.log('Schedule ID being set:', (data.schedule_id || ''));
|
// console.log('Schedule ID being set:', (data.schedule_id || ''));
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
html += '<div class="step-config-section">';
|
html += '<div class="step-config-section">';
|
||||||
@@ -506,15 +572,15 @@ jQuery(document).ready(function($) {
|
|||||||
html += '<p class="description">Define what happens when calls come in outside business hours:</p>';
|
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 id="after-hours-steps" class="after-hours-steps-container">';
|
||||||
html += '<div class="after-hours-step-list">';
|
html += '<div class="after-hours-step-list">';
|
||||||
console.log('Generating after-hours steps HTML, data:', data.after_hours_steps);
|
// console.log('Generating after-hours steps HTML, data:', data.after_hours_steps);
|
||||||
if (data.after_hours_steps && data.after_hours_steps.length > 0) {
|
if (data.after_hours_steps && data.after_hours_steps.length > 0) {
|
||||||
console.log('Found', data.after_hours_steps.length, 'after-hours steps');
|
// console.log('Found', data.after_hours_steps.length, 'after-hours steps');
|
||||||
data.after_hours_steps.forEach(function(step, index) {
|
data.after_hours_steps.forEach(function(step, index) {
|
||||||
console.log('Generating HTML for after-hours step', index, ':', step);
|
// console.log('Generating HTML for after-hours step', index, ':', step);
|
||||||
html += generateAfterHoursStepHtml(step, index);
|
html += generateAfterHoursStepHtml(step, index);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('No after-hours steps found');
|
// console.log('No after-hours steps found');
|
||||||
html += '<p class="no-steps-message">No after-hours steps configured. Add steps below.</p>';
|
html += '<p class="no-steps-message">No after-hours steps configured. Add steps below.</p>';
|
||||||
}
|
}
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
@@ -569,12 +635,17 @@ jQuery(document).ready(function($) {
|
|||||||
// Forward action - phone number input
|
// Forward action - phone number input
|
||||||
html += '<input type="text" name="target[]" class="target-forward" placeholder="Phone Number" value="' +
|
html += '<input type="text" name="target[]" class="target-forward" placeholder="Phone Number" value="' +
|
||||||
(option.action === 'forward' ? (option.number || option.target || '') : '') +
|
(option.action === 'forward' ? (option.number || option.target || '') : '') +
|
||||||
'" style="display: ' + (option.action === 'forward' || !option.action ? 'block' : 'none') + ';">';
|
'" style="display: ' + (option.action === 'forward' || !option.action ? 'block' : 'none') + ';" ' +
|
||||||
|
(option.action !== 'forward' && option.action ? 'disabled' : '') + '>';
|
||||||
|
|
||||||
// Queue action - queue dropdown
|
// Queue action - queue dropdown
|
||||||
html += '<select name="target[]" class="target-queue queue-select" data-current="' +
|
var queueCurrent = '';
|
||||||
(option.action === 'queue' ? (option.queue_id || option.target || '') : '') +
|
if (option.action === 'queue') {
|
||||||
'" style="display: ' + (option.action === 'queue' ? 'block' : 'none') + ';">';
|
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>';
|
html += '<option value="">Select queue...</option>';
|
||||||
// Queue options will be populated by loadQueues function
|
// Queue options will be populated by loadQueues function
|
||||||
html += '</select>';
|
html += '</select>';
|
||||||
@@ -582,12 +653,14 @@ jQuery(document).ready(function($) {
|
|||||||
// Voicemail action - message input
|
// Voicemail action - message input
|
||||||
html += '<input type="text" name="target[]" class="target-voicemail" placeholder="Voicemail Message" value="' +
|
html += '<input type="text" name="target[]" class="target-voicemail" placeholder="Voicemail Message" value="' +
|
||||||
(option.action === 'voicemail' ? (option.message || option.target || '') : '') +
|
(option.action === 'voicemail' ? (option.message || option.target || '') : '') +
|
||||||
'" style="display: ' + (option.action === 'voicemail' ? 'block' : 'none') + ';">';
|
'" style="display: ' + (option.action === 'voicemail' ? 'block' : 'none') + ';" ' +
|
||||||
|
(option.action !== 'voicemail' ? 'disabled' : '') + '>';
|
||||||
|
|
||||||
// Message action - message input
|
// Message action - message input
|
||||||
html += '<input type="text" name="target[]" class="target-message" placeholder="Message Text" value="' +
|
html += '<input type="text" name="target[]" class="target-message" placeholder="Message Text" value="' +
|
||||||
(option.action === 'message' ? (option.message || option.target || '') : '') +
|
(option.action === 'message' ? (option.message || option.target || '') : '') +
|
||||||
'" style="display: ' + (option.action === 'message' ? 'block' : 'none') + ';">';
|
'" style="display: ' + (option.action === 'message' ? 'block' : 'none') + ';" ' +
|
||||||
|
(option.action !== 'message' ? 'disabled' : '') + '>';
|
||||||
|
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
html += '<button type="button" class="button button-small remove-ivr-option" onclick="removeIvrOption(this)">Remove</button>';
|
html += '<button type="button" class="button button-small remove-ivr-option" onclick="removeIvrOption(this)">Remove</button>';
|
||||||
@@ -678,26 +751,23 @@ jQuery(document).ready(function($) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.loadSchedulesForStep = function() {
|
window.loadSchedulesForStep = function() {
|
||||||
console.log('Loading schedules for step modal');
|
// console.log('Loading schedules for step modal');
|
||||||
$.post(twp_ajax.ajax_url, {
|
$.post(twp_ajax.ajax_url, {
|
||||||
action: 'twp_get_schedules',
|
action: 'twp_get_schedules',
|
||||||
nonce: twp_ajax.nonce
|
nonce: twp_ajax.nonce
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
console.log('Schedule response:', response);
|
// console.log('Schedule response:', response);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
var $select = $('#schedule-select');
|
var $select = $('#schedule-select');
|
||||||
var currentScheduleId = $select.data('current') || $select.val();
|
var currentScheduleId = $select.data('current') || $select.val();
|
||||||
console.log('Current schedule ID to select:', currentScheduleId);
|
// console.log('Current schedule ID to select:', currentScheduleId);
|
||||||
console.log('Available schedules:', response.data.length);
|
|
||||||
console.log('Select element found:', $select.length > 0);
|
|
||||||
console.log('Select element name attribute:', $select.attr('name'));
|
|
||||||
|
|
||||||
var options = '<option value="">Select a schedule...</option>';
|
var options = '<option value="">Select a schedule...</option>';
|
||||||
|
|
||||||
response.data.forEach(function(schedule) {
|
response.data.forEach(function(schedule) {
|
||||||
var selected = (schedule.id == currentScheduleId) ? ' selected' : '';
|
var selected = (schedule.id == currentScheduleId) ? ' selected' : '';
|
||||||
options += '<option value="' + schedule.id + '"' + selected + '>' + schedule.schedule_name + '</option>';
|
options += '<option value="' + schedule.id + '"' + selected + '>' + schedule.schedule_name + '</option>';
|
||||||
console.log('Added schedule option:', schedule.schedule_name, 'ID:', schedule.id, 'Selected:', selected);
|
// console.log('Added schedule option:', schedule.schedule_name, 'ID:', schedule.id, 'Selected:', selected);
|
||||||
});
|
});
|
||||||
|
|
||||||
$select.html(options);
|
$select.html(options);
|
||||||
@@ -706,7 +776,7 @@ jQuery(document).ready(function($) {
|
|||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
if (currentScheduleId) {
|
if (currentScheduleId) {
|
||||||
$select.val(currentScheduleId);
|
$select.val(currentScheduleId);
|
||||||
console.log('Set schedule select value to:', currentScheduleId, 'Current value:', $select.val());
|
// console.log('Set schedule select value to:', currentScheduleId, 'Current value:', $select.val());
|
||||||
|
|
||||||
// Force trigger change event to ensure any listeners are notified
|
// Force trigger change event to ensure any listeners are notified
|
||||||
$select.trigger('change');
|
$select.trigger('change');
|
||||||
@@ -745,9 +815,9 @@ jQuery(document).ready(function($) {
|
|||||||
// Parse form data into step data
|
// Parse form data into step data
|
||||||
step.data = parseStepFormData(stepType, formData);
|
step.data = parseStepFormData(stepType, formData);
|
||||||
|
|
||||||
console.log('Saved step data:', step);
|
// console.log('Saved step data:', step);
|
||||||
if (stepType === 'schedule_check') {
|
if (stepType === 'schedule_check') {
|
||||||
console.log('Saved schedule step data:', step.data);
|
// console.log('Saved schedule step data:', step.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateWorkflowDisplay();
|
updateWorkflowDisplay();
|
||||||
@@ -757,11 +827,10 @@ jQuery(document).ready(function($) {
|
|||||||
function parseStepFormData(stepType, formData) {
|
function parseStepFormData(stepType, formData) {
|
||||||
var data = {};
|
var data = {};
|
||||||
|
|
||||||
console.log('Parsing form data for step type:', stepType);
|
// console.log('Parsing form data for step type:', stepType);
|
||||||
console.log('Raw form data:', formData);
|
|
||||||
|
|
||||||
formData.forEach(function(field) {
|
formData.forEach(function(field) {
|
||||||
console.log('Processing field:', field.name, '=', field.value);
|
// console.log('Processing field:', field.name, '=', field.value);
|
||||||
|
|
||||||
if (field.name === 'use_tts') {
|
if (field.name === 'use_tts') {
|
||||||
data.use_tts = true;
|
data.use_tts = true;
|
||||||
@@ -780,7 +849,12 @@ jQuery(document).ready(function($) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Parsed data object:', data);
|
// 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
|
// For queue steps, also save the queue name for display purposes
|
||||||
if (stepType === 'queue' && data.queue_id) {
|
if (stepType === 'queue' && data.queue_id) {
|
||||||
@@ -800,15 +874,33 @@ jQuery(document).ready(function($) {
|
|||||||
|
|
||||||
// Handle IVR options specially
|
// Handle IVR options specially
|
||||||
if (stepType === 'ivr_menu' && data.digit) {
|
if (stepType === 'ivr_menu' && data.digit) {
|
||||||
|
// console.log('Processing IVR options - raw data:', data);
|
||||||
data.options = {};
|
data.options = {};
|
||||||
for (var i = 0; i < data.digit.length; i++) {
|
for (var i = 0; i < data.digit.length; i++) {
|
||||||
data.options[data.digit[i]] = {
|
var option = {
|
||||||
action: data.action[i],
|
action: data.action[i],
|
||||||
description: data.description[i],
|
description: data.description[i],
|
||||||
number: data.target[i],
|
number: data.target[i],
|
||||||
queue_name: data.target[i],
|
queue_name: data.target[i],
|
||||||
message: data.target[i]
|
message: data.target[i]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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.digit;
|
||||||
delete data.action;
|
delete data.action;
|
||||||
@@ -818,7 +910,7 @@ jQuery(document).ready(function($) {
|
|||||||
|
|
||||||
// Handle schedule check after-hours steps
|
// Handle schedule check after-hours steps
|
||||||
if (stepType === 'schedule_check') {
|
if (stepType === 'schedule_check') {
|
||||||
console.log('Parsing schedule_check step data:', data);
|
// console.log('Parsing schedule_check step data:', data);
|
||||||
|
|
||||||
var afterHoursSteps = [];
|
var afterHoursSteps = [];
|
||||||
|
|
||||||
@@ -852,9 +944,7 @@ jQuery(document).ready(function($) {
|
|||||||
return step && step.type;
|
return step && step.type;
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Parsed schedule_check data:', data);
|
// console.log('Parsed schedule_check data:', data);
|
||||||
console.log('After hours steps count:', data.after_hours_steps.length);
|
|
||||||
console.log('After hours steps:', data.after_hours_steps);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
@@ -1030,26 +1120,26 @@ jQuery(document).ready(function($) {
|
|||||||
|
|
||||||
// Load workflow data
|
// Load workflow data
|
||||||
currentWorkflowId = workflowId;
|
currentWorkflowId = workflowId;
|
||||||
console.log('Loading workflow data:', workflow.workflow_data);
|
// console.log('Loading workflow data:', workflow.workflow_data);
|
||||||
if (workflow.workflow_data) {
|
if (workflow.workflow_data) {
|
||||||
try {
|
try {
|
||||||
var workflowData = JSON.parse(workflow.workflow_data);
|
var workflowData = JSON.parse(workflow.workflow_data);
|
||||||
workflowSteps = workflowData.steps || [];
|
workflowSteps = workflowData.steps || [];
|
||||||
console.log('Parsed workflow steps:', workflowSteps);
|
// console.log('Parsed workflow steps:', workflowSteps);
|
||||||
|
|
||||||
// Debug schedule steps specifically
|
// Debug schedule steps specifically
|
||||||
workflowSteps.forEach(function(step, index) {
|
workflowSteps.forEach(function(step, index) {
|
||||||
if (step.type === 'schedule_check') {
|
if (step.type === 'schedule_check') {
|
||||||
console.log('Found schedule step at index', index, ':', step);
|
// console.log('Found schedule step at index', index, ':', step);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error parsing workflow data:', e);
|
console.error('Error parsing workflow data:', e);
|
||||||
console.log('Raw workflow data that failed to parse:', workflow.workflow_data);
|
// console.log('Raw workflow data that failed to parse:', workflow.workflow_data);
|
||||||
workflowSteps = [];
|
workflowSteps = [];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('No workflow data found');
|
// console.log('No workflow data found');
|
||||||
workflowSteps = [];
|
workflowSteps = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1121,7 +1211,7 @@ jQuery(document).ready(function($) {
|
|||||||
var queue = response.data;
|
var queue = response.data;
|
||||||
$('#queue-id').val(queue.id);
|
$('#queue-id').val(queue.id);
|
||||||
$('[name="queue_name"]').val(queue.queue_name);
|
$('[name="queue_name"]').val(queue.queue_name);
|
||||||
$('[name="phone_number"]').val(queue.phone_number);
|
$('[name="notification_number"]').val(queue.notification_number);
|
||||||
$('[name="agent_group_id"]').val(queue.agent_group_id);
|
$('[name="agent_group_id"]').val(queue.agent_group_id);
|
||||||
$('[name="max_size"]').val(queue.max_size);
|
$('[name="max_size"]').val(queue.max_size);
|
||||||
$('[name="timeout_seconds"]').val(queue.timeout_seconds);
|
$('[name="timeout_seconds"]').val(queue.timeout_seconds);
|
||||||
@@ -1464,8 +1554,9 @@ jQuery(document).ready(function($) {
|
|||||||
window.loadQueues = function(button) {
|
window.loadQueues = function(button) {
|
||||||
var $button = $(button);
|
var $button = $(button);
|
||||||
var $select = $button.prev('select.queue-select');
|
var $select = $button.prev('select.queue-select');
|
||||||
var currentValue = $select.data('current');
|
var currentValue = $select.attr('data-current') || $select.val() || '';
|
||||||
|
|
||||||
|
// console.log('loadQueues - currentValue from data-current:', currentValue);
|
||||||
$button.text('Loading...').prop('disabled', true);
|
$button.text('Loading...').prop('disabled', true);
|
||||||
|
|
||||||
$.post(twp_ajax.ajax_url, {
|
$.post(twp_ajax.ajax_url, {
|
||||||
@@ -1494,50 +1585,106 @@ jQuery(document).ready(function($) {
|
|||||||
|
|
||||||
// Load queues for a specific select element (used in IVR options)
|
// Load queues for a specific select element (used in IVR options)
|
||||||
function loadQueuesForSelect($select) {
|
function loadQueuesForSelect($select) {
|
||||||
var currentValue = $select.data('current') || $select.val();
|
var currentValue = $select.attr('data-current') || $select.val() || '';
|
||||||
|
// console.log('loadQueuesForSelect called with currentValue:', currentValue);
|
||||||
|
|
||||||
$.post(twp_ajax.ajax_url, {
|
$.post(twp_ajax.ajax_url, {
|
||||||
action: 'twp_get_all_queues',
|
action: 'twp_get_all_queues',
|
||||||
nonce: twp_ajax.nonce
|
nonce: twp_ajax.nonce
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
|
// console.log('Queue loading response:', response);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
var options = '<option value="">Select queue...</option>';
|
var options = '<option value="">Select queue...</option>';
|
||||||
|
|
||||||
response.data.forEach(function(queue) {
|
response.data.forEach(function(queue) {
|
||||||
var selected = queue.id == currentValue ? ' selected' : '';
|
var selected = queue.id == currentValue ? ' selected' : '';
|
||||||
options += '<option value="' + queue.id + '"' + selected + '>' + queue.queue_name + '</option>';
|
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);
|
$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
|
// Voice management for workflow steps
|
||||||
window.loadWorkflowVoices = function(button) {
|
window.loadWorkflowVoices = function(buttonOrSelect) {
|
||||||
var $button = $(button);
|
var $button, $select;
|
||||||
var $select = $button.prev('select.voice-select');
|
|
||||||
var currentValue = $select.data('current');
|
|
||||||
|
|
||||||
$button.text('Loading...').prop('disabled', true);
|
// 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, {
|
$.post(twp_ajax.ajax_url, {
|
||||||
action: 'twp_get_elevenlabs_voices',
|
action: 'twp_get_elevenlabs_voices',
|
||||||
nonce: twp_ajax.nonce
|
nonce: twp_ajax.nonce
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
$button.text('Load Voices').prop('disabled', false);
|
// console.log('Voice loading response:', response);
|
||||||
|
if ($button.length) {
|
||||||
|
$button.text('Load Voices').prop('disabled', false);
|
||||||
|
}
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
var options = '<option value="">Default voice</option>';
|
var options = '<option value="">Default voice</option>';
|
||||||
|
|
||||||
response.data.forEach(function(voice) {
|
response.data.forEach(function(voice) {
|
||||||
var selected = voice.voice_id === currentValue ? ' selected' : '';
|
var selected = (voice.voice_id === currentValue) ? ' selected' : '';
|
||||||
var description = voice.labels ? Object.values(voice.labels).join(', ') : '';
|
var description = voice.labels ? Object.values(voice.labels).join(', ') : '';
|
||||||
var optionText = voice.name + (description ? ' (' + description + ')' : '');
|
var optionText = voice.name + (description ? ' (' + description + ')' : '');
|
||||||
options += '<option value="' + voice.voice_id + '"' + selected + '>' + optionText + '</option>';
|
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);
|
$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 {
|
} else {
|
||||||
var errorMessage = 'Error loading voices: ';
|
var errorMessage = 'Error loading voices: ';
|
||||||
if (typeof response.data === 'string') {
|
if (typeof response.data === 'string') {
|
||||||
@@ -1552,7 +1699,9 @@ jQuery(document).ready(function($) {
|
|||||||
alert(errorMessage);
|
alert(errorMessage);
|
||||||
}
|
}
|
||||||
}).fail(function() {
|
}).fail(function() {
|
||||||
$button.text('Load Voices').prop('disabled', false);
|
if ($button.length) {
|
||||||
|
$button.text('Load Voices').prop('disabled', false);
|
||||||
|
}
|
||||||
alert('Failed to load voices. Please check your API key.');
|
alert('Failed to load voices. Please check your API key.');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -1587,22 +1736,40 @@ jQuery(document).ready(function($) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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
|
// Handle IVR action changes to show/hide appropriate target inputs
|
||||||
$(document).on('change', '.ivr-action-select', function() {
|
$(document).on('change', '.ivr-action-select', function() {
|
||||||
var $option = $(this).closest('.ivr-option');
|
var $option = $(this).closest('.ivr-option');
|
||||||
var $container = $option.find('.ivr-target-container');
|
var $container = $option.find('.ivr-target-container');
|
||||||
var action = $(this).val();
|
var action = $(this).val();
|
||||||
|
|
||||||
// Hide all target inputs
|
// Hide and disable all target inputs first
|
||||||
$container.find('.target-forward, .target-queue, .target-voicemail, .target-message').hide();
|
$container.find('.target-forward, .target-queue, .target-voicemail, .target-message').hide().prop('disabled', true);
|
||||||
|
|
||||||
// Show the appropriate input based on action
|
// Show and enable the appropriate input based on action
|
||||||
switch(action) {
|
switch(action) {
|
||||||
case 'forward':
|
case 'forward':
|
||||||
$container.find('.target-forward').show();
|
$container.find('.target-forward').show().prop('disabled', false);
|
||||||
break;
|
break;
|
||||||
case 'queue':
|
case 'queue':
|
||||||
$container.find('.target-queue').show();
|
$container.find('.target-queue').show().prop('disabled', false);
|
||||||
// Load queues if not already loaded
|
// Load queues if not already loaded
|
||||||
var $queueSelect = $container.find('.target-queue');
|
var $queueSelect = $container.find('.target-queue');
|
||||||
if ($queueSelect.find('option').length <= 1) {
|
if ($queueSelect.find('option').length <= 1) {
|
||||||
@@ -1610,10 +1777,10 @@ jQuery(document).ready(function($) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'voicemail':
|
case 'voicemail':
|
||||||
$container.find('.target-voicemail').show();
|
$container.find('.target-voicemail').show().prop('disabled', false);
|
||||||
break;
|
break;
|
||||||
case 'message':
|
case 'message':
|
||||||
$container.find('.target-message').show();
|
$container.find('.target-message').show().prop('disabled', false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1672,7 +1839,7 @@ jQuery(document).ready(function($) {
|
|||||||
|
|
||||||
// Play after loading
|
// Play after loading
|
||||||
audio.play().catch(function(error) {
|
audio.play().catch(function(error) {
|
||||||
console.log('Audio play error:', error);
|
// console.log('Audio play error:', error);
|
||||||
// If data URL fails, try direct Twilio URL as fallback
|
// If data URL fails, try direct Twilio URL as fallback
|
||||||
if (voicemail.recording_url) {
|
if (voicemail.recording_url) {
|
||||||
audio.src = voicemail.recording_url + '.mp3';
|
audio.src = voicemail.recording_url + '.mp3';
|
||||||
|
@@ -101,7 +101,7 @@ class TWP_Activator {
|
|||||||
$sql_queues = "CREATE TABLE $table_queues (
|
$sql_queues = "CREATE TABLE $table_queues (
|
||||||
id int(11) NOT NULL AUTO_INCREMENT,
|
id int(11) NOT NULL AUTO_INCREMENT,
|
||||||
queue_name varchar(100) NOT NULL,
|
queue_name varchar(100) NOT NULL,
|
||||||
phone_number varchar(20),
|
notification_number varchar(20),
|
||||||
agent_group_id int(11),
|
agent_group_id int(11),
|
||||||
max_size int(11) DEFAULT 10,
|
max_size int(11) DEFAULT 10,
|
||||||
wait_music_url varchar(255),
|
wait_music_url varchar(255),
|
||||||
@@ -110,7 +110,7 @@ class TWP_Activator {
|
|||||||
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
KEY agent_group_id (agent_group_id),
|
KEY agent_group_id (agent_group_id),
|
||||||
KEY phone_number (phone_number)
|
KEY notification_number (notification_number)
|
||||||
) $charset_collate;";
|
) $charset_collate;";
|
||||||
|
|
||||||
// Queued calls table
|
// Queued calls table
|
||||||
@@ -304,20 +304,29 @@ class TWP_Activator {
|
|||||||
$wpdb->query("ALTER TABLE $table_schedules MODIFY COLUMN days_of_week varchar(100) NOT NULL");
|
$wpdb->query("ALTER TABLE $table_schedules MODIFY COLUMN days_of_week varchar(100) NOT NULL");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new columns to call queues table
|
// Add new columns to call queues table and migrate phone_number to notification_number
|
||||||
$table_queues = $wpdb->prefix . 'twp_call_queues';
|
$table_queues = $wpdb->prefix . 'twp_call_queues';
|
||||||
|
|
||||||
// Check if phone_number column exists in queues table
|
// Check if phone_number column exists and notification_number doesn't - need migration
|
||||||
$phone_column_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'phone_number'");
|
$phone_column_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'phone_number'");
|
||||||
if (empty($phone_column_exists)) {
|
$notification_column_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'notification_number'");
|
||||||
$wpdb->query("ALTER TABLE $table_queues ADD COLUMN phone_number varchar(20) AFTER queue_name");
|
|
||||||
$wpdb->query("ALTER TABLE $table_queues ADD INDEX phone_number (phone_number)");
|
if (!empty($phone_column_exists) && empty($notification_column_exists)) {
|
||||||
|
// Migrate phone_number to notification_number
|
||||||
|
$wpdb->query("ALTER TABLE $table_queues CHANGE phone_number notification_number varchar(20)");
|
||||||
|
// Update the index name
|
||||||
|
$wpdb->query("ALTER TABLE $table_queues DROP INDEX phone_number");
|
||||||
|
$wpdb->query("ALTER TABLE $table_queues ADD INDEX notification_number (notification_number)");
|
||||||
|
} elseif (empty($phone_column_exists) && empty($notification_column_exists)) {
|
||||||
|
// Fresh installation - add notification_number column
|
||||||
|
$wpdb->query("ALTER TABLE $table_queues ADD COLUMN notification_number varchar(20) AFTER queue_name");
|
||||||
|
$wpdb->query("ALTER TABLE $table_queues ADD INDEX notification_number (notification_number)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if agent_group_id column exists in queues table
|
// Check if agent_group_id column exists in queues table
|
||||||
$group_column_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'agent_group_id'");
|
$group_column_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queues LIKE 'agent_group_id'");
|
||||||
if (empty($group_column_exists)) {
|
if (empty($group_column_exists)) {
|
||||||
$wpdb->query("ALTER TABLE $table_queues ADD COLUMN agent_group_id int(11) AFTER phone_number");
|
$wpdb->query("ALTER TABLE $table_queues ADD COLUMN agent_group_id int(11) AFTER notification_number");
|
||||||
$wpdb->query("ALTER TABLE $table_queues ADD INDEX agent_group_id (agent_group_id)");
|
$wpdb->query("ALTER TABLE $table_queues ADD INDEX agent_group_id (agent_group_id)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -243,18 +243,18 @@ class TWP_Agent_Manager {
|
|||||||
// Make a new call to the agent with proper caller ID
|
// Make a new call to the agent with proper caller ID
|
||||||
$twilio = new TWP_Twilio_API();
|
$twilio = new TWP_Twilio_API();
|
||||||
|
|
||||||
// Get the queue's phone number for proper caller ID (same logic as SMS webhook)
|
// Get the queue's notification number for proper caller ID (same logic as SMS webhook)
|
||||||
$queues_table = $wpdb->prefix . 'twp_call_queues';
|
$queues_table = $wpdb->prefix . 'twp_call_queues';
|
||||||
$queue_info = $wpdb->get_row($wpdb->prepare(
|
$queue_info = $wpdb->get_row($wpdb->prepare(
|
||||||
"SELECT phone_number FROM $queues_table WHERE id = %d",
|
"SELECT notification_number FROM $queues_table WHERE id = %d",
|
||||||
$call->queue_id
|
$call->queue_id
|
||||||
));
|
));
|
||||||
|
|
||||||
// Priority: 1) Queue's phone number, 2) Call's original to_number, 3) Default SMS number
|
// Priority: 1) Queue's notification number, 2) Call's original to_number, 3) Default SMS number
|
||||||
$workflow_number = null;
|
$workflow_number = null;
|
||||||
if (!empty($queue_info->phone_number)) {
|
if (!empty($queue_info->notification_number)) {
|
||||||
$workflow_number = $queue_info->phone_number;
|
$workflow_number = $queue_info->notification_number;
|
||||||
error_log('TWP Web Accept: Using queue phone number: ' . $workflow_number);
|
error_log('TWP Web Accept: Using queue notification number: ' . $workflow_number);
|
||||||
} elseif (!empty($call->to_number)) {
|
} elseif (!empty($call->to_number)) {
|
||||||
$workflow_number = $call->to_number;
|
$workflow_number = $call->to_number;
|
||||||
error_log('TWP Web Accept: Using original workflow number: ' . $workflow_number);
|
error_log('TWP Web Accept: Using original workflow number: ' . $workflow_number);
|
||||||
|
@@ -435,7 +435,7 @@ class TWP_Call_Queue {
|
|||||||
|
|
||||||
$insert_data = array(
|
$insert_data = array(
|
||||||
'queue_name' => sanitize_text_field($data['queue_name']),
|
'queue_name' => sanitize_text_field($data['queue_name']),
|
||||||
'phone_number' => !empty($data['phone_number']) ? sanitize_text_field($data['phone_number']) : '',
|
'notification_number' => !empty($data['notification_number']) ? sanitize_text_field($data['notification_number']) : '',
|
||||||
'agent_group_id' => !empty($data['agent_group_id']) ? intval($data['agent_group_id']) : null,
|
'agent_group_id' => !empty($data['agent_group_id']) ? intval($data['agent_group_id']) : null,
|
||||||
'max_size' => intval($data['max_size']),
|
'max_size' => intval($data['max_size']),
|
||||||
'wait_music_url' => esc_url_raw($data['wait_music_url']),
|
'wait_music_url' => esc_url_raw($data['wait_music_url']),
|
||||||
@@ -463,7 +463,7 @@ class TWP_Call_Queue {
|
|||||||
|
|
||||||
$update_data = array(
|
$update_data = array(
|
||||||
'queue_name' => sanitize_text_field($data['queue_name']),
|
'queue_name' => sanitize_text_field($data['queue_name']),
|
||||||
'phone_number' => !empty($data['phone_number']) ? sanitize_text_field($data['phone_number']) : '',
|
'notification_number' => !empty($data['notification_number']) ? sanitize_text_field($data['notification_number']) : '',
|
||||||
'agent_group_id' => !empty($data['agent_group_id']) ? intval($data['agent_group_id']) : null,
|
'agent_group_id' => !empty($data['agent_group_id']) ? intval($data['agent_group_id']) : null,
|
||||||
'max_size' => intval($data['max_size']),
|
'max_size' => intval($data['max_size']),
|
||||||
'wait_music_url' => esc_url_raw($data['wait_music_url']),
|
'wait_music_url' => esc_url_raw($data['wait_music_url']),
|
||||||
@@ -585,8 +585,8 @@ class TWP_Call_Queue {
|
|||||||
|
|
||||||
$twilio = new TWP_Twilio_API();
|
$twilio = new TWP_Twilio_API();
|
||||||
|
|
||||||
// Use the queue's phone number as the from number, or fall back to default
|
// Use the queue's notification number as the from number, or fall back to default
|
||||||
$from_number = !empty($queue->phone_number) ? $queue->phone_number : TWP_Twilio_API::get_sms_from_number();
|
$from_number = !empty($queue->notification_number) ? $queue->notification_number : TWP_Twilio_API::get_sms_from_number();
|
||||||
|
|
||||||
if (empty($from_number)) {
|
if (empty($from_number)) {
|
||||||
error_log("TWP: No SMS from number available for queue notifications");
|
error_log("TWP: No SMS from number available for queue notifications");
|
||||||
|
@@ -767,57 +767,121 @@ class TWP_Webhooks {
|
|||||||
/**
|
/**
|
||||||
* Handle IVR response
|
* Handle IVR response
|
||||||
*/
|
*/
|
||||||
private function handle_ivr_response() {
|
public function handle_ivr_response($request) {
|
||||||
$digits = isset($_POST['Digits']) ? $_POST['Digits'] : '';
|
$digits = $request->get_param('Digits') ?: '';
|
||||||
$workflow_id = isset($_GET['workflow_id']) ? intval($_GET['workflow_id']) : 0;
|
$workflow_id = intval($request->get_param('workflow_id') ?: 0);
|
||||||
$step_id = isset($_GET['step_id']) ? intval($_GET['step_id']) : 0;
|
$step_id = intval($request->get_param('step_id') ?: 0);
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
error_log('TWP IVR: Received digits="' . $digits . '", workflow_id=' . $workflow_id . ', step_id=' . $step_id);
|
||||||
|
error_log('TWP IVR: All request params: ' . json_encode($request->get_params()));
|
||||||
|
|
||||||
if (!$workflow_id || !$step_id) {
|
if (!$workflow_id || !$step_id) {
|
||||||
$this->send_default_response();
|
return $this->send_twiml_response($this->get_default_twiml());
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$workflow = TWP_Workflow::get_workflow($workflow_id);
|
$workflow = TWP_Workflow::get_workflow($workflow_id);
|
||||||
|
|
||||||
if (!$workflow) {
|
if (!$workflow) {
|
||||||
$this->send_default_response();
|
return $this->send_twiml_response($this->get_default_twiml());
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$workflow_data = json_decode($workflow->workflow_data, true);
|
$workflow_data = json_decode($workflow->workflow_data, true);
|
||||||
|
|
||||||
|
// Debug: log all steps in workflow
|
||||||
|
error_log('TWP IVR: Looking for step_id ' . $step_id . ' in workflow ' . $workflow_id);
|
||||||
|
foreach ($workflow_data['steps'] as $index => $step) {
|
||||||
|
error_log('TWP IVR: Step ' . $index . ' has ID: ' . (isset($step['id']) ? $step['id'] : 'NO ID'));
|
||||||
|
}
|
||||||
|
|
||||||
// Find the step and its options
|
// Find the step and its options
|
||||||
foreach ($workflow_data['steps'] as $step) {
|
foreach ($workflow_data['steps'] as $step) {
|
||||||
if ($step['id'] == $step_id && isset($step['options'][$digits])) {
|
if ($step['id'] == $step_id) {
|
||||||
$option = $step['options'][$digits];
|
error_log('TWP IVR: Found matching step with ID ' . $step_id);
|
||||||
|
// Options can be in step['data']['options'] or step['options']
|
||||||
|
$options = isset($step['data']['options']) ? $step['data']['options'] :
|
||||||
|
(isset($step['options']) ? $step['options'] : array());
|
||||||
|
|
||||||
switch ($option['action']) {
|
// Debug: log all available options
|
||||||
|
error_log('TWP IVR: All available options for step ' . $step_id . ': ' . json_encode($options));
|
||||||
|
|
||||||
|
if (isset($options[$digits])) {
|
||||||
|
$option = $options[$digits];
|
||||||
|
|
||||||
|
// Log for debugging
|
||||||
|
error_log('TWP IVR: Found option for digit ' . $digits . ': ' . json_encode($option));
|
||||||
|
|
||||||
|
switch ($option['action']) {
|
||||||
case 'forward':
|
case 'forward':
|
||||||
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
||||||
$dial = $twiml->addChild('Dial');
|
$dial = $twiml->addChild('Dial');
|
||||||
$dial->addChild('Number', $option['number']);
|
$dial->addChild('Number', $option['number']);
|
||||||
echo $twiml->asXML();
|
return $this->send_twiml_response($twiml->asXML());
|
||||||
return;
|
|
||||||
|
|
||||||
case 'queue':
|
case 'queue':
|
||||||
|
// Determine queue ID - could be in queue_id field or legacy queue_name field
|
||||||
|
$queue_id = null;
|
||||||
|
if (isset($option['queue_id']) && is_numeric($option['queue_id']) && $option['queue_id'] > 0) {
|
||||||
|
$queue_id = intval($option['queue_id']);
|
||||||
|
} elseif (isset($option['queue_name']) && is_numeric($option['queue_name']) && $option['queue_name'] > 0) {
|
||||||
|
// Legacy format where queue_name contains the queue ID
|
||||||
|
$queue_id = intval($option['queue_name']);
|
||||||
|
} elseif (isset($option['number']) && is_numeric($option['number']) && $option['number'] > 0) {
|
||||||
|
// Another legacy format where number contains the queue ID
|
||||||
|
$queue_id = intval($option['number']);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log('TWP IVR Queue: Determined queue_id=' . ($queue_id ? $queue_id : 'NULL') . ' from option: ' . json_encode($option));
|
||||||
|
|
||||||
|
// Use the TWP queue system if we have a valid queue_id
|
||||||
|
if ($queue_id && $queue_id > 0) {
|
||||||
|
$call_data = array(
|
||||||
|
'call_sid' => $request->get_param('CallSid'),
|
||||||
|
'from_number' => $request->get_param('From'),
|
||||||
|
'to_number' => $request->get_param('To')
|
||||||
|
);
|
||||||
|
|
||||||
|
error_log('TWP IVR Queue: Adding call to queue_id=' . $queue_id . ', call_sid=' . $call_data['call_sid']);
|
||||||
|
$position = TWP_Call_Queue::add_to_queue($queue_id, $call_data);
|
||||||
|
|
||||||
|
if ($position) {
|
||||||
|
error_log('TWP IVR Queue: Call added to position ' . $position);
|
||||||
|
// Generate TwiML for queue wait with proper callback URL
|
||||||
|
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
||||||
|
$enqueue = $twiml->addChild('Enqueue');
|
||||||
|
$enqueue->addAttribute('waitUrl', home_url('/wp-json/twilio-webhook/v1/queue-wait?queue_id=' . $queue_id));
|
||||||
|
$enqueue->addChild('Task', json_encode(array('queue_id' => $queue_id, 'position' => $position)));
|
||||||
|
return $this->send_twiml_response($twiml->asXML());
|
||||||
|
} else {
|
||||||
|
error_log('TWP IVR Queue: Failed to add call to queue');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we reach here, no valid queue was found - provide helpful message
|
||||||
|
error_log('TWP IVR Queue: No valid queue_id found, providing error message to caller');
|
||||||
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
||||||
$enqueue = $twiml->addChild('Enqueue', $option['queue_name']);
|
$say = $twiml->addChild('Say', 'Sorry, that option is not currently available. Please try again or hang up.');
|
||||||
echo $twiml->asXML();
|
$say->addAttribute('voice', 'alice');
|
||||||
return;
|
$twiml->addChild('Redirect'); // Redirect back to IVR menu
|
||||||
|
return $this->send_twiml_response($twiml->asXML());
|
||||||
|
|
||||||
case 'voicemail':
|
case 'voicemail':
|
||||||
$elevenlabs = new TWP_ElevenLabs_API();
|
$elevenlabs = new TWP_ElevenLabs_API();
|
||||||
$twiml = TWP_Workflow::create_voicemail_twiml($option, $elevenlabs);
|
$twiml = TWP_Workflow::create_voicemail_twiml($option, $elevenlabs);
|
||||||
echo $twiml;
|
return $this->send_twiml_response($twiml);
|
||||||
return;
|
|
||||||
|
|
||||||
case 'message':
|
case 'message':
|
||||||
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
$twiml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Response></Response>');
|
||||||
$say = $twiml->addChild('Say', $option['message']);
|
$say = $twiml->addChild('Say', $option['message']);
|
||||||
$say->addAttribute('voice', 'alice');
|
$say->addAttribute('voice', 'alice');
|
||||||
$twiml->addChild('Hangup');
|
$twiml->addChild('Hangup');
|
||||||
echo $twiml->asXML();
|
return $this->send_twiml_response($twiml->asXML());
|
||||||
return;
|
}
|
||||||
|
} else {
|
||||||
|
// Log for debugging when option not found
|
||||||
|
error_log('TWP IVR: No option found for digit "' . $digits . '" in step ' . $step_id);
|
||||||
|
error_log('TWP IVR: Available options: ' . json_encode(array_keys($options)));
|
||||||
|
error_log('TWP IVR: Full step data: ' . json_encode($step));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -827,7 +891,7 @@ class TWP_Webhooks {
|
|||||||
$say = $twiml->addChild('Say', 'Invalid option. Please try again.');
|
$say = $twiml->addChild('Say', 'Invalid option. Please try again.');
|
||||||
$say->addAttribute('voice', 'alice');
|
$say->addAttribute('voice', 'alice');
|
||||||
$twiml->addChild('Redirect');
|
$twiml->addChild('Redirect');
|
||||||
echo $twiml->asXML();
|
return $this->send_twiml_response($twiml->asXML());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1308,6 +1372,13 @@ class TWP_Webhooks {
|
|||||||
echo $response->asXML();
|
echo $response->asXML();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function get_default_twiml() {
|
||||||
|
$response = new \Twilio\TwiML\VoiceResponse();
|
||||||
|
$response->say('Thank you for calling. Goodbye.', ['voice' => 'alice']);
|
||||||
|
$response->hangup();
|
||||||
|
return $response->asXML();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send status SMS
|
* Send status SMS
|
||||||
*/
|
*/
|
||||||
@@ -1764,7 +1835,7 @@ class TWP_Webhooks {
|
|||||||
// Find waiting calls from queues assigned to this agent's groups
|
// Find waiting calls from queues assigned to this agent's groups
|
||||||
$placeholders = implode(',', array_fill(0, count($agent_groups), '%d'));
|
$placeholders = implode(',', array_fill(0, count($agent_groups), '%d'));
|
||||||
$waiting_call = $wpdb->get_row($wpdb->prepare("
|
$waiting_call = $wpdb->get_row($wpdb->prepare("
|
||||||
SELECT qc.*, q.phone_number as queue_phone_number, q.agent_group_id
|
SELECT qc.*, q.notification_number as queue_notification_number, q.agent_group_id
|
||||||
FROM $calls_table qc
|
FROM $calls_table qc
|
||||||
LEFT JOIN $queues_table q ON qc.queue_id = q.id
|
LEFT JOIN $queues_table q ON qc.queue_id = q.id
|
||||||
WHERE qc.status = 'waiting'
|
WHERE qc.status = 'waiting'
|
||||||
@@ -1775,7 +1846,7 @@ class TWP_Webhooks {
|
|||||||
} else {
|
} else {
|
||||||
// Agent not in any group - can only handle calls from queues with no assigned group
|
// Agent not in any group - can only handle calls from queues with no assigned group
|
||||||
$waiting_call = $wpdb->get_row($wpdb->prepare("
|
$waiting_call = $wpdb->get_row($wpdb->prepare("
|
||||||
SELECT qc.*, q.phone_number as queue_phone_number, q.agent_group_id
|
SELECT qc.*, q.notification_number as queue_notification_number, q.agent_group_id
|
||||||
FROM $calls_table qc
|
FROM $calls_table qc
|
||||||
LEFT JOIN $queues_table q ON qc.queue_id = q.id
|
LEFT JOIN $queues_table q ON qc.queue_id = q.id
|
||||||
WHERE qc.status = %s
|
WHERE qc.status = %s
|
||||||
@@ -1796,12 +1867,12 @@ class TWP_Webhooks {
|
|||||||
error_log('TWP Debug: Waiting call data: ' . print_r($waiting_call, true));
|
error_log('TWP Debug: Waiting call data: ' . print_r($waiting_call, true));
|
||||||
|
|
||||||
// Detailed debugging of phone number selection
|
// Detailed debugging of phone number selection
|
||||||
error_log('TWP Debug: Queue workflow_number field: ' . (empty($waiting_call->queue_phone_number) ? 'EMPTY' : $waiting_call->queue_phone_number));
|
error_log('TWP Debug: Queue notification_number field: ' . (empty($waiting_call->queue_notification_number) ? 'EMPTY' : $waiting_call->queue_notification_number));
|
||||||
error_log('TWP Debug: Original workflow_number field: ' . (empty($waiting_call->to_number) ? 'EMPTY' : $waiting_call->to_number));
|
error_log('TWP Debug: Original workflow_number field: ' . (empty($waiting_call->to_number) ? 'EMPTY' : $waiting_call->to_number));
|
||||||
|
|
||||||
if (!empty($waiting_call->queue_phone_number)) {
|
if (!empty($waiting_call->queue_notification_number)) {
|
||||||
$workflow_number = $waiting_call->queue_phone_number;
|
$workflow_number = $waiting_call->queue_notification_number;
|
||||||
error_log('TWP Debug: SELECTED queue workflow_number: ' . $workflow_number);
|
error_log('TWP Debug: SELECTED queue notification_number: ' . $workflow_number);
|
||||||
} elseif (!empty($waiting_call->to_number)) {
|
} elseif (!empty($waiting_call->to_number)) {
|
||||||
$workflow_number = $waiting_call->to_number;
|
$workflow_number = $waiting_call->to_number;
|
||||||
error_log('TWP Debug: SELECTED original workflow_number: ' . $workflow_number);
|
error_log('TWP Debug: SELECTED original workflow_number: ' . $workflow_number);
|
||||||
|
@@ -72,6 +72,8 @@ class TWP_Workflow {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ivr_menu':
|
case 'ivr_menu':
|
||||||
|
// Add workflow_id to the step data
|
||||||
|
$step['workflow_id'] = $workflow_id;
|
||||||
$step_twiml = self::create_ivr_menu_twiml($step, $elevenlabs);
|
$step_twiml = self::create_ivr_menu_twiml($step, $elevenlabs);
|
||||||
$stop_after_step = true; // IVR menu needs user input, stop here
|
$stop_after_step = true; // IVR menu needs user input, stop here
|
||||||
break;
|
break;
|
||||||
@@ -301,8 +303,12 @@ class TWP_Workflow {
|
|||||||
$gather->addAttribute('action', $step['action_url']);
|
$gather->addAttribute('action', $step['action_url']);
|
||||||
} else {
|
} else {
|
||||||
$webhook_url = home_url('/wp-json/twilio-webhook/v1/ivr-response');
|
$webhook_url = home_url('/wp-json/twilio-webhook/v1/ivr-response');
|
||||||
$webhook_url = add_query_arg('workflow_id', $step['workflow_id'], $webhook_url);
|
if (isset($step['workflow_id'])) {
|
||||||
$webhook_url = add_query_arg('step_id', $step['id'], $webhook_url);
|
$webhook_url = add_query_arg('workflow_id', $step['workflow_id'], $webhook_url);
|
||||||
|
}
|
||||||
|
if (isset($step['id'])) {
|
||||||
|
$webhook_url = add_query_arg('step_id', $step['id'], $webhook_url);
|
||||||
|
}
|
||||||
$gather->addAttribute('action', $webhook_url);
|
$gather->addAttribute('action', $webhook_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user