Fix time and reshape parked call block (#7824)

This commit is contained in:
frytimo
2026-03-31 16:53:24 -03:00
committed by GitHub
parent 1a915bb21c
commit 61c3040ec6
2 changed files with 106 additions and 29 deletions
+41 -10
View File
@@ -783,21 +783,52 @@ body.op-dragging, body.op-dragging * {
padding: 8px;
max-height: 280px;
overflow: auto;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.op-parked-item {
border: 1px solid #d0d8e5;
border-radius: 4px;
background: #f8fbff;
padding: 6px 8px;
margin-bottom: 7px;
width: 235px;
min-height: 50px;
border-style: solid;
border-width: 1px 3px;
border-color: #77d779;
border-radius: 5px;
background: #baf4bb;
box-shadow: 0 0 3px #c8cdd9;
padding: 5px 8px;
cursor: grab;
user-select: none;
line-height: 1.25;
line-height: 1.2;
position: relative;
overflow: hidden;
}
.op-parked-item:hover { background: #c8f6c9; border-color: #4fc453; }
.op-parked-main {
font-size: 12px;
font-weight: 700;
color: #3164AD;
padding-right: 74px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.op-parked-sub {
font-size: 10px;
color: #444;
margin-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.op-parked-duration {
position: absolute;
top: 6px;
right: 8px;
font-size: 12px;
color: #4a4a4a;
line-height: 1;
}
.op-parked-item:last-child { margin-bottom: 0; }
.op-parked-item:hover { background: #eef5ff; border-color: #80bdff; }
.op-parked-main { font-size: 12px; font-weight: 600; color: #2c3e50; }
.op-parked-sub { font-size: 11px; color: #555; margin-top: 2px; }
.op-parked-drop-over {
box-shadow: 0 0 0 3px #0d6efd;
border-color: #0d6efd;
@@ -91,6 +91,10 @@ let dragged_parked_uuid = null;
/** UUIDs recently removed from parked state; suppressed from snapshots briefly. */
const parked_suppress_map = new Map();
/** Persistent store of the first valid parked_since_us seen per UUID.
* Survives parked_calls_map.clear() so duration never resets on snapshot refresh. */
const parked_since_known = new Map();
/** Current user's status; this is the only status source for My Extensions cards. */
let current_user_status = user_status.trim();
@@ -530,10 +534,44 @@ function get_conference_action_icon(name, fallback) {
return fallback;
}
/** Normalize epoch-like timestamps to Unix microseconds as a numeric string. */
function normalize_epoch_us(raw_value) {
if (raw_value === null || raw_value === undefined) return '0';
const value = String(raw_value).trim();
if (!value) return '0';
// Fast path: pure integer string (seconds/ms/us/ns).
if (/^\d+$/.test(value)) {
if (value.length <= 10) return `${value}000000`; // seconds -> us
if (value.length <= 13) return `${value}000`; // ms -> us
if (value.length <= 16) return value; // already us
if (value.length <= 19) return String(Math.floor(Number(value) / 1000)); // ns -> us
return value.slice(0, 16);
}
// Common case from some APIs: decimal seconds (e.g. 1711864980.123456).
if (/^\d+\.\d+$/.test(value)) {
const float_val = Number(value);
if (Number.isFinite(float_val) && float_val > 0) {
return String(Math.floor(float_val * 1000000));
}
}
// Fallback: pull a 10-19 digit run from mixed strings.
const match = value.match(/(\d{10,19})/);
if (match && match[1]) {
return normalize_epoch_us(match[1]);
}
return '0';
}
/** Format a Unix microsecond timestamp as elapsed time hh:mm:ss */
function format_elapsed(us_timestamp) {
if (!us_timestamp || us_timestamp === '0') return '--:--:--';
const start = Math.floor(Number(us_timestamp) / 1000000);
const normalized_us = normalize_epoch_us(us_timestamp);
if (!normalized_us || normalized_us === '0') return '--:--:--';
const start = Math.floor(Number(normalized_us) / 1000000);
if (!Number.isFinite(start) || start <= 0) return '--:--:--';
const now = Math.floor(Date.now() / 1000);
let sec = Math.max(0, now - start);
const h = Math.floor(sec / 3600); sec -= h * 3600;
@@ -545,7 +583,7 @@ function format_elapsed(us_timestamp) {
function tick_durations() {
document.querySelectorAll('[data-created]').forEach(el => {
const ts = el.getAttribute('data-created');
if (ts && ts !== '0') {
if (normalize_epoch_us(ts) !== '0') {
el.textContent = format_elapsed(ts);
}
});
@@ -584,18 +622,16 @@ function get_parked_since_us(ch) {
ch.parked_epoch,
ch.variable_parked_epoch,
ch.variable_park_epoch,
ch.event_date_timestamp,
ch.caller_channel_created_time,
ch.variable_start_uepoch,
ch.start_uepoch,
ch.variable_start_epoch,
ch.start_epoch,
ch.event_date_timestamp,
];
for (const value of candidates) {
if (!value) continue;
const raw = String(value).trim();
if (!raw) continue;
if (/^\d+$/.test(raw)) {
if (raw.length <= 10) return `${raw}000000`;
if (raw.length <= 13) return `${raw}000`;
return raw;
}
const normalized = normalize_epoch_us(value);
if (normalized !== '0') return normalized;
}
return '0';
}
@@ -690,12 +726,23 @@ function upsert_parked_call(ch) {
parked_suppress_map.delete(normalized.uuid);
}
const current = parked_calls_map.get(normalized.uuid) || {};
// Keep the first valid parked_since_us we ever saw for this UUID.
// parked_since_known persists across parked_calls_map.clear() so snapshot
// refreshes and rebuild_parked_calls_map cannot wipe the live duration.
if (normalized.parked_since_us && normalized.parked_since_us !== '0') {
parked_since_known.set(normalized.uuid, normalized.parked_since_us);
} else if (parked_since_known.has(normalized.uuid)) {
normalized.parked_since_us = parked_since_known.get(normalized.uuid);
}
parked_calls_map.set(normalized.uuid, Object.assign(current, normalized));
}
function remove_parked_call_by_uuid(uuid) {
if (!uuid) return;
parked_calls_map.delete(uuid);
parked_since_known.delete(uuid);
// Suppress this UUID from being re-added by snapshots for a short window
parked_suppress_map.set(uuid, Date.now() + 6000);
}
@@ -862,18 +909,17 @@ function render_parked_side_card() {
parked_calls.forEach(p => {
const uuid = esc(p.uuid);
const caller = esc((p.caller_id_name || '').trim() || (p.caller_id_number || 'Unknown'));
const caller_num = esc(p.caller_id_number || '-');
const lot = esc(p.parking_lot || '-');
const dest = esc(p.original_destination || p.caller_id_number || '-');
const parked_by = esc(p.parked_by || '-');
const dest = esc(p.original_destination || '-');
const parked_since = esc(p.parked_since_us || '0');
html += `<div class="op-parked-item" draggable="true" data-uuid="${uuid}" data-group-key="${esc(p.group_key || '')}"`;
html += ` ondragstart="on_drag_parked('${uuid}', event)" ondragend="on_drag_end()">`;
html += `<div class="op-parked-main">${caller}</div>`;
html += `<div class="op-parked-sub">${esc(text['label-caller_id'] || 'Caller ID')}: ${caller_num}</div>`;
html += `<div class="op-parked-sub">${esc(text['label-parking_lot'] || 'Parking Lot')}: ${lot}</div>`;
html += `<div class="op-parked-sub">${esc(text['label-duration'] || 'Duration')}: <span class="mono" data-created="${esc(p.parked_since_us || '0')}">${esc(format_elapsed(p.parked_since_us || '0'))}</span></div>`;
html += `<span class="op-parked-duration mono" data-created="${parked_since}">${esc(format_elapsed(parked_since))}</span>`;
html += `<div class="op-parked-main">${lot}</div>`;
html += `<div class="op-parked-sub">${caller}</div>`;
html += `<div class="op-parked-sub">${esc(text['label-on_call'] || 'On Call')}: ${dest}</div>`;
html += `<div class="op-parked-sub">${esc(text['label-parked_by'] || 'Parked By')}: ${parked_by}</div>`;
html += `<div class="op-parked-sub">${esc(text['label-original_destination'] || 'Original Destination')}: ${dest}</div>`;
html += `</div>`;
});
html += `</div></div>`;