Add comprehensive voicemail integration to browser phone
- Add AJAX endpoint for fetching user voicemails with stats and recent list - Create voicemail display section with counts, transcriptions, and playback - Implement click-to-play functionality for voicemail audio - Add refresh and view-all buttons with admin page integration - Style voicemail section with consistent design and dark mode support - Include visual indicators for recordings and formatted durations - Add mobile responsive design and hover effects 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3770,6 +3770,56 @@ class TWP_Admin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX handler for getting user's recent voicemails
|
||||||
|
*/
|
||||||
|
public function ajax_get_user_voicemails() {
|
||||||
|
check_ajax_referer('twp_ajax_nonce', 'nonce');
|
||||||
|
|
||||||
|
if (!current_user_can('manage_options') && !current_user_can('twp_access_voicemails')) {
|
||||||
|
wp_send_json_error('Unauthorized');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global $wpdb;
|
||||||
|
$table_name = $wpdb->prefix . 'twp_voicemails';
|
||||||
|
|
||||||
|
// Get recent voicemails (last 10)
|
||||||
|
$voicemails = $wpdb->get_results($wpdb->prepare("
|
||||||
|
SELECT id, from_number, duration, transcription, created_at, recording_url
|
||||||
|
FROM $table_name
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT %d
|
||||||
|
", 10));
|
||||||
|
|
||||||
|
// Format data for frontend
|
||||||
|
$formatted_voicemails = array();
|
||||||
|
foreach ($voicemails as $vm) {
|
||||||
|
$formatted_voicemails[] = array(
|
||||||
|
'id' => $vm->id,
|
||||||
|
'from_number' => $vm->from_number,
|
||||||
|
'duration' => $vm->duration,
|
||||||
|
'transcription' => $vm->transcription ? substr($vm->transcription, 0, 100) . '...' : 'No transcription',
|
||||||
|
'created_at' => $vm->created_at,
|
||||||
|
'time_ago' => human_time_diff(strtotime($vm->created_at), current_time('timestamp')) . ' ago',
|
||||||
|
'has_recording' => !empty($vm->recording_url)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get voicemail counts
|
||||||
|
$total_count = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
|
||||||
|
$today_count = $wpdb->get_var($wpdb->prepare("
|
||||||
|
SELECT COUNT(*) FROM $table_name
|
||||||
|
WHERE DATE(created_at) = %s
|
||||||
|
", current_time('Y-m-d')));
|
||||||
|
|
||||||
|
wp_send_json_success(array(
|
||||||
|
'voicemails' => $formatted_voicemails,
|
||||||
|
'total_count' => $total_count,
|
||||||
|
'today_count' => $today_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AJAX handler for getting all groups
|
* AJAX handler for getting all groups
|
||||||
*/
|
*/
|
||||||
|
@@ -463,6 +463,148 @@
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Voicemail Section */
|
||||||
|
.twp-voicemail-section {
|
||||||
|
background: #fff;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twp-voicemail-section h4 {
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
color: #212529;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voicemail-stats {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #6c757d;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
display: block;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twp-voicemail-list {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voicemail-item {
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voicemail-item:hover {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voicemail-item.has-recording {
|
||||||
|
border-left: 4px solid #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voicemail-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voicemail-from {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.from-number {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voicemail-time {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voicemail-details {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voicemail-duration {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recording-indicator {
|
||||||
|
background: #28a745;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voicemail-transcription {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #495057;
|
||||||
|
font-style: italic;
|
||||||
|
padding: 8px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-left: 3px solid #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voicemail-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voicemail-actions .twp-btn {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-voicemails,
|
||||||
|
.voicemail-loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #6c757d;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.twp-btn-secondary {
|
.twp-btn-secondary {
|
||||||
background: #6c757d;
|
background: #6c757d;
|
||||||
color: white;
|
color: white;
|
||||||
@@ -588,7 +730,8 @@
|
|||||||
.twp-connection-status,
|
.twp-connection-status,
|
||||||
.twp-call-info,
|
.twp-call-info,
|
||||||
.twp-queue-controls,
|
.twp-queue-controls,
|
||||||
.twp-queue-section {
|
.twp-queue-section,
|
||||||
|
.twp-voicemail-section {
|
||||||
background: #2d3748;
|
background: #2d3748;
|
||||||
border-color: #4a5568;
|
border-color: #4a5568;
|
||||||
}
|
}
|
||||||
@@ -623,8 +766,11 @@
|
|||||||
.twp-phone-selection label,
|
.twp-phone-selection label,
|
||||||
.twp-browser-phone-title,
|
.twp-browser-phone-title,
|
||||||
.twp-queue-section h4,
|
.twp-queue-section h4,
|
||||||
|
.twp-voicemail-section h4,
|
||||||
.selected-queue-info h5,
|
.selected-queue-info h5,
|
||||||
.timer-value {
|
.timer-value,
|
||||||
|
.from-number,
|
||||||
|
.stat-value {
|
||||||
color: #f7fafc;
|
color: #f7fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,14 +781,33 @@
|
|||||||
.queue-info,
|
.queue-info,
|
||||||
.queue-stats,
|
.queue-stats,
|
||||||
.queue-loading,
|
.queue-loading,
|
||||||
.no-queues {
|
.no-queues,
|
||||||
|
.voicemail-stats,
|
||||||
|
.voicemail-time,
|
||||||
|
.voicemail-duration,
|
||||||
|
.stat-label,
|
||||||
|
.voicemail-loading,
|
||||||
|
.no-voicemails {
|
||||||
color: #cbd5e0;
|
color: #cbd5e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-queues {
|
.no-queues,
|
||||||
|
.voicemail-stats,
|
||||||
|
.voicemail-transcription {
|
||||||
background: #2d3748;
|
background: #2d3748;
|
||||||
border-color: #4a5568;
|
border-color: #4a5568;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.voicemail-item {
|
||||||
|
background: #2d3748;
|
||||||
|
border-color: #4a5568;
|
||||||
|
color: #f7fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.voicemail-item:hover {
|
||||||
|
background: #4a5568;
|
||||||
|
border-color: #63b3ed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Animation for incoming calls */
|
/* Animation for incoming calls */
|
||||||
|
@@ -32,6 +32,7 @@
|
|||||||
bindEvents();
|
bindEvents();
|
||||||
loadPhoneNumbers();
|
loadPhoneNumbers();
|
||||||
loadUserQueues();
|
loadUserQueues();
|
||||||
|
loadUserVoicemails();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -281,6 +282,23 @@
|
|||||||
toggleAlert();
|
toggleAlert();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Voicemail refresh button
|
||||||
|
$('#twp-refresh-voicemails').on('click', function() {
|
||||||
|
loadUserVoicemails();
|
||||||
|
});
|
||||||
|
|
||||||
|
// View all voicemails button
|
||||||
|
$('#twp-view-all-voicemails').on('click', function() {
|
||||||
|
// Open admin voicemails page in new tab
|
||||||
|
window.open(twp_frontend_ajax.admin_url + 'admin.php?page=twilio-wp-voicemails', '_blank');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Voicemail item click handler
|
||||||
|
$(document).on('click', '.voicemail-item', function() {
|
||||||
|
const voicemailId = $(this).data('voicemail-id');
|
||||||
|
playVoicemail(voicemailId);
|
||||||
|
});
|
||||||
|
|
||||||
// Queue item selection
|
// Queue item selection
|
||||||
$(document).on('click', '.queue-item', function() {
|
$(document).on('click', '.queue-item', function() {
|
||||||
const queueId = $(this).data('queue-id');
|
const queueId = $(this).data('queue-id');
|
||||||
@@ -921,6 +939,119 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load user's voicemails
|
||||||
|
*/
|
||||||
|
function loadUserVoicemails(silent = false) {
|
||||||
|
$.ajax({
|
||||||
|
url: twp_frontend_ajax.ajax_url,
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
action: 'twp_get_user_voicemails',
|
||||||
|
nonce: twp_frontend_ajax.nonce
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
displayVoicemails(response.data);
|
||||||
|
} else if (!silent) {
|
||||||
|
showMessage('Failed to load voicemails: ' + (response.data || 'Unknown error'), 'error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
if (!silent) {
|
||||||
|
showMessage('Failed to load voicemails', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display voicemails in the UI
|
||||||
|
*/
|
||||||
|
function displayVoicemails(data) {
|
||||||
|
const $voicemailList = $('#twp-voicemail-list');
|
||||||
|
|
||||||
|
// Update stats
|
||||||
|
$('#twp-total-voicemails').text(data.total_count || 0);
|
||||||
|
$('#twp-today-voicemails').text(data.today_count || 0);
|
||||||
|
|
||||||
|
if (!data.voicemails || data.voicemails.length === 0) {
|
||||||
|
$voicemailList.html('<div class="no-voicemails">No voicemails found.</div>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
data.voicemails.forEach(function(voicemail) {
|
||||||
|
const hasTranscription = voicemail.transcription && voicemail.transcription !== 'No transcription';
|
||||||
|
const hasRecording = voicemail.has_recording;
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<div class="voicemail-item ${hasRecording ? 'has-recording' : ''}" data-voicemail-id="${voicemail.id}">
|
||||||
|
<div class="voicemail-header">
|
||||||
|
<div class="voicemail-from">
|
||||||
|
<span class="phone-icon">📞</span>
|
||||||
|
<span class="from-number">${voicemail.from_number}</span>
|
||||||
|
</div>
|
||||||
|
<div class="voicemail-time">${voicemail.time_ago}</div>
|
||||||
|
</div>
|
||||||
|
<div class="voicemail-details">
|
||||||
|
<div class="voicemail-duration">
|
||||||
|
<span class="duration-icon">⏱️</span>
|
||||||
|
<span>${formatDuration(voicemail.duration)}</span>
|
||||||
|
</div>
|
||||||
|
${hasRecording ? '<span class="recording-indicator">🎵 Recording</span>' : ''}
|
||||||
|
</div>
|
||||||
|
${hasTranscription ? `<div class="voicemail-transcription">${voicemail.transcription}</div>` : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
$voicemailList.html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format duration in seconds to mm:ss
|
||||||
|
*/
|
||||||
|
function formatDuration(seconds) {
|
||||||
|
if (!seconds || seconds === 0) return '0:00';
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const remainingSeconds = seconds % 60;
|
||||||
|
return minutes + ':' + String(remainingSeconds).padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play voicemail audio
|
||||||
|
*/
|
||||||
|
function playVoicemail(voicemailId) {
|
||||||
|
if (!voicemailId) return;
|
||||||
|
|
||||||
|
// Get voicemail audio URL and play it
|
||||||
|
$.ajax({
|
||||||
|
url: twp_frontend_ajax.ajax_url,
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
action: 'twp_get_voicemail_audio',
|
||||||
|
voicemail_id: voicemailId,
|
||||||
|
nonce: twp_frontend_ajax.nonce
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success && response.data.audio_url) {
|
||||||
|
// Create and play audio element
|
||||||
|
const audio = new Audio(response.data.audio_url);
|
||||||
|
audio.play().catch(function(error) {
|
||||||
|
showMessage('Failed to play voicemail: ' + error.message, 'error');
|
||||||
|
});
|
||||||
|
showMessage('Playing voicemail...', 'info');
|
||||||
|
} else {
|
||||||
|
showMessage('No audio available for this voicemail', 'error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
showMessage('Failed to load voicemail audio', 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Load alert preference on init
|
// Load alert preference on init
|
||||||
loadAlertPreference();
|
loadAlertPreference();
|
||||||
|
|
||||||
|
@@ -156,6 +156,7 @@ class TWP_Core {
|
|||||||
$this->loader->add_action('wp_ajax_twp_delete_voicemail', $plugin_admin, 'ajax_delete_voicemail');
|
$this->loader->add_action('wp_ajax_twp_delete_voicemail', $plugin_admin, 'ajax_delete_voicemail');
|
||||||
$this->loader->add_action('wp_ajax_twp_transcribe_voicemail', $plugin_admin, 'ajax_transcribe_voicemail');
|
$this->loader->add_action('wp_ajax_twp_transcribe_voicemail', $plugin_admin, 'ajax_transcribe_voicemail');
|
||||||
$this->loader->add_action('wp_ajax_twp_get_voicemail_audio', $plugin_admin, 'ajax_get_voicemail_audio');
|
$this->loader->add_action('wp_ajax_twp_get_voicemail_audio', $plugin_admin, 'ajax_get_voicemail_audio');
|
||||||
|
$this->loader->add_action('wp_ajax_twp_get_user_voicemails', $plugin_admin, 'ajax_get_user_voicemails');
|
||||||
|
|
||||||
// Agent group management AJAX
|
// Agent group management AJAX
|
||||||
$this->loader->add_action('wp_ajax_twp_get_all_groups', $plugin_admin, 'ajax_get_all_groups');
|
$this->loader->add_action('wp_ajax_twp_get_all_groups', $plugin_admin, 'ajax_get_all_groups');
|
||||||
|
@@ -72,6 +72,7 @@ class TWP_Shortcodes {
|
|||||||
// Localize script with AJAX data
|
// Localize script with AJAX data
|
||||||
wp_localize_script('twp-browser-phone-frontend', 'twp_frontend_ajax', array(
|
wp_localize_script('twp-browser-phone-frontend', 'twp_frontend_ajax', array(
|
||||||
'ajax_url' => admin_url('admin-ajax.php'),
|
'ajax_url' => admin_url('admin-ajax.php'),
|
||||||
|
'admin_url' => admin_url(),
|
||||||
'nonce' => wp_create_nonce('twp_frontend_nonce'),
|
'nonce' => wp_create_nonce('twp_frontend_nonce'),
|
||||||
'user_id' => get_current_user_id(),
|
'user_id' => get_current_user_id(),
|
||||||
'is_logged_in' => is_user_logged_in()
|
'is_logged_in' => is_user_logged_in()
|
||||||
@@ -200,6 +201,34 @@ class TWP_Shortcodes {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Voicemail Section -->
|
||||||
|
<div class="twp-voicemail-section" id="twp-voicemail-section">
|
||||||
|
<h4>Recent Voicemails</h4>
|
||||||
|
<div class="voicemail-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Total:</span>
|
||||||
|
<span class="stat-value" id="twp-total-voicemails">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Today:</span>
|
||||||
|
<span class="stat-value" id="twp-today-voicemails">0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="twp-voicemail-list" id="twp-voicemail-list">
|
||||||
|
<div class="voicemail-loading">Loading voicemails...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="voicemail-actions">
|
||||||
|
<button id="twp-refresh-voicemails" class="twp-btn twp-btn-secondary">
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
<button id="twp-view-all-voicemails" class="twp-btn twp-btn-secondary">
|
||||||
|
View All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Audio elements for incoming calls handled by browser -->
|
<!-- Audio elements for incoming calls handled by browser -->
|
||||||
<audio id="twp-ringtone" style="display: none;"></audio>
|
<audio id="twp-ringtone" style="display: none;"></audio>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user