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:
@@ -293,8 +293,8 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Queue Controls */
|
||||
.twp-queue-controls {
|
||||
/* Queue Management Section */
|
||||
.twp-queue-section {
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
@@ -302,18 +302,148 @@
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.twp-queue-controls h4 {
|
||||
margin: 0 0 12px 0;
|
||||
.twp-queue-section h4 {
|
||||
margin: 0 0 16px 0;
|
||||
color: #333;
|
||||
font-size: 1.1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.queue-status {
|
||||
margin-bottom: 12px;
|
||||
/* Queue List */
|
||||
.twp-queue-list {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.queue-loading,
|
||||
.no-queues {
|
||||
text-align: center;
|
||||
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;
|
||||
}
|
||||
|
||||
.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 */
|
||||
.twp-messages {
|
||||
margin-top: 16px;
|
||||
@@ -397,6 +527,26 @@
|
||||
min-height: 45px;
|
||||
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 */
|
||||
|
@@ -11,6 +11,8 @@
|
||||
let callStartTime = null;
|
||||
let isConnected = false;
|
||||
let availableNumbers = [];
|
||||
let userQueues = [];
|
||||
let selectedQueue = null;
|
||||
|
||||
// Initialize when document is ready
|
||||
$(document).ready(function() {
|
||||
@@ -22,6 +24,7 @@
|
||||
initializeBrowserPhone();
|
||||
bindEvents();
|
||||
loadPhoneNumbers();
|
||||
loadUserQueues();
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -191,6 +194,17 @@
|
||||
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
|
||||
$('#twp-dial-number').on('input', function() {
|
||||
// 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() {
|
||||
if (!selectedQueue) {
|
||||
showMessage('Please select a queue first', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: twp_frontend_ajax.ajax_url,
|
||||
method: 'POST',
|
||||
data: {
|
||||
action: 'twp_accept_next_queue_call',
|
||||
queue_id: selectedQueue.id,
|
||||
nonce: twp_frontend_ajax.nonce
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
showMessage('Connecting to next caller...', 'info');
|
||||
// Refresh queue data after accepting call
|
||||
setTimeout(loadUserQueues, 1000);
|
||||
} else {
|
||||
showMessage(response.data || 'No calls waiting', 'info');
|
||||
showMessage(response.data || 'No calls waiting in this queue', 'info');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
@@ -467,34 +583,9 @@
|
||||
// Periodic status updates
|
||||
setInterval(function() {
|
||||
if (isConnected) {
|
||||
loadWaitingCallsCount();
|
||||
loadUserQueues(); // This will refresh all queue data including waiting counts
|
||||
}
|
||||
}, 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);
|
Reference in New Issue
Block a user