From b91ccba3130ed543d9c0451401057acac2349a57 Mon Sep 17 00:00:00 2001 From: marsalva Date: Sat, 7 Mar 2026 17:07:46 +0000 Subject: [PATCH] Actualizar calendario.html --- calendario.html | 163 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 142 insertions(+), 21 deletions(-) diff --git a/calendario.html b/calendario.html index f3627de..937c03f 100644 --- a/calendario.html +++ b/calendario.html @@ -44,6 +44,10 @@ /* Caja de descripción sobria */ .desc-box { background: white; border: 1px solid #e2e8f0; border-radius: 1.5rem; padding: 1.25rem; } + + /* Estilos para el chat */ + .msg-me { background-color: #dcfce7; border-bottom-right-radius: 4px; border: 1px solid #bbf7d0; align-self: flex-end; } + .msg-other { background-color: #f1f5f9; border-bottom-left-radius: 4px; border: 1px solid #e2e8f0; align-self: flex-start; } @@ -126,11 +130,12 @@

Compañía

-

Nombre Cliente

+

Nombre Cliente

+ + - @@ -139,6 +144,25 @@ +
+ + +
+

Ubicación

@@ -353,7 +377,6 @@ const modalSelect = document.getElementById('detStatusMap'); modalSelect.innerHTML = ''; systemStatuses.forEach(st => { - // Modificación: Solo el nombre en el select para diseño más limpio modalSelect.innerHTML += ``; }); } @@ -395,7 +418,6 @@ } } - // Mapa de colores (Mismo que en proveedores) const colorMap = { 'gray': { text: 'text-slate-500', bg: 'bg-slate-100', border: 'border-slate-200' }, 'blue': { text: 'text-blue-600', bg: 'bg-blue-50', border: 'border-blue-100' }, @@ -433,7 +455,6 @@ let compShort = (raw["Compañía"] || "Particular").split('-')[0].trim().substring(0, 15); const guildObj = systemGuilds.find(g => String(g.id) === String(s.guild_id || raw.guild_id)); - // MODIFICACIÓN 1: LÓGICA DE ICONO Y COLOR SEGÚN ESTADO let iconName = "clock"; const dbStatId = raw.status_operativo; const statusObj = systemStatuses.find(st => String(st.id) === String(dbStatId)); @@ -520,6 +541,11 @@ modal.style.display = 'flex'; setTimeout(() => modal.classList.remove('translate-y-full'), 10); calculateDistance(fullAddress); + + // Cerrar chat al abrir otro servicio + document.getElementById('chatContainer').classList.add('hidden'); + document.getElementById('chatChevron').classList.remove('rotate-180'); + safeLoadIcons(); } @@ -529,17 +555,15 @@ setTimeout(() => modal.style.display = 'none', 300); } - // --- FUNCIÓN SECRETA DE LOGS --- function sendLog(action, details) { if(!currentServiceId) return; fetch(`${API_URL}/services/${currentServiceId}/log`, { method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify({ action, details }) - }).catch(()=>{}); // Si falla, que no rompa la app, es un log silencioso + }).catch(()=>{}); } - // --- BOTONES MODIFICADOS --- function callClient() { const p = document.getElementById('detPhoneRaw').value; if (p) { @@ -601,6 +625,7 @@ lucide.createIcons(); } } + function openMaps() { const a = document.getElementById('detAddress').innerText; if (a) { @@ -698,28 +723,21 @@ } catch (e) { alert("Error al actualizar"); } } - // MODIFICACIÓN 3: LÓGICA DE FINALIZAR CON PREGUNTA DE ENCUESTA async function askToFinish() { if(!currentServiceId) return; const stFinalizado = systemStatuses.find(s => s.name.toLowerCase().includes('finaliza')); if(!stFinalizado) return alert("El sistema no tiene un estado Finalizado válido."); - // Preguntamos explícitamente si se envía la encuesta const sendSurvey = confirm("¿Deseas enviar la encuesta de satisfacción al cliente antes de finalizar?"); try { - // Al poner el estado a Finalizado, el servidor borra el aviso de la agenda. - // Le pasamos al servidor un "flag" para que sepa si envía o no envía el WhatsApp de la encuesta. - // NOTA: Como la ruta de server.js actual dispara automáticamente el WA al ver 'finalizado', - // le pasamos un booleano en el body "skip_survey" para que el servidor lo bloquee si eligió NO. - const res = await fetch(`${API_URL}/services/set-appointment/${currentServiceId}`, { method: 'PUT', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify({ status_operativo: stFinalizado.id, - skip_survey: !sendSurvey // <- Flag que puedes usar en tu server.js si quieres anularlo + skip_survey: !sendSurvey }) }); @@ -768,9 +786,112 @@ finally { btn.innerHTML = originalContent; btn.disabled = false; safeLoadIcons(); } } - function showToast(m) { - const t = document.getElementById('toast'); document.getElementById('toastMsg').innerText = m; - t.classList.remove('opacity-0', 'pointer-events-none', '-translate-y-10'); t.classList.add('translate-y-0'); + // --- SISTEMA DE CHAT OPERARIO --- + function toggleChat() { + const container = document.getElementById('chatContainer'); + const chevron = document.getElementById('chatChevron'); + + if (container.classList.contains('hidden')) { + container.classList.remove('hidden'); + container.classList.add('flex'); + chevron.classList.add('rotate-180'); + loadChat(currentServiceId); + } else { + container.classList.add('hidden'); + container.classList.remove('flex'); + chevron.classList.remove('rotate-180'); + } + } + + async function loadChat(serviceId) { + const chatBox = document.getElementById('chatBox'); + chatBox.innerHTML = '
Cargando...
'; + safeLoadIcons(); + + try { + const res = await fetch(`${API_URL}/services/${serviceId}/chat`, { + headers: { 'Authorization': `Bearer ${localStorage.getItem("token")}` } + }); + const data = await res.json(); + + if (data.ok) { + chatBox.innerHTML = ''; + if (data.messages.length === 0) { + chatBox.innerHTML = '
No hay mensajes aún
'; + return; + } + + data.messages.forEach(msg => { + // Ocultar notas internas si por alguna razón el server las mandara + if (msg.is_internal) return; + + const isMe = msg.sender_role === 'operario' || msg.sender_role === 'operario_cerrado'; + const time = new Date(msg.created_at).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' }); + const date = new Date(msg.created_at).toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit' }); + + let bubbleClass = isMe ? 'msg-me' : 'msg-other'; + let headerColor = isMe ? 'text-emerald-700' : 'text-slate-600'; + + const bubbleHtml = ` +
+
+ ${isMe ? 'Tú' : msg.sender_name} + ${date} ${time} +
+

${msg.message}

+
+ `; + chatBox.innerHTML += bubbleHtml; + }); + + setTimeout(() => { chatBox.scrollTop = chatBox.scrollHeight; }, 100); + } + } catch (e) { + chatBox.innerHTML = '
Error de carga
'; + } + } + + async function sendChatMessage() { + if(!currentServiceId) return; + const input = document.getElementById('chatInput'); + const message = input.value.trim(); + + if (!message) return; + + input.disabled = true; + try { + const res = await fetch(`${API_URL}/services/${currentServiceId}/chat`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem("token")}` }, + body: JSON.stringify({ message, is_internal: false }) + }); + + if (res.ok) { + input.value = ''; + loadChat(currentServiceId); + } else { + showToast("Error al enviar mensaje"); + } + } catch (e) { + showToast("Error de conexión"); + } finally { + input.disabled = false; + } + } + + function showToast(m, isError = false) { + const t = document.getElementById('toast'); + document.getElementById('toastMsg').innerText = m; + if(isError) { + t.classList.replace('bg-slate-900', 'bg-red-600'); + t.innerHTML = ` ${m}`; + } else { + t.classList.replace('bg-red-600', 'bg-slate-900'); + t.innerHTML = ` ${m}`; + } + safeLoadIcons(); + t.classList.remove('opacity-0', 'pointer-events-none', '-translate-y-10'); + t.classList.add('translate-y-0'); setTimeout(() => { t.classList.add('opacity-0', 'pointer-events-none', '-translate-y-10'); t.classList.remove('translate-y-0'); }, 2000); }