Update conversations.php
This commit is contained in:
+207
-105
@@ -556,20 +556,59 @@
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
/* Fullscreen sidebar that can slide in/out */
|
||||
.chat-container { padding: 0; gap: 0; }
|
||||
.chat-sidebar {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1100;
|
||||
height: 100vh;
|
||||
transform: translateX(0);
|
||||
transition: transform 240ms ease;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
/* Hidden by default on mobile. .open will show it */
|
||||
.chat-sidebar.mobile-hidden { transform: translateX(-110%); }
|
||||
|
||||
.chat-main {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.chat-sidebar.show-chat {
|
||||
transform: translateX(-100%);
|
||||
|
||||
/* Header adjustment */
|
||||
.chat-header { padding: 10px 12px; gap: 8px; }
|
||||
.chat-header-avatar { width: 38px; height: 38px; }
|
||||
|
||||
/* Fix input to bottom to emulate mobile chat apps */
|
||||
.chat-input {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
z-index: 1200;
|
||||
border-top: 1px solid #e9eef2;
|
||||
}
|
||||
|
||||
/* Give chat area bottom padding so messages are not hidden under input */
|
||||
.chat-conversations { padding-bottom: 120px; }
|
||||
|
||||
/* Compact avatars and message bubble width */
|
||||
.conversation-avatar { width: 40px; height: 40px; }
|
||||
.message-bubble { max-width: 85%; font-size: 15px; }
|
||||
|
||||
/* Quick replies panel adapt */
|
||||
.quick-replies-panel { left: 8px; right: 8px; bottom: 70px; max-height: 160px; }
|
||||
|
||||
/* Show a small back button in header to open sidebar */
|
||||
#sidebar-toggle { display: inline-flex; }
|
||||
}
|
||||
|
||||
.reply-preview {
|
||||
@@ -652,6 +691,7 @@
|
||||
|
||||
<div id="chat-area" style="display: none; height: 100%; flex-direction: column;">
|
||||
<div class="chat-header">
|
||||
<button id="sidebar-toggle" class="btn btn-sm btn-outline-light d-md-none me-2" style="display:none; border-radius:8px; padding:6px 8px;" title="Volver a conversaciones"><i class="fas fa-bars"></i></button>
|
||||
<div class="chat-header-avatar" id="chat-avatar">
|
||||
<i class="fas fa-user"></i>
|
||||
</div>
|
||||
@@ -966,25 +1006,6 @@
|
||||
}
|
||||
}
|
||||
};
|
||||
openBtn.className = notification.cool ? 'btn btn-sm btn-light' : 'btn btn-sm btn-primary';
|
||||
openBtn.textContent = 'Abrir';
|
||||
openBtn.onclick = async () => {
|
||||
try {
|
||||
await fetch('api/mark_notification_read.php', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({id: notification.id})});
|
||||
} catch(e) { console.warn('mark_notification_read failed', e); }
|
||||
let data = {};
|
||||
try { data = notification.data ? JSON.parse(notification.data) : {}; } catch(e) {}
|
||||
const userId = data.user_id || notification.user_id;
|
||||
if (userId) {
|
||||
const conv = this.conversations.find(c => c.user_id == userId);
|
||||
if (conv) {
|
||||
this.openConversation(conv.user_id, conv.name, conv.phone_number);
|
||||
} else {
|
||||
this.openConversation(userId, 'Usuario', '');
|
||||
}
|
||||
}
|
||||
removeToast();
|
||||
};
|
||||
|
||||
const dismiss = document.createElement('button');
|
||||
dismiss.className = 'btn btn-sm btn-outline-secondary';
|
||||
@@ -1100,6 +1121,27 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Mobile: sidebar toggle
|
||||
const sidebar = document.querySelector('.chat-sidebar');
|
||||
const sidebarToggle = document.getElementById('sidebar-toggle');
|
||||
if (sidebarToggle && sidebar) {
|
||||
sidebarToggle.addEventListener('click', (ev) => {
|
||||
ev.preventDefault();
|
||||
// toggle mobile-hidden class
|
||||
sidebar.classList.toggle('mobile-hidden');
|
||||
});
|
||||
// ensure sidebar hidden by default on small screens
|
||||
const applyInitialMobile = () => {
|
||||
if (window.innerWidth <= 768) {
|
||||
sidebar.classList.add('mobile-hidden');
|
||||
} else {
|
||||
sidebar.classList.remove('mobile-hidden');
|
||||
}
|
||||
};
|
||||
applyInitialMobile();
|
||||
window.addEventListener('resize', applyInitialMobile);
|
||||
}
|
||||
|
||||
const stopRecBtn = document.getElementById('stop-recording');
|
||||
if (stopRecBtn) stopRecBtn.addEventListener('click', () => this.stopRecording());
|
||||
const cancelRecBtn = document.getElementById('cancel-recording');
|
||||
@@ -1415,6 +1457,12 @@
|
||||
const releaseBtn = document.getElementById('release-hold-btn');
|
||||
const markUnreadBtn = document.getElementById('mark-unread-btn');
|
||||
|
||||
// On mobile, hide the sidebar when opening a conversation so chat occupies full screen
|
||||
const sidebar = document.querySelector('.chat-sidebar');
|
||||
if (sidebar && window.innerWidth <= 768) {
|
||||
sidebar.classList.add('mobile-hidden');
|
||||
}
|
||||
|
||||
if (markUnreadBtn) {
|
||||
markUnreadBtn.style.display = 'inline-block';
|
||||
markUnreadBtn.onclick = async () => {
|
||||
@@ -2353,84 +2401,66 @@
|
||||
}
|
||||
|
||||
renderMediaMessage(message) {
|
||||
if (!message.media_url) return '';
|
||||
|
||||
const mediaType = message.message_type;
|
||||
const mediaUrl = message.media_url;
|
||||
const caption = message.content;
|
||||
|
||||
if (!message) return '';
|
||||
const mediaType = message.message_type || message.media_type || 'text';
|
||||
const mediaUrl = message.media_url || message.media_url_external || '';
|
||||
const caption = message.content || '';
|
||||
const localFile = message.local_file || '';
|
||||
const localThumb = message.local_thumb || '';
|
||||
const thumb = localThumb ? (`/${localThumb}`) : (localFile ? (`/${localFile}`) : (message.media_url_external || mediaUrl));
|
||||
|
||||
let full = '';
|
||||
if (localFile) {
|
||||
full = `/api/version/media-url.php?local=${encodeURIComponent(localFile)}`;
|
||||
} else if (message.media_url_external) {
|
||||
if (message.media_url_external.indexOf('api/get_media.php?id=') !== -1) {
|
||||
const parts = message.media_url_external.split('id=');
|
||||
const mid = parts[1] ? decodeURIComponent(parts[1]) : '';
|
||||
full = `/api/version/media-url.php?id=${encodeURIComponent(mid)}`;
|
||||
} else if (/^https?:\/\//i.test(message.media_url_external)) {
|
||||
full = message.media_url_external;
|
||||
} else {
|
||||
full = `/api/version/media-url.php?url=${encodeURIComponent(message.media_url_external)}`;
|
||||
}
|
||||
} else {
|
||||
full = mediaUrl || '';
|
||||
}
|
||||
|
||||
switch (mediaType) {
|
||||
case 'image':
|
||||
// Usar miniatura local si existe, al abrir expandir con el archivo local completo o con media-url redirect
|
||||
const thumb = msg.local_thumb ? (`/${msg.local_thumb}`) : (msg.local_file ? (`/${msg.local_file}`) : (msg.media_url_external || mediaUrl));
|
||||
let full = '';
|
||||
if (msg.local_file) {
|
||||
full = `/api/version/media-url.php?local=${encodeURIComponent(msg.local_file)}`;
|
||||
} else if (msg.media_url_external) {
|
||||
// Si viene una ruta del tipo api/get_media.php?id=XXX extraer id
|
||||
if (msg.media_url_external.indexOf('api/get_media.php?id=') !== -1) {
|
||||
const parts = msg.media_url_external.split('id=');
|
||||
const mid = parts[1] ? decodeURIComponent(parts[1]) : '';
|
||||
full = `/api/version/media-url.php?id=${encodeURIComponent(mid)}`;
|
||||
} else if (/^https?:\/\//i.test(msg.media_url_external)) {
|
||||
// URL directa
|
||||
full = msg.media_url_external;
|
||||
} else {
|
||||
// fallback: pasar como url
|
||||
full = `/api/version/media-url.php?url=${encodeURIComponent(msg.media_url_external)}`;
|
||||
}
|
||||
} else {
|
||||
full = mediaUrl || '';
|
||||
}
|
||||
return `
|
||||
<div class="message-media">
|
||||
<a href="#" onclick="openImageLightbox(${JSON.stringify(full)}); return false;" title="Abrir imagen">
|
||||
<img src="${thumb}" alt="Imagen" style="cursor:zoom-in">
|
||||
<a href="#" data-full="${escapeHtml(full)}" data-type="image" data-caption="${escapeHtml(caption)}" onclick="openMediaLightbox(this.dataset.full, this.dataset.type, this.dataset.caption); return false;" title="Abrir imagen">
|
||||
<img src="${escapeHtml(thumb)}" alt="Imagen" style="cursor:zoom-in">
|
||||
</a>
|
||||
</div>
|
||||
${caption ? `<div>${caption}</div>` : ''}
|
||||
${caption ? `<div>${escapeHtml(caption)}</div>` : ''}
|
||||
`;
|
||||
|
||||
case 'video':
|
||||
let videoSrc = '';
|
||||
if (msg.local_file) {
|
||||
videoSrc = `/${msg.local_file}`;
|
||||
} else if (msg.media_url_external) {
|
||||
if (msg.media_url_external.indexOf('api/get_media.php?id=') !== -1) {
|
||||
const parts = msg.media_url_external.split('id=');
|
||||
const mid = parts[1] ? decodeURIComponent(parts[1]) : '';
|
||||
videoSrc = `/api/version/media-url.php?id=${encodeURIComponent(mid)}`;
|
||||
} else if (/^https?:\/\//i.test(msg.media_url_external)) {
|
||||
videoSrc = msg.media_url_external;
|
||||
} else {
|
||||
videoSrc = `/api/version/media-url.php?url=${encodeURIComponent(msg.media_url_external)}`;
|
||||
}
|
||||
} else {
|
||||
videoSrc = mediaUrl;
|
||||
}
|
||||
return `
|
||||
<div class="message-media">
|
||||
<video controls>
|
||||
<source src="${videoSrc}" type="video/mp4">
|
||||
Tu navegador no soporta video.
|
||||
</video>
|
||||
<a href="#" data-full="${escapeHtml(full)}" data-type="video" data-caption="${escapeHtml(caption)}" onclick="openMediaLightbox(this.dataset.full, this.dataset.type, this.dataset.caption); return false;" title="Abrir video">
|
||||
<div style="position:relative; display:inline-block;">
|
||||
<img src="${escapeHtml(thumb)}" alt="Video" style="cursor:zoom-in; max-width:100%; border-radius:6px;">
|
||||
<div style="position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); font-size:28px; color:white; text-shadow:0 1px 6px rgba(0,0,0,0.6);"><i class="fas fa-play-circle"></i></div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
${caption ? `<div>${caption}</div>` : ''}
|
||||
${caption ? `<div>${escapeHtml(caption)}</div>` : ''}
|
||||
`;
|
||||
|
||||
case 'audio':
|
||||
case 'audio': {
|
||||
let audioSrc = '';
|
||||
if (msg.local_file) {
|
||||
audioSrc = `/${msg.local_file}`;
|
||||
} else if (msg.media_url_external) {
|
||||
if (msg.media_url_external.indexOf('api/get_media.php?id=') !== -1) {
|
||||
const parts = msg.media_url_external.split('id=');
|
||||
if (localFile) {
|
||||
audioSrc = `/${localFile}`;
|
||||
} else if (message.media_url_external) {
|
||||
if (message.media_url_external.indexOf('api/get_media.php?id=') !== -1) {
|
||||
const parts = message.media_url_external.split('id=');
|
||||
const mid = parts[1] ? decodeURIComponent(parts[1]) : '';
|
||||
audioSrc = `/api/version/media-url.php?id=${encodeURIComponent(mid)}`;
|
||||
} else if (/^https?:\/\//i.test(msg.media_url_external)) {
|
||||
audioSrc = msg.media_url_external;
|
||||
} else if (/^https?:\/\//i.test(message.media_url_external)) {
|
||||
audioSrc = message.media_url_external;
|
||||
} else {
|
||||
audioSrc = `/api/version/media-url.php?url=${encodeURIComponent(msg.media_url_external)}`;
|
||||
audioSrc = `/api/version/media-url.php?url=${encodeURIComponent(message.media_url_external)}`;
|
||||
}
|
||||
} else {
|
||||
audioSrc = mediaUrl;
|
||||
@@ -2438,27 +2468,25 @@
|
||||
return `
|
||||
<div class="message-media">
|
||||
<audio controls>
|
||||
<source src="${audioSrc}" type="audio/mpeg">
|
||||
<source src="${escapeHtml(audioSrc)}" type="audio/mpeg">
|
||||
Tu navegador no soporta audio.
|
||||
</audio>
|
||||
</div>
|
||||
`;
|
||||
|
||||
case 'document':
|
||||
// Para documentos no descargamos automáticamente; abrir la URL externa (o la redirección por media id) para descargar
|
||||
}
|
||||
case 'document': {
|
||||
let docUrl = '';
|
||||
if (msg.local_file) {
|
||||
docUrl = `/api/version/media-url.php?local=${encodeURIComponent(msg.local_file)}&download=1`;
|
||||
} else if (msg.media_url_external) {
|
||||
if (msg.media_url_external.indexOf('api/get_media.php?id=') !== -1) {
|
||||
const parts = msg.media_url_external.split('id=');
|
||||
if (localFile) {
|
||||
docUrl = `/api/version/media-url.php?local=${encodeURIComponent(localFile)}&download=1`;
|
||||
} else if (message.media_url_external) {
|
||||
if (message.media_url_external.indexOf('api/get_media.php?id=') !== -1) {
|
||||
const parts = message.media_url_external.split('id=');
|
||||
const mid = parts[1] ? decodeURIComponent(parts[1]) : '';
|
||||
docUrl = `/api/version/media-url.php?id=${encodeURIComponent(mid)}&download=1`;
|
||||
} else if (/^https?:\/\//i.test(msg.media_url_external)) {
|
||||
docUrl = `/api/version/media-url.php?url=${encodeURIComponent(msg.media_url_external)}&download=1`;
|
||||
} else if (/^https?:\/\//i.test(message.media_url_external)) {
|
||||
docUrl = `/api/version/media-url.php?url=${encodeURIComponent(message.media_url_external)}&download=1`;
|
||||
} else {
|
||||
// fallback
|
||||
docUrl = `/api/version/media-url.php?url=${encodeURIComponent(msg.media_url_external)}&download=1`;
|
||||
docUrl = `/api/version/media-url.php?url=${encodeURIComponent(message.media_url_external)}&download=1`;
|
||||
}
|
||||
}
|
||||
if (!docUrl) {
|
||||
@@ -2466,7 +2494,7 @@
|
||||
<div class="message-document disabled">
|
||||
<i class="fas fa-file-pdf"></i>
|
||||
<div class="document-info">
|
||||
<div class="document-name">${caption || 'Documento'}</div>
|
||||
<div class="document-name">${escapeHtml(caption || 'Documento')}</div>
|
||||
<div class="document-size text-muted">No disponible para descargar</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2476,17 +2504,17 @@
|
||||
<div class="message-document">
|
||||
<i class="fas fa-file-pdf"></i>
|
||||
<div class="document-info">
|
||||
<div class="document-name">${caption || 'Documento'}</div>
|
||||
<div class="document-name">${escapeHtml(caption || 'Documento')}</div>
|
||||
<div class="document-size">
|
||||
<a href="${docUrl}" target="_blank" rel="noopener noreferrer" ${docUrl && docUrl.indexOf('download=1') !== -1 ? 'download' : ''}>Descargar</a>
|
||||
<a href="${escapeHtml(docUrl)}" target="_blank" rel="noopener noreferrer" ${docUrl && docUrl.indexOf('download=1') !== -1 ? 'download' : ''}>Descargar</a>
|
||||
</div>
|
||||
</div>
|
||||
<i class="fas fa-download"></i>
|
||||
</div>
|
||||
`;
|
||||
|
||||
}
|
||||
default:
|
||||
return caption || '';
|
||||
return escapeHtml(caption) || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2498,6 +2526,63 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Helper para iconos de archivos (copiado de chat_window)
|
||||
function getFileIcon(filename) {
|
||||
if (!filename) return 'fas fa-file text-muted';
|
||||
const ext = String(filename).split('.').pop().toLowerCase();
|
||||
const iconMap = {
|
||||
'pdf': 'fas fa-file-pdf text-danger',
|
||||
'doc': 'fas fa-file-word text-primary',
|
||||
'docx': 'fas fa-file-word text-primary',
|
||||
'xls': 'fas fa-file-excel text-success',
|
||||
'xlsx': 'fas fa-file-excel text-success',
|
||||
'ppt': 'fas fa-file-powerpoint text-warning',
|
||||
'pptx': 'fas fa-file-powerpoint text-warning',
|
||||
'zip': 'fas fa-file-archive text-secondary',
|
||||
'rar': 'fas fa-file-archive text-secondary',
|
||||
'txt': 'fas fa-file-alt text-muted',
|
||||
'csv': 'fas fa-file-csv text-success'
|
||||
};
|
||||
return iconMap[ext] || 'fas fa-file text-muted';
|
||||
}
|
||||
|
||||
// Media lightbox helper (image/video with download)
|
||||
function openMediaLightbox(url, type = 'image', caption = '') {
|
||||
if (!url) {
|
||||
showAlert && showAlert('Media no disponible', 'warning');
|
||||
return;
|
||||
}
|
||||
const body = document.getElementById('imageLightboxBody');
|
||||
const cap = document.getElementById('imageLightboxCaption');
|
||||
const downloadBtn = document.getElementById('imageLightboxDownload');
|
||||
if (!body || !cap) return;
|
||||
// clear existing content
|
||||
body.innerHTML = '';
|
||||
|
||||
// Decide rendering by type or by URL extension
|
||||
const isVideo = (type === 'video') || /\.(mp4|webm|ogg)(\?|$)/i.test(url);
|
||||
if (isVideo) {
|
||||
body.innerHTML = `<video controls class="w-100" style="border-radius:10px;"><source src="${escapeHtml(url)}" type="video/mp4">Tu navegador no soporta video.</video>`;
|
||||
} else {
|
||||
body.innerHTML = `<img src="${escapeHtml(url)}" class="img-fluid w-100" style="border-radius:10px;">`;
|
||||
}
|
||||
|
||||
cap.textContent = caption || '';
|
||||
if (downloadBtn) {
|
||||
downloadBtn.href = url;
|
||||
downloadBtn.style.display = 'inline-block';
|
||||
}
|
||||
|
||||
const modalEl = document.getElementById('imageLightboxModal');
|
||||
const bsModal = new bootstrap.Modal(modalEl);
|
||||
bsModal.show();
|
||||
}
|
||||
|
||||
// backward compatibility wrapper
|
||||
function openImageLightbox(url, caption = '') {
|
||||
openMediaLightbox(url, 'image', caption);
|
||||
}
|
||||
|
||||
// Inicializar cuando la página cargue
|
||||
let chat;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@@ -2505,5 +2590,22 @@
|
||||
window.chatApp = chat; // Exponer globalmente para funciones auxiliares
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Lightbox Modal -->
|
||||
<div class="modal fade" id="imageLightboxModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content bg-transparent border-0">
|
||||
<div class="modal-body p-0 position-relative">
|
||||
<button type="button" class="btn-close btn-close-white position-absolute top-0 end-0 m-3" data-bs-dismiss="modal" aria-label="Cerrar" style="z-index:10;"></button>
|
||||
<div id="imageLightboxBody" style="max-height:75vh; overflow:auto;"></div>
|
||||
<div id="imageLightboxCaption" class="mt-2 text-center text-white small"></div>
|
||||
<div class="position-absolute bottom-0 end-0 m-3">
|
||||
<a id="imageLightboxDownload" class="btn btn-sm btn-light" href="#" target="_blank" download style="display:none;"><i class="fas fa-download"></i> Descargar</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user