fix: embed conversations data inline in chat page HTML

Instead of making a separate fetch to /admin/chat/conversations (which
Nginx intercepts), pre-load conversations in the PHP chat() method and
embed them as window.INITIAL_CONVS JSON in the page script.

renderConvs(INITIAL_CONVS) runs synchronously on page load — no HTTP
request needed. setInterval still tries to refresh via fetch every 15s
but the initial render no longer depends on it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-06-27 19:51:15 -05:00
co-authored by Claude Sonnet 4.6
parent 5bae03e5a3
commit 0be3d467ff
+64 -27
View File
@@ -1808,7 +1808,37 @@ HTML;
SessionAuth::require();
$user = SessionAuth::user();
$userName = self::h($user['name'] ?? 'Admin');
$companies = CompanyRepository::findAll();
// Pre-load conversations for inline embedding (no extra HTTP round-trip)
$companyMap = [];
foreach (CompanyRepository::findAll() as $co) {
$companyMap[(int)$co['id']] = $co['name'];
}
$convRows = db()->query("
SELECT c.phone_number,
MAX(c.contact_name) as contact_name,
MAX(c.company_id) as company_id,
MAX(c.created_at) as last_time,
(SELECT content FROM conversations c2 WHERE c2.phone_number = c.phone_number ORDER BY c2.id DESC LIMIT 1) as last_message,
(SELECT COUNT(*) FROM conversations c3 WHERE c3.phone_number = c.phone_number AND c3.direction = 'inbound'
AND c3.id > COALESCE((SELECT MAX(c4.id) FROM conversations c4 WHERE c4.phone_number = c.phone_number AND c4.direction = 'outbound'),0)) as unread_count
FROM conversations c
GROUP BY c.phone_number
ORDER BY last_time DESC
")->fetchAll();
$initialConvs = [];
foreach ($convRows as $r) {
$cid = (int)$r['company_id'];
$initialConvs[] = [
'phone' => $r['phone_number'],
'contact_name' => $r['contact_name'] ?? '',
'company_name' => $companyMap[$cid] ?? '',
'last_message' => mb_substr($r['last_message'] ?? '', 0, 80),
'last_time' => $r['last_time'],
'unread_count' => (int)$r['unread_count'],
];
}
$initialConvsJson = json_encode($initialConvs, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT);
http_response_code(200);
header('Content-Type: text/html; charset=utf-8');
@@ -1917,16 +1947,42 @@ HTML;
</div>
<script>
let activePhone = '';
const INITIAL_CONVS = {$initialConvsJson};
// ─── Render conversations list ───────────────────────────────────────────────
function renderConvs(convs) {
const list = document.getElementById('convList');
if (!convs || convs.length === 0) {
list.innerHTML = '<div style="padding:24px 16px;text-align:center;color:#a0a8b8;font-size:13px">Sin conversaciones aún</div>';
return;
}
list.innerHTML = convs.map(c => {
const time = formatTime(c.last_time);
const name = c.contact_name || c.phone || '';
const avatar = name.charAt(0).toUpperCase() || '?';
const unread = c.unread_count > 0 ? \`<div class="conv-unread">\${c.unread_count}</div>\` : '';
const active = c.phone === activePhone ? ' active' : '';
const company = c.company_name ? \`<span class="conv-company">\${esc(c.company_name)}</span>\` : '';
return \`<div class="conv-item\${active}" onclick="selectConv('\${esc(c.phone)}','\${esc(c.contact_name||c.phone)}','\${esc(c.company_name||'')}')">
<div class="conv-avatar">\${avatar}</div>
<div class="conv-info">
<div class="conv-name">\${esc(name || 'Desconocido')} \${company}</div>
<div class="conv-phone">\${esc(c.phone)}</div>
<div class="conv-last">\${esc(c.last_message || '')}</div>
</div>
<div class="conv-meta">
<div class="conv-time">\${time}</div>
\${unread}
</div>
</div>\`;
}).join('');
}
// ─── Load conversations ──────────────────────────────────────────────────────
async function loadConvs() {
const list = document.getElementById('convList');
try {
const r = await fetch('/admin/chat?api=convs');
if (!r.ok) {
list.innerHTML = \`<div style="padding:16px;color:#e53e3e;font-size:13px">Error \${r.status} al cargar conversaciones</div>\`;
return;
}
if (!r.ok) return;
const data = await r.json();
const convs = data.conversations || [];
if (convs.length === 0) {
@@ -1934,28 +1990,9 @@ async function loadConvs() {
return;
}
list.innerHTML = convs.map(c => {
const time = formatTime(c.last_time);
const name = c.contact_name || c.phone || '';
const avatar = name.charAt(0).toUpperCase() || '?';
const unread = c.unread_count > 0 ? \`<div class="conv-unread">\${c.unread_count}</div>\` : '';
const active = c.phone === activePhone ? ' active' : '';
const company = c.company_name ? \`<span class="conv-company">\${esc(c.company_name)}</span>\` : '';
return \`<div class="conv-item\${active}" onclick="selectConv('\${esc(c.phone)}','\${esc(c.contact_name||c.phone)}','\${esc(c.company_name||'')}')">
<div class="conv-avatar">\${avatar}</div>
<div class="conv-info">
<div class="conv-name">\${esc(name || 'Desconocido')} \${company}</div>
<div class="conv-phone">\${esc(c.phone)}</div>
<div class="conv-last">\${esc(c.last_message || '')}</div>
</div>
<div class="conv-meta">
<div class="conv-time">\${time}</div>
\${unread}
</div>
</div>\`;
}).join('');
renderConvs(convs);
} catch (e) {
console.error('loadConvs error:', e);
list.innerHTML = '<div style="padding:16px;color:#e53e3e;font-size:13px">Error al cargar conversaciones. Revisa la consola.</div>';
}
}
@@ -2123,7 +2160,7 @@ if (msgInput) {
});
}
loadConvs();
renderConvs(INITIAL_CONVS);
setInterval(loadConvs, 15000);
</script>
</body>