/** * whatsapp.js — WhatsApp Business conversation UI * * Left panel: conversation list (phone numbers + last message preview) * Right panel: chat view with message bubbles * Bottom: text input + send button * Toolbar: "Enviar Cotizacion" button */ (function () { 'use strict'; var token = localStorage.getItem('pos_token'); if (!token) { window.location.href = '/pos/login'; return; } var API = '/pos/api/whatsapp'; var activePhone = null; var pollTimer = null; // ── Helpers ────────────────────────────────────────────────────────── function authHeaders() { return { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }; } function api(method, path, body) { var opts = { method: method, headers: authHeaders() }; if (body) opts.body = JSON.stringify(body); return fetch(API + path, opts).then(function (r) { if (r.status === 401) { window.location.href = '/pos/login'; } return r.json(); }); } function escHtml(s) { var d = document.createElement('div'); d.textContent = s || ''; return d.innerHTML; } function fmtTime(iso) { if (!iso) return ''; var d = new Date(iso); var now = new Date(); var isToday = d.toDateString() === now.toDateString(); if (isToday) { return d.toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit' }); } return d.toLocaleDateString('es-MX', { day: '2-digit', month: 'short' }) + ' ' + d.toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit' }); } function fmtPhone(phone) { if (!phone) return ''; // Format Mexican numbers nicely: 52 1 55 1234 5678 if (phone.length === 13 && phone.startsWith('521')) { return '+52 1 ' + phone.slice(3, 5) + ' ' + phone.slice(5, 9) + ' ' + phone.slice(9); } if (phone.length === 12 && phone.startsWith('52')) { return '+52 ' + phone.slice(2, 4) + ' ' + phone.slice(4, 8) + ' ' + phone.slice(8); } return '+' + phone; } // ── DOM refs ──────────────────────────────────────────────────────── var convList = document.getElementById('convList'); var chatMessages = document.getElementById('chatMessages'); var chatHeader = document.getElementById('chatHeaderPhone'); var chatInput = document.getElementById('chatInput'); var sendBtn = document.getElementById('sendBtn'); var newChatBtn = document.getElementById('newChatBtn'); var emptyState = document.getElementById('emptyState'); var chatPanel = document.getElementById('chatPanel'); var statusDot = document.getElementById('statusDot'); var statusText = document.getElementById('statusText'); // ── Load conversations ────────────────────────────────────────────── function loadConversations() { api('GET', '/conversations').then(function (data) { var convs = data.conversations || []; if (convs.length === 0) { convList.innerHTML = '
No hay conversaciones
'; return; } var html = ''; convs.forEach(function (c) { var isActive = c.phone === activePhone; var dirIcon = c.last_direction === 'outgoing' ? '→ ' : ''; html += '
' + '
' + escHtml(fmtPhone(c.phone)) + '
' + '
' + dirIcon + escHtml(c.last_message) + '
' + '
' + fmtTime(c.last_at) + '
' + '
'; }); convList.innerHTML = html; // Click handlers convList.querySelectorAll('.conv-item').forEach(function (el) { el.addEventListener('click', function () { openConversation(el.getAttribute('data-phone')); }); }); }).catch(function () { convList.innerHTML = '
Error cargando conversaciones
'; }); } // ── Open a conversation ───────────────────────────────────────────── function openConversation(phone) { activePhone = phone; chatHeader.textContent = fmtPhone(phone); emptyState.style.display = 'none'; chatPanel.style.display = 'flex'; // Highlight in list convList.querySelectorAll('.conv-item').forEach(function (el) { el.classList.toggle('is-active', el.getAttribute('data-phone') === phone); }); loadMessages(phone); startPolling(); } function loadMessages(phone) { api('GET', '/conversations/' + encodeURIComponent(phone)).then(function (data) { var msgs = data.messages || []; renderMessages(msgs); }); } function renderMessages(msgs) { var html = ''; msgs.forEach(function (m) { var cls = m.direction === 'outgoing' ? 'msg-bubble--out' : 'msg-bubble--in'; var statusBadge = ''; if (m.direction === 'outgoing' && m.status) { statusBadge = '' + escHtml(m.status) + ''; } html += '
' + '
' + escHtml(m.message_text).replace(/\n/g, '
') + '
' + '
' + fmtTime(m.created_at) + ' ' + statusBadge + '
' + '
'; }); chatMessages.innerHTML = html || '
Sin mensajes
'; chatMessages.scrollTop = chatMessages.scrollHeight; } // ── Send message ──────────────────────────────────────────────────── function doSend() { var text = chatInput.value.trim(); if (!text || !activePhone) return; chatInput.value = ''; sendBtn.disabled = true; api('POST', '/send', { phone: activePhone, message: text }).then(function (res) { sendBtn.disabled = false; if (res.error) { alert('Error: ' + res.error); } else { loadMessages(activePhone); loadConversations(); } }).catch(function () { sendBtn.disabled = false; alert('Error de red al enviar mensaje'); }); } sendBtn.addEventListener('click', doSend); chatInput.addEventListener('keydown', function (e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); doSend(); } }); // ── New conversation ──────────────────────────────────────────────── newChatBtn.addEventListener('click', function () { var phone = prompt('Numero de telefono (formato: 5215512345678):'); if (phone) { phone = phone.replace(/[\s\-\+\(\)]/g, ''); openConversation(phone); loadConversations(); } }); // ── Send quotation modal ──────────────────────────────────────────── var quoteBtn = document.getElementById('sendQuoteBtn'); if (quoteBtn) { quoteBtn.addEventListener('click', function () { if (!activePhone) { alert('Selecciona una conversacion primero'); return; } var quoteId = prompt('ID de la cotizacion a enviar:'); if (!quoteId) return; api('POST', '/send-quote/' + quoteId, { phone: activePhone }).then(function (res) { if (res.error) { alert('Error: ' + res.error); } else { loadMessages(activePhone); loadConversations(); } }); }); } // ── Polling for new messages ──────────────────────────────────────── function startPolling() { if (pollTimer) clearInterval(pollTimer); pollTimer = setInterval(function () { if (activePhone) loadMessages(activePhone); loadConversations(); }, 10000); // every 10s } // ── Connection status indicator ───────────────────────────────────── function checkStatus() { // Check if we can reach the API (proxy for "connected") fetch(API + '/conversations', { headers: authHeaders() }) .then(function (r) { if (r.ok) { statusDot.className = 'status-dot status-dot--ok'; statusText.textContent = 'Conectado'; } else { statusDot.className = 'status-dot status-dot--warn'; statusText.textContent = 'Sin credenciales'; } }) .catch(function () { statusDot.className = 'status-dot status-dot--error'; statusText.textContent = 'Desconectado'; }); } // ── Init ──────────────────────────────────────────────────────────── loadConversations(); checkStatus(); setInterval(checkStatus, 30000); // ── User info for sidebar ─────────────────────────────────────────── try { var payload = JSON.parse(atob(token.split('.')[1])); window.POS_USER = { name: payload.name || 'Usuario', roleLabel: (payload.role || '').charAt(0).toUpperCase() + (payload.role || '').slice(1), initials: (payload.name || 'U').split(' ').map(function(w){return w[0]}).join('').slice(0,2).toUpperCase() }; } catch(e) {} })();