Fix Parked title and add contact photo (#7825)
This commit is contained in:
@@ -431,32 +431,32 @@ $text['label-call_direction']['zh-cn'] = "通话方向";
|
||||
$text['label-call_direction']['ja-jp'] = "通話方向";
|
||||
$text['label-call_direction']['ko-kr'] = "통화 방향";
|
||||
|
||||
$text['label-parked_calls']['en-us'] = "Parked Calls";
|
||||
$text['label-parked_calls']['en-gb'] = "Parked Calls";
|
||||
$text['label-parked_calls']['ar-eg'] = "المكالمات المركونة";
|
||||
$text['label-parked_calls']['de-at'] = "Geparkte Anrufe";
|
||||
$text['label-parked_calls']['de-ch'] = "Geparkte Anrufe";
|
||||
$text['label-parked_calls']['de-de'] = "Geparkte Anrufe";
|
||||
$text['label-parked_calls']['el-gr'] = "Σταθμευμένες κλήσεις";
|
||||
$text['label-parked_calls']['es-cl'] = "Llamadas aparcadas";
|
||||
$text['label-parked_calls']['es-mx'] = "Llamadas aparcadas";
|
||||
$text['label-parked_calls']['fr-ca'] = "Appels parqués";
|
||||
$text['label-parked_calls']['fr-fr'] = "Appels parqués";
|
||||
$text['label-parked_calls']['he-il'] = "שיחות מושהות";
|
||||
$text['label-parked_calls']['it-it'] = "Chiamate parcheggiate";
|
||||
$text['label-parked_calls']['ka-ge'] = "დაპარკებული ზარები";
|
||||
$text['label-parked_calls']['nl-nl'] = "Geparkeerde oproepen";
|
||||
$text['label-parked_calls']['pl-pl'] = "Zaparkowane połączenia";
|
||||
$text['label-parked_calls']['pt-br'] = "Chamadas estacionadas";
|
||||
$text['label-parked_calls']['pt-pt'] = "Chamadas estacionadas";
|
||||
$text['label-parked_calls']['ro-ro'] = "Apeluri parcate";
|
||||
$text['label-parked_calls']['ru-ru'] = "Припаркоованные вызовы";
|
||||
$text['label-parked_calls']['sv-se'] = "Parkerade samtal";
|
||||
$text['label-parked_calls']['uk-ua'] = "Припарковані дзвінки";
|
||||
$text['label-parked_calls']['tr-tr'] = "Park edilmiş aramalar";
|
||||
$text['label-parked_calls']['zh-cn'] = "已停泊通话";
|
||||
$text['label-parked_calls']['ja-jp'] = "保留中の通話";
|
||||
$text['label-parked_calls']['ko-kr'] = "주차된 통화";
|
||||
$text['label-parked_calls']['en-us'] = "Parked";
|
||||
$text['label-parked_calls']['en-gb'] = "Parked";
|
||||
$text['label-parked_calls']['ar-eg'] = "مركون";
|
||||
$text['label-parked_calls']['de-at'] = "Geparkt";
|
||||
$text['label-parked_calls']['de-ch'] = "Geparkt";
|
||||
$text['label-parked_calls']['de-de'] = "Geparkt";
|
||||
$text['label-parked_calls']['el-gr'] = "Παρκαρισμένο";
|
||||
$text['label-parked_calls']['es-cl'] = "Aparcado";
|
||||
$text['label-parked_calls']['es-mx'] = "Aparcado";
|
||||
$text['label-parked_calls']['fr-ca'] = "Parqué";
|
||||
$text['label-parked_calls']['fr-fr'] = "Parqué";
|
||||
$text['label-parked_calls']['he-il'] = "מושהה";
|
||||
$text['label-parked_calls']['it-it'] = "Parcheggiato";
|
||||
$text['label-parked_calls']['ka-ge'] = "დაპარკებული";
|
||||
$text['label-parked_calls']['nl-nl'] = "Geparkeerd";
|
||||
$text['label-parked_calls']['pl-pl'] = "Zaparkowane";
|
||||
$text['label-parked_calls']['pt-br'] = "Estacionado";
|
||||
$text['label-parked_calls']['pt-pt'] = "Estacionado";
|
||||
$text['label-parked_calls']['ro-ro'] = "Parcat";
|
||||
$text['label-parked_calls']['ru-ru'] = "Припарковано";
|
||||
$text['label-parked_calls']['sv-se'] = "Parkerat";
|
||||
$text['label-parked_calls']['uk-ua'] = "Припарковано";
|
||||
$text['label-parked_calls']['tr-tr'] = "Park edilmiş";
|
||||
$text['label-parked_calls']['zh-cn'] = "已停泊";
|
||||
$text['label-parked_calls']['ja-jp'] = "保留中";
|
||||
$text['label-parked_calls']['ko-kr'] = "주차됨";
|
||||
|
||||
$text['label-no_parked_calls']['en-us'] = "No parked calls";
|
||||
$text['label-no_parked_calls']['en-gb'] = "No parked calls";
|
||||
@@ -583,6 +583,8 @@ $text['button-intercept']['en-us'] = "Intercept";
|
||||
$text['button-intercept']['en-gb'] = "Intercept";
|
||||
$text['button-call']['en-us'] = "Call";
|
||||
$text['button-call']['en-gb'] = "Call";
|
||||
$text['button-call_voicemail']['en-us'] = "Call Voicemail";
|
||||
$text['button-call_voicemail']['en-gb'] = "Call Voicemail";
|
||||
$text['button-reject']['en-us'] = "Reject";
|
||||
$text['button-reject']['en-gb'] = "Reject";
|
||||
$text['button-hangup_caller']['en-us'] = "Hangup Caller";
|
||||
|
||||
@@ -194,6 +194,9 @@
|
||||
// Default auto-park destination for drag/drop parking
|
||||
const park_destination = <?= json_encode($park_destination) ?>;
|
||||
|
||||
// Session ID used in contact photo URLs for cache control
|
||||
const contact_image_sid = <?= json_encode(session_id()) ?>;
|
||||
|
||||
</script>
|
||||
|
||||
<script src="resources/javascript/websocket_client.js?v=<?= $ws_client_hash ?>"></script>
|
||||
@@ -478,6 +481,14 @@
|
||||
line-height: 1;
|
||||
color: inherit;
|
||||
}
|
||||
.op-ext-contact-photo {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.op-ext-info {
|
||||
flex: 1;
|
||||
padding: 5px 8px 5px 8px;
|
||||
@@ -731,7 +742,7 @@ body.op-dragging, body.op-dragging * {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Top row in Extensions tab: My Extensions + Parked Calls */
|
||||
/* Top row in Extensions tab: My Extensions + Parked */
|
||||
.op-top-row {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
@@ -750,34 +761,34 @@ body.op-dragging, body.op-dragging * {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* Parked calls side panel */
|
||||
/* Parked side panel */
|
||||
.op-parked-card {
|
||||
border: 1px solid #d0d8e5;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 3px #d0d8e5;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.op-parked-header {
|
||||
background-color: #e5e9f0;
|
||||
padding: 8px 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #444;
|
||||
border-right: 1px solid #d0d8e5;
|
||||
font-family: Calibri, Candara, Segoe, 'Segoe UI', Optima, Arial, sans-serif;
|
||||
writing-mode: vertical-rl;
|
||||
text-orientation: mixed;
|
||||
transform: rotate(180deg);
|
||||
letter-spacing: .6px;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #e5e9f0;
|
||||
border-bottom: 1px solid #d0d8e5;
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #444;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .4px;
|
||||
}
|
||||
.op-parked-badge {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
background: #6c757d;
|
||||
color: #fff;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
justify-content: center;
|
||||
min-width: 34px;
|
||||
}
|
||||
.op-parked-list {
|
||||
padding: 8px;
|
||||
@@ -955,7 +966,7 @@ body.op-dragging, body.op-dragging * {
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="tab-parked" data-bs-toggle="tab" data-bs-target="#panel-parked"
|
||||
type="button" role="tab" aria-controls="panel-parked" aria-selected="false">
|
||||
<?= htmlspecialchars($text['label-parked_calls'] ?? 'Parked Calls') ?>
|
||||
<?= htmlspecialchars($text['label-parked_calls'] ?? 'Parked') ?>
|
||||
<span id="parked_count" class="badge ms-1" style="background:#6c757d;color:#fff;">0</span>
|
||||
</button>
|
||||
</li>
|
||||
@@ -1033,7 +1044,7 @@ body.op-dragging, body.op-dragging * {
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- PARKED CALLS TAB -->
|
||||
<!-- PARKED TAB -->
|
||||
<?php if ($perm['operator_panel_extensions']): ?>
|
||||
<div class="tab-pane fade" id="panel-parked" role="tabpanel" aria-labelledby="tab-parked">
|
||||
<div id="parked_filter_bar" class="op-filter-bar" style="display:none;">
|
||||
|
||||
@@ -534,14 +534,19 @@ class operator_panel_service extends base_websocket_system_service implements we
|
||||
$user_status_map = [];
|
||||
try {
|
||||
$t_us0 = microtime(true);
|
||||
$sql = "SELECT e.extension, eu.user_uuid, COALESCE(u.user_status, '') AS user_status "
|
||||
$sql = "SELECT e.extension, eu.user_uuid, COALESCE(u.user_status, '') AS user_status,"
|
||||
. " ca.contact_attachment_uuid AS contact_image "
|
||||
. "FROM v_extensions AS e "
|
||||
. "LEFT JOIN v_domains AS d ON e.domain_uuid = d.domain_uuid "
|
||||
. "LEFT JOIN ("
|
||||
. "SELECT extension_uuid, MIN(user_uuid) AS user_uuid "
|
||||
. "FROM v_extension_users GROUP BY extension_uuid"
|
||||
. "SELECT DISTINCT ON (extension_uuid) extension_uuid, user_uuid "
|
||||
. "FROM v_extension_users ORDER BY extension_uuid"
|
||||
. ") AS eu ON eu.extension_uuid = e.extension_uuid "
|
||||
. "LEFT JOIN v_users AS u ON u.user_uuid = eu.user_uuid "
|
||||
. "LEFT JOIN v_contact_attachments AS ca ON ca.contact_uuid = u.contact_uuid "
|
||||
. "AND ca.attachment_primary = true "
|
||||
. "AND ca.attachment_filename IS NOT NULL "
|
||||
. "AND ca.attachment_content IS NOT NULL "
|
||||
. "WHERE d.domain_name = :domain_name AND e.enabled = 'true'";
|
||||
|
||||
$rows = $database->select($sql, [':domain_name' => $domain_name], 'all');
|
||||
@@ -557,8 +562,9 @@ class operator_panel_service extends base_websocket_system_service implements we
|
||||
$ext_num = $row['extension'] ?? '';
|
||||
if ($ext_num === '') continue;
|
||||
$user_status_map[$ext_num] = [
|
||||
'user_uuid' => $row['user_uuid'] ?? null,
|
||||
'user_status' => $row['user_status'] ?? '',
|
||||
'user_uuid' => $row['user_uuid'] ?? null,
|
||||
'user_status' => $row['user_status'] ?? '',
|
||||
'contact_image' => $row['contact_image'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -611,6 +617,7 @@ class operator_panel_service extends base_websocket_system_service implements we
|
||||
$ext['registration_count'] = $registered_map[$ext_num] ?? 0;
|
||||
$ext['user_uuid'] = $user_status_map[$ext_num]['user_uuid'] ?? null;
|
||||
$ext['user_status'] = $user_status_map[$ext_num]['user_status'] ?? '';
|
||||
$ext['contact_image'] = $user_status_map[$ext_num]['contact_image'] ?? null;
|
||||
}
|
||||
unset($ext);
|
||||
$this->debug('extensions_active trace [step6] annotation complete: extensions=' . count($extensions));
|
||||
|
||||
@@ -888,14 +888,11 @@ function render_parked_side_card() {
|
||||
if (!container) return;
|
||||
|
||||
const parked_calls = get_sorted_parked_calls();
|
||||
const title = esc(text['label-parked_calls'] || 'Parked Calls');
|
||||
const title = esc(text['label-parked_calls'] || 'Parked');
|
||||
const no_parked = esc(text['label-no_parked_calls'] || 'No parked calls');
|
||||
|
||||
let html = `<div class="op-parked-card" ondragover="on_parked_dragover(event)" ondragleave="on_parked_dragleave(event)" ondrop="on_parked_drop(event)">`;
|
||||
html += `<div class="op-parked-header">`;
|
||||
html += `<span>${title}</span>`;
|
||||
html += `<span class="op-parked-badge">${parked_calls.length}</span>`;
|
||||
html += `</div>`;
|
||||
html += `<div class="op-parked-header">${title}</div>`;
|
||||
|
||||
if (!parked_calls.length) {
|
||||
html += `<div class="op-parked-empty">${no_parked}</div>`;
|
||||
@@ -1735,6 +1732,8 @@ function on_ext_contextmenu(event, ext_num) {
|
||||
const is_mine = !!(block && block.classList.contains('op-ext-mine'));
|
||||
const { state, call_info } = get_extension_call_state(ext_num);
|
||||
const has_call = !!uuid;
|
||||
const ext_data = extensions_map.get(ext_num) || {};
|
||||
const voicemail_enabled = (ext_data.voicemail_enabled || '') === 'true';
|
||||
|
||||
// Derive call direction for the ringing extension to suppress Reject on outbound calls.
|
||||
const call_dest = ((call_info || {}).caller_destination_number || '').trim();
|
||||
@@ -1762,6 +1761,11 @@ function on_ext_contextmenu(event, ext_num) {
|
||||
items.push({ label: text['button-call'] || 'Call', icon_class: 'fa-solid fa-phone',
|
||||
fn: function () { action_call_extension(ext_num); }
|
||||
});
|
||||
if (voicemail_enabled) {
|
||||
items.push({ label: text['button-call_voicemail'] || 'Call Voicemail', icon_class: 'fa-solid fa-voicemail',
|
||||
fn: function () { action_call_voicemail(ext_num); }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (state === 'ringing') {
|
||||
@@ -1888,6 +1892,15 @@ function action_call_extension(ext_num) {
|
||||
send_action('originate', { source: from_ext, destination: ext_num }).catch(console.error);
|
||||
}
|
||||
|
||||
function action_call_voicemail(ext_num) {
|
||||
if (!Array.isArray(user_own_extensions) || user_own_extensions.length === 0) return;
|
||||
const from_ext = user_own_extensions.length === 1
|
||||
? user_own_extensions[0]
|
||||
: prompt(text['label-your_extension'] || 'Your extension:');
|
||||
if (!from_ext) return;
|
||||
send_action('originate', { source: from_ext, destination: '*99' + ext_num }).catch(console.error);
|
||||
}
|
||||
|
||||
function action_intercept_icon(uuid, target_ext) {
|
||||
if (!uuid) return;
|
||||
const from_ext = (Array.isArray(user_own_extensions) && user_own_extensions.length === 1)
|
||||
@@ -2585,7 +2598,9 @@ function render_ext_block(ext, is_mine) {
|
||||
` ondragleave="event.currentTarget.classList.remove('op-ext-drop-over')"` +
|
||||
` ondrop="on_ext_drop('${esc(num)}', event)"` +
|
||||
` oncontextmenu="on_ext_contextmenu(event, '${esc(num)}')">` +
|
||||
`<div class="op-ext-icon" title="${esc(status_hover)}"><img class="op-ext-status-icon" src="../operator_panel/resources/images/${status_icon}.png" width="28" height="28" alt="${esc(status_hover)}"></div>` +
|
||||
(ext.contact_image
|
||||
? `<div class="op-ext-icon" title="${esc(status_hover)}"><div class="op-ext-contact-photo" style="background-image: url('/core/contacts/contact_attachment.php?id=${esc(ext.contact_image)}&action=download&sid=${esc(contact_image_sid)}')"></div></div>`
|
||||
: `<div class="op-ext-icon" title="${esc(status_hover)}"><img class="op-ext-status-icon" src="../operator_panel/resources/images/${status_icon}.png" width="28" height="28" alt="${esc(status_hover)}"></div>`) +
|
||||
`<div class="op-ext-info${has_live_call ? ' op-has-live-call' : ''}">` +
|
||||
`<div class="op-ext-number">${esc(num)}</div>` +
|
||||
dialpad_html +
|
||||
|
||||
Reference in New Issue
Block a user