Add comprehensive queue management to browser phone shortcode

Features Added:
- Queue display showing all assigned queues for the current user
- Real-time queue statistics (waiting calls, capacity)
- Visual indicators for queues with waiting calls (green border, pulse animation)
- Queue selection with click interaction
- Accept next call from selected queue functionality
- Auto-refresh of queue data every 30 seconds
- Mobile-responsive queue interface

Backend Changes:
- New ajax_get_agent_queues() handler to fetch user's assigned queues
- Enhanced ajax_get_waiting_calls() to return structured data
- Proper permission checking for twp_access_agent_queue capability

Frontend Enhancements:
- Interactive queue list with selection states
- Queue controls panel showing selected queue info
- Refresh button for manual queue updates
- Visual feedback for queues with active calls
- Mobile-optimized layout for smaller screens

UI/UX Improvements:
- Queues with waiting calls highlighted with green accent
- Pulsing indicator for active queues
- Auto-selection of first queue with calls
- Responsive design for mobile devices
- Dark mode support for queue elements

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-08-13 13:58:24 -07:00
parent 9bef599406
commit 12c285dc90
5 changed files with 341 additions and 43 deletions

View File

@@ -3973,7 +3973,47 @@ class TWP_Admin {
ORDER BY c.position ASC ORDER BY c.position ASC
", $user_id)); ", $user_id));
wp_send_json_success($waiting_calls); wp_send_json_success(array(
'count' => count($waiting_calls),
'calls' => $waiting_calls
));
}
/**
* AJAX handler for getting agent's assigned queues
*/
public function ajax_get_agent_queues() {
// Check for either admin or frontend nonce
if (!$this->verify_ajax_nonce()) {
wp_send_json_error('Invalid nonce');
return;
}
if (!current_user_can('manage_options') && !current_user_can('twp_access_agent_queue')) {
wp_send_json_error('Unauthorized - Agent queue access required');
return;
}
global $wpdb;
$user_id = get_current_user_id();
$queues_table = $wpdb->prefix . 'twp_call_queues';
$groups_table = $wpdb->prefix . 'twp_group_members';
$calls_table = $wpdb->prefix . 'twp_queued_calls';
// Get queues where user is a member of the assigned agent group
$user_queues = $wpdb->get_results($wpdb->prepare("
SELECT DISTINCT q.*,
COUNT(c.id) as waiting_count,
COALESCE(SUM(CASE WHEN c.status = 'waiting' THEN 1 ELSE 0 END), 0) as current_waiting
FROM $queues_table q
LEFT JOIN $groups_table gm ON gm.group_id = q.agent_group_id
LEFT JOIN $calls_table c ON c.queue_id = q.id AND c.status = 'waiting'
WHERE gm.user_id = %d AND gm.is_active = 1
GROUP BY q.id
ORDER BY q.queue_name ASC
", $user_id));
wp_send_json_success($user_queues);
} }
/** /**

View File

@@ -293,8 +293,8 @@
font-weight: 500; font-weight: 500;
} }
/* Queue Controls */ /* Queue Management Section */
.twp-queue-controls { .twp-queue-section {
background: #fff; background: #fff;
padding: 16px; padding: 16px;
border-radius: 8px; border-radius: 8px;
@@ -302,18 +302,148 @@
margin-bottom: 20px; margin-bottom: 20px;
} }
.twp-queue-controls h4 { .twp-queue-section h4 {
margin: 0 0 12px 0; margin: 0 0 16px 0;
color: #333; color: #333;
font-size: 1.1rem; font-size: 1.1rem;
text-align: center;
} }
.queue-status { /* Queue List */
margin-bottom: 12px; .twp-queue-list {
margin-bottom: 16px;
}
.queue-loading,
.no-queues {
text-align: center;
color: #6c757d; color: #6c757d;
font-style: italic;
padding: 20px;
}
.no-queues {
background: #f8f9fa;
border: 2px dashed #e9ecef;
border-radius: 8px;
}
.queue-item {
background: #f8f9fa;
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
}
.queue-item:hover {
border-color: #007cba;
background: #e3f2fd;
}
.queue-item.selected {
border-color: #007cba;
background: #e3f2fd;
box-shadow: 0 0 0 1px #007cba;
}
.queue-item.has-calls {
border-left: 4px solid #28a745;
}
.queue-item.has-calls::after {
content: '';
position: absolute;
top: 8px;
right: 8px;
width: 8px;
height: 8px;
background: #28a745;
border-radius: 50%;
animation: pulse 2s infinite;
}
.queue-name {
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.queue-info {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.9rem;
color: #6c757d;
}
.queue-waiting {
font-weight: 500; font-weight: 500;
} }
.queue-waiting.has-calls {
color: #28a745;
font-weight: 600;
}
.queue-capacity {
font-size: 0.8rem;
}
/* Queue Controls */
.twp-queue-controls {
background: #f8f9fa;
padding: 16px;
border-radius: 8px;
border: 2px solid #e9ecef;
margin-top: 16px;
}
.selected-queue-info {
margin-bottom: 16px;
text-align: center;
}
.selected-queue-info h5 {
margin: 0 0 8px 0;
color: #333;
font-size: 1rem;
}
.queue-stats {
display: flex;
justify-content: center;
gap: 20px;
font-size: 0.9rem;
color: #6c757d;
}
.queue-stats span {
font-weight: 500;
}
.queue-actions {
display: flex;
gap: 8px;
}
.queue-actions .twp-btn {
flex: 1;
}
.twp-btn-secondary {
background: #6c757d;
color: white;
}
.twp-btn-secondary:hover {
background: #5a6268;
transform: translateY(-1px);
}
/* Messages */ /* Messages */
.twp-messages { .twp-messages {
margin-top: 16px; margin-top: 16px;
@@ -397,6 +527,26 @@
min-height: 45px; min-height: 45px;
font-size: 16px; font-size: 16px;
} }
/* Queue section mobile adjustments */
.queue-stats {
flex-direction: column;
gap: 8px;
}
.queue-actions {
flex-direction: column;
}
.queue-item {
padding: 10px;
}
.queue-info {
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
} }
/* Dark mode support */ /* Dark mode support */

View File

@@ -11,6 +11,8 @@
let callStartTime = null; let callStartTime = null;
let isConnected = false; let isConnected = false;
let availableNumbers = []; let availableNumbers = [];
let userQueues = [];
let selectedQueue = null;
// Initialize when document is ready // Initialize when document is ready
$(document).ready(function() { $(document).ready(function() {
@@ -22,6 +24,7 @@
initializeBrowserPhone(); initializeBrowserPhone();
bindEvents(); bindEvents();
loadPhoneNumbers(); loadPhoneNumbers();
loadUserQueues();
}); });
/** /**
@@ -191,6 +194,17 @@
acceptQueueCall(); acceptQueueCall();
}); });
// Refresh queues button
$('#twp-refresh-queues').on('click', function() {
loadUserQueues();
});
// Queue item selection
$(document).on('click', '.queue-item', function() {
const queueId = $(this).data('queue-id');
selectQueue(queueId);
});
// Manual number input // Manual number input
$('#twp-dial-number').on('input', function() { $('#twp-dial-number').on('input', function() {
// Only allow valid phone number characters // Only allow valid phone number characters
@@ -336,21 +350,123 @@
} }
/** /**
* Accept next call from queue * Load user's assigned queues
*/
function loadUserQueues() {
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_get_agent_queues',
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success) {
userQueues = response.data;
displayQueues();
} else {
showMessage('Failed to load queues: ' + (response.data || 'Unknown error'), 'error');
}
},
error: function() {
showMessage('Failed to load queues', 'error');
}
});
}
/**
* Display queues in the UI
*/
function displayQueues() {
const $queueList = $('#twp-queue-list');
if (userQueues.length === 0) {
$queueList.html('<div class="no-queues">No queues assigned to you.</div>');
$('#twp-queue-section').hide();
return;
}
$('#twp-queue-section').show();
let html = '';
userQueues.forEach(function(queue) {
const hasWaiting = parseInt(queue.current_waiting) > 0;
const waitingCount = queue.current_waiting || 0;
html += `
<div class="queue-item ${hasWaiting ? 'has-calls' : ''}" data-queue-id="${queue.id}">
<div class="queue-name">${queue.queue_name}</div>
<div class="queue-info">
<span class="queue-waiting ${hasWaiting ? 'has-calls' : ''}">
${waitingCount} waiting
</span>
<span class="queue-capacity">
Max: ${queue.max_size}
</span>
</div>
</div>
`;
});
$queueList.html(html);
// Auto-select first queue with calls, or first queue if none have calls
const firstQueueWithCalls = userQueues.find(q => parseInt(q.current_waiting) > 0);
const queueToSelect = firstQueueWithCalls || userQueues[0];
if (queueToSelect) {
selectQueue(queueToSelect.id);
}
}
/**
* Select a queue
*/
function selectQueue(queueId) {
selectedQueue = userQueues.find(q => q.id == queueId);
if (!selectedQueue) return;
// Update UI selection
$('.queue-item').removeClass('selected');
$(`.queue-item[data-queue-id="${queueId}"]`).addClass('selected');
// Update queue controls
$('#selected-queue-name').text(selectedQueue.queue_name);
$('#twp-waiting-count').text(selectedQueue.current_waiting || 0);
$('#twp-queue-max-size').text(selectedQueue.max_size);
// Show queue controls if there are waiting calls
if (parseInt(selectedQueue.current_waiting) > 0) {
$('#twp-queue-controls').show();
} else {
$('#twp-queue-controls').hide();
}
}
/**
* Accept next call from selected queue
*/ */
function acceptQueueCall() { function acceptQueueCall() {
if (!selectedQueue) {
showMessage('Please select a queue first', 'error');
return;
}
$.ajax({ $.ajax({
url: twp_frontend_ajax.ajax_url, url: twp_frontend_ajax.ajax_url,
method: 'POST', method: 'POST',
data: { data: {
action: 'twp_accept_next_queue_call', action: 'twp_accept_next_queue_call',
queue_id: selectedQueue.id,
nonce: twp_frontend_ajax.nonce nonce: twp_frontend_ajax.nonce
}, },
success: function(response) { success: function(response) {
if (response.success) { if (response.success) {
showMessage('Connecting to next caller...', 'info'); showMessage('Connecting to next caller...', 'info');
// Refresh queue data after accepting call
setTimeout(loadUserQueues, 1000);
} else { } else {
showMessage(response.data || 'No calls waiting', 'info'); showMessage(response.data || 'No calls waiting in this queue', 'info');
} }
}, },
error: function() { error: function() {
@@ -467,34 +583,9 @@
// Periodic status updates // Periodic status updates
setInterval(function() { setInterval(function() {
if (isConnected) { if (isConnected) {
loadWaitingCallsCount(); loadUserQueues(); // This will refresh all queue data including waiting counts
} }
}, 30000); // Every 30 seconds }, 30000); // Every 30 seconds
/**
* Load waiting calls count for queue display
*/
function loadWaitingCallsCount() {
$.ajax({
url: twp_frontend_ajax.ajax_url,
method: 'POST',
data: {
action: 'twp_get_waiting_calls',
nonce: twp_frontend_ajax.nonce
},
success: function(response) {
if (response.success) {
$('#twp-waiting-count').text(response.data.count || 0);
// Show queue controls if user has permission and there are waiting calls
if (response.data.count > 0) {
$('#twp-queue-controls').show();
} else {
$('#twp-queue-controls').hide();
}
}
}
});
}
})(jQuery); })(jQuery);

View File

@@ -174,6 +174,7 @@ class TWP_Core {
$this->loader->add_action('twp_check_queue_timeouts', $this, 'check_queue_timeouts'); $this->loader->add_action('twp_check_queue_timeouts', $this, 'check_queue_timeouts');
$this->loader->add_action('wp_ajax_twp_accept_next_queue_call', $plugin_admin, 'ajax_accept_next_queue_call'); $this->loader->add_action('wp_ajax_twp_accept_next_queue_call', $plugin_admin, 'ajax_accept_next_queue_call');
$this->loader->add_action('wp_ajax_twp_get_waiting_calls', $plugin_admin, 'ajax_get_waiting_calls'); $this->loader->add_action('wp_ajax_twp_get_waiting_calls', $plugin_admin, 'ajax_get_waiting_calls');
$this->loader->add_action('wp_ajax_twp_get_agent_queues', $plugin_admin, 'ajax_get_agent_queues');
$this->loader->add_action('wp_ajax_twp_set_agent_status', $plugin_admin, 'ajax_set_agent_status'); $this->loader->add_action('wp_ajax_twp_set_agent_status', $plugin_admin, 'ajax_set_agent_status');
$this->loader->add_action('wp_ajax_twp_get_call_details', $plugin_admin, 'ajax_get_call_details'); $this->loader->add_action('wp_ajax_twp_get_call_details', $plugin_admin, 'ajax_get_call_details');

View File

@@ -153,15 +153,31 @@ class TWP_Shortcodes {
</div> </div>
</div> </div>
<!-- Queue Controls (for agents) --> <!-- Queue Management Section -->
<div class="twp-queue-controls" id="twp-queue-controls" style="display: none;"> <div class="twp-queue-section" id="twp-queue-section">
<h4>Queue Management</h4> <h4>Your Queues</h4>
<div class="queue-status"> <div class="twp-queue-list" id="twp-queue-list">
<span>Waiting calls: <span id="twp-waiting-count">0</span></span> <div class="queue-loading">Loading queues...</div>
</div> </div>
<!-- Queue Controls -->
<div class="twp-queue-controls" id="twp-queue-controls" style="display: none;">
<div class="selected-queue-info">
<h5 id="selected-queue-name">No queue selected</h5>
<div class="queue-stats">
<span class="waiting-count">Waiting: <span id="twp-waiting-count">0</span></span>
<span class="queue-size">Max: <span id="twp-queue-max-size">-</span></span>
</div>
</div>
<div class="queue-actions">
<button id="twp-accept-queue-call" class="twp-btn twp-btn-success"> <button id="twp-accept-queue-call" class="twp-btn twp-btn-success">
Accept Next Call Accept Next Call
</button> </button>
<button id="twp-refresh-queues" class="twp-btn twp-btn-secondary">
Refresh
</button>
</div>
</div>
</div> </div>
<!-- Audio elements --> <!-- Audio elements -->