Update conversations.php
This commit is contained in:
+104
-17
@@ -428,6 +428,33 @@
|
|||||||
.notification-toast .nt-actions { margin-left: auto; display:flex; gap:6px; }
|
.notification-toast .nt-actions { margin-left: auto; display:flex; gap:6px; }
|
||||||
.notification-toast .btn { font-size: 12px; padding: 4px 8px; }
|
.notification-toast .btn { font-size: 12px; padding: 4px 8px; }
|
||||||
|
|
||||||
|
/* UI improvements for chat area */
|
||||||
|
.quick-replies .btn {
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-conversations {
|
||||||
|
background-repeat: repeat;
|
||||||
|
background-color: #e9efe9;
|
||||||
|
padding-bottom: 20px; /* space for reply preview and input */
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble { transition: transform 0.12s ease, box-shadow 0.12s ease; }
|
||||||
|
.message-bubble:hover { transform: translateY(-1px); box-shadow: 0 4px 10px rgba(0,0,0,0.06); }
|
||||||
|
|
||||||
|
#reply-preview { box-shadow: 0 1px 2px rgba(0,0,0,0.04); }
|
||||||
|
#reply-preview button { border: none; color: #888; }
|
||||||
|
#reply-preview button:hover { color: #333; }
|
||||||
|
|
||||||
|
.message.incoming .message-bubble::before,
|
||||||
|
.message.outgoing .message-bubble::after { content: ''; }
|
||||||
|
|
||||||
|
|
||||||
.notification-toast.urgent { border-left: 4px solid #e74c3c; }
|
.notification-toast.urgent { border-left: 4px solid #e74c3c; }
|
||||||
|
|
||||||
.message-template {
|
.message-template {
|
||||||
@@ -593,6 +620,11 @@
|
|||||||
<input type="text" id="media-caption" class="form-control mt-2" placeholder="Agregar un comentario (opcional)" maxlength="1024">
|
<input type="text" id="media-caption" class="form-control mt-2" placeholder="Agregar un comentario (opcional)" maxlength="1024">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="reply-preview" class="reply-preview" style="display:none; align-items:center; justify-content:space-between;">
|
||||||
|
<div style="flex:1; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" id="reply-preview-text">En respuesta a: (selecciona un mensaje)</div>
|
||||||
|
<button class="btn btn-sm btn-link" id="cancel-reply-btn" title="Cancelar respuesta">✕</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="chat-input">
|
<div class="chat-input">
|
||||||
<input type="file" id="file-input" accept="image/*,video/*,audio/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx" style="display: none;">
|
<input type="file" id="file-input" accept="image/*,video/*,audio/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx" style="display: none;">
|
||||||
<button class="btn" id="attach-btn" title="Adjuntar archivo">
|
<button class="btn" id="attach-btn" title="Adjuntar archivo">
|
||||||
@@ -776,8 +808,8 @@
|
|||||||
const input = document.getElementById('message-input');
|
const input = document.getElementById('message-input');
|
||||||
if (!input) return;
|
if (!input) return;
|
||||||
input.dataset.replyTo = messageId;
|
input.dataset.replyTo = messageId;
|
||||||
|
this.showReplyPreview(messageId);
|
||||||
input.focus();
|
input.focus();
|
||||||
// Optionally show a small UI indicator (not implemented)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// React to message placeholder
|
// React to message placeholder
|
||||||
@@ -1193,6 +1225,8 @@
|
|||||||
console.warn('holdIndicator missing when releasing hold');
|
console.warn('holdIndicator missing when releasing hold');
|
||||||
}
|
}
|
||||||
releaseBtn.style.display = 'none';
|
releaseBtn.style.display = 'none';
|
||||||
|
// show bot toggle again
|
||||||
|
if (btn) btn.style.display = 'inline-block';
|
||||||
await this.loadConversations();
|
await this.loadConversations();
|
||||||
} else {
|
} else {
|
||||||
alert('Error al liberar la espera');
|
alert('Error al liberar la espera');
|
||||||
@@ -1235,6 +1269,8 @@
|
|||||||
releaseBtn.style.display = 'inline-block';
|
releaseBtn.style.display = 'inline-block';
|
||||||
// show finish button
|
// show finish button
|
||||||
if (finishBtn) finishBtn.style.display = 'inline-block';
|
if (finishBtn) finishBtn.style.display = 'inline-block';
|
||||||
|
// hide bot toggle while in service
|
||||||
|
if (btn) btn.style.display = 'none';
|
||||||
await this.loadConversations();
|
await this.loadConversations();
|
||||||
} else {
|
} else {
|
||||||
alert('Error al marcar como atendida');
|
alert('Error al marcar como atendida');
|
||||||
@@ -1269,6 +1305,8 @@
|
|||||||
finishBtn.style.display = 'none';
|
finishBtn.style.display = 'none';
|
||||||
releaseBtn.style.display = 'none';
|
releaseBtn.style.display = 'none';
|
||||||
attendBtn.style.display = 'none';
|
attendBtn.style.display = 'none';
|
||||||
|
// show bot toggle again
|
||||||
|
if (btn) btn.style.display = 'inline-block';
|
||||||
await this.loadConversations();
|
await this.loadConversations();
|
||||||
} else {
|
} else {
|
||||||
alert('Error al finalizar la atención');
|
alert('Error al finalizar la atención');
|
||||||
@@ -1285,6 +1323,14 @@
|
|||||||
btn.textContent = conv.bot_enabled ? 'Bot: On' : 'Bot: Off';
|
btn.textContent = conv.bot_enabled ? 'Bot: On' : 'Bot: Off';
|
||||||
btn.classList.toggle('btn-outline-danger', !conv.bot_enabled);
|
btn.classList.toggle('btn-outline-danger', !conv.bot_enabled);
|
||||||
btn.classList.toggle('btn-outline-secondary', conv.bot_enabled);
|
btn.classList.toggle('btn-outline-secondary', conv.bot_enabled);
|
||||||
|
|
||||||
|
// Hide bot toggle when advisor requested or in service or on hold (we expect attend/finish actions)
|
||||||
|
if (conv.advisor_requested || conv.in_service || conv.on_hold) {
|
||||||
|
btn.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
btn.style.display = 'inline-block';
|
||||||
|
}
|
||||||
|
|
||||||
btn.onclick = async () => {
|
btn.onclick = async () => {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('api/set_bot_enabled.php', {
|
const resp = await fetch('api/set_bot_enabled.php', {
|
||||||
@@ -1643,32 +1689,42 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async sendTemplateQuick(templateName, language) {
|
async sendTemplateQuick(templateName, language) {
|
||||||
if (!this.currentUserId) return;
|
// delegate to unified sendTemplateMessage helper
|
||||||
const conv = this.conversations.find(c => c.user_id === this.currentUserId);
|
await this.sendTemplateMessage(templateName, language, null);
|
||||||
const phone = conv ? (conv.phone_number || conv.phone || conv.user_phone) : (document.getElementById('chat-phone') ? document.getElementById('chat-phone').textContent.trim() : null);
|
}
|
||||||
|
|
||||||
if (!phone) {
|
async sendTemplateMessage(templateName, language = 'es', parameters = null) {
|
||||||
alert('No se pudo determinar el número de teléfono del destinatario.');
|
if (!this.currentUserId) return;
|
||||||
console.error('sendTemplateQuick: missing phone for user', this.currentUserId, conv);
|
const recipient = this.getConversationPhone(this.currentUserId);
|
||||||
|
if (!recipient) {
|
||||||
|
alert('No se pudo determinar el número de destino para la plantilla');
|
||||||
|
console.error('sendTemplateMessage: missing recipient for user', this.currentUserId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('api/send_message.php', {
|
const requestBody = {
|
||||||
method: 'POST',
|
recipient: recipient,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
type: 'template',
|
||||||
body: JSON.stringify({ recipient: phone, type: 'template', template_name: templateName, language })
|
template: templateName,
|
||||||
});
|
language: language || 'es',
|
||||||
const result = await resp.json();
|
parameters: parameters || []
|
||||||
if (result.success) {
|
};
|
||||||
|
|
||||||
|
const resp = await this.apiCall('send_message.php', { body: requestBody });
|
||||||
|
// hide reply preview if any
|
||||||
|
this.hideReplyPreview();
|
||||||
|
if (resp && resp.success) {
|
||||||
|
this.addMessageToView(`Plantilla: ${templateName}`, 'outgoing');
|
||||||
|
showAlert && showAlert('Mensaje de plantilla enviado correctamente', 'success');
|
||||||
await this.loadconversations(this.currentUserId, false);
|
await this.loadconversations(this.currentUserId, false);
|
||||||
await this.loadConversations();
|
await this.loadConversations();
|
||||||
} else {
|
} else {
|
||||||
alert('Error al enviar plantilla: ' + (result.error || JSON.stringify(result)));
|
throw new Error(resp && resp.error ? resp.error : 'Error enviando plantilla');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error sending template quick', e);
|
console.error('Error sending template message', e);
|
||||||
alert('Error al enviar plantilla');
|
alert('Error enviando plantilla: ' + (e.message || e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1699,6 +1755,36 @@
|
|||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showReplyPreview(messageId) {
|
||||||
|
try {
|
||||||
|
const preview = document.getElementById('reply-preview');
|
||||||
|
const previewText = document.getElementById('reply-preview-text');
|
||||||
|
const cancelBtn = document.getElementById('cancel-reply-btn');
|
||||||
|
const msg = this.conversations.find(m => m.message_id == messageId);
|
||||||
|
const text = msg ? (msg.content || msg.message_text || '[Mensaje]') : ('Mensaje ' + messageId);
|
||||||
|
if (preview && previewText) {
|
||||||
|
previewText.textContent = 'En respuesta a: ' + (String(text).replace(/\n/g,' ')).substring(0,140);
|
||||||
|
preview.style.display = 'flex';
|
||||||
|
}
|
||||||
|
if (cancelBtn) {
|
||||||
|
cancelBtn.onclick = () => this.hideReplyPreview();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('showReplyPreview failed', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideReplyPreview() {
|
||||||
|
try {
|
||||||
|
const preview = document.getElementById('reply-preview');
|
||||||
|
if (preview) preview.style.display = 'none';
|
||||||
|
const input = document.getElementById('message-input');
|
||||||
|
if (input) input.dataset.replyTo = null;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('hideReplyPreview failed', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async sendMessage() {
|
async sendMessage() {
|
||||||
const input = document.getElementById('message-input');
|
const input = document.getElementById('message-input');
|
||||||
const message = input.value.trim();
|
const message = input.value.trim();
|
||||||
@@ -1753,6 +1839,7 @@
|
|||||||
const result = await this.apiCall('send_message.php', { body: requestBody });
|
const result = await this.apiCall('send_message.php', { body: requestBody });
|
||||||
// Clear reply data attribute
|
// Clear reply data attribute
|
||||||
input.dataset.replyTo = null;
|
input.dataset.replyTo = null;
|
||||||
|
this.hideReplyPreview();
|
||||||
|
|
||||||
if (result && result.success) {
|
if (result && result.success) {
|
||||||
input.value = '';
|
input.value = '';
|
||||||
|
|||||||
Reference in New Issue
Block a user