feat: dropdown de mensajes muestra conversaciones sin responder
- HeaderComposer ahora muestra conversaciones donde el último mensaje es del otro usuario (pendiente de respuesta), no solo mensajes no leídos - El badge refleja el conteo de conversaciones pendientes - Al responder desde el chat, se dispara chat:replied para quitar la conversación del dropdown en tiempo real - El badge en tiempo real solo incrementa si es una conversación nueva Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,18 +15,25 @@ class HeaderComposer
|
|||||||
|
|
||||||
$userId = auth()->id();
|
$userId = auth()->id();
|
||||||
|
|
||||||
// Últimas conversaciones con mensajes no leídos
|
// Conversaciones donde el último mensaje es del otro usuario (pendiente de respuesta)
|
||||||
$unreadConversations = Message::where('receiver_id', $userId)
|
$pendingConversations = Message::where(function ($q) use ($userId) {
|
||||||
->whereNull('read_at')
|
$q->where('receiver_id', $userId)
|
||||||
|
->orWhere('user_id', $userId);
|
||||||
|
})
|
||||||
->with('sender')
|
->with('sender')
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->get()
|
->get()
|
||||||
->unique('user_id')
|
->groupBy(function ($msg) use ($userId) {
|
||||||
|
return $msg->user_id === $userId ? $msg->receiver_id : $msg->user_id;
|
||||||
|
})
|
||||||
|
->filter(function ($msgs) use ($userId) {
|
||||||
|
return $msgs->first()->user_id !== $userId;
|
||||||
|
})
|
||||||
|
->map(fn($msgs) => $msgs->first())
|
||||||
->take(5);
|
->take(5);
|
||||||
|
|
||||||
$unreadMessagesCount = Message::where('receiver_id', $userId)
|
$unreadConversations = $pendingConversations;
|
||||||
->whereNull('read_at')
|
$unreadMessagesCount = $pendingConversations->count();
|
||||||
->count();
|
|
||||||
|
|
||||||
// Notificaciones no leídas
|
// Notificaciones no leídas
|
||||||
$unreadNotifications = auth()->user()
|
$unreadNotifications = auth()->user()
|
||||||
|
|||||||
@@ -114,6 +114,8 @@ document.getElementById('chat-form').addEventListener('submit', function(e) {
|
|||||||
scrollBottom();
|
scrollBottom();
|
||||||
form.reset();
|
form.reset();
|
||||||
document.getElementById('attachment-preview').textContent = '';
|
document.getElementById('attachment-preview').textContent = '';
|
||||||
|
// Notificar al header que esta conversación ya fue respondida
|
||||||
|
window.dispatchEvent(new CustomEvent('chat:replied', { detail: { receiverId } }));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -147,10 +147,10 @@
|
|||||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
<span class="mr-2 d-none d-lg-inline text-gray-600 small">{{ Auth::user()->name }}</span>
|
<span class="mr-2 d-none d-lg-inline text-gray-600 small">{{ Auth::user()->name }}</span>
|
||||||
|
|
||||||
{{-- <img class="img-profile rounded-circle" src="{{ Auth::user()->profile_photo_url }}"> --}}
|
<img class="img-profile rounded-circle" src="{{ Auth::user()->profile_photo_url }}">
|
||||||
<img class="img-profile rounded-circle"
|
{{-- <img class="img-profile rounded-circle"
|
||||||
src="{{ Auth::user()->profile_photo_url }}"
|
src="{{ Auth::user()->profile_photo_url }}"
|
||||||
alt="{{ Auth::user()->name }}">
|
alt="{{ Auth::user()->name }}"> --}}
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in"
|
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in"
|
||||||
aria-labelledby="userDropdown">
|
aria-labelledby="userDropdown">
|
||||||
@@ -199,8 +199,6 @@ waitForEcho(() => {
|
|||||||
// Badge + dropdown de mensajes
|
// Badge + dropdown de mensajes
|
||||||
window.Echo.channel(`chat.${authId}`)
|
window.Echo.channel(`chat.${authId}`)
|
||||||
.listen('MessageSent', (e) => {
|
.listen('MessageSent', (e) => {
|
||||||
updateBadge('msg-badge', 1);
|
|
||||||
|
|
||||||
const list = document.querySelector('#messagesDropdown + .dropdown-list');
|
const list = document.querySelector('#messagesDropdown + .dropdown-list');
|
||||||
if (!list) return;
|
if (!list) return;
|
||||||
|
|
||||||
@@ -208,9 +206,14 @@ waitForEcho(() => {
|
|||||||
const empty = list.querySelector('.text-muted.py-3');
|
const empty = list.querySelector('.text-muted.py-3');
|
||||||
if (empty) empty.remove();
|
if (empty) empty.remove();
|
||||||
|
|
||||||
// Quitar entrada previa del mismo remitente si ya estaba
|
// Si ya había una entrada de este remitente, quitarla (actualizar con el nuevo)
|
||||||
const existing = list.querySelector(`[data-sender-id="${e.sender.id}"]`);
|
const existing = list.querySelector(`[data-sender-id="${e.sender.id}"]`);
|
||||||
if (existing) existing.remove();
|
if (!existing) {
|
||||||
|
// Solo incrementar badge si es una conversación nueva en el dropdown
|
||||||
|
updateBadge('msg-badge', 1);
|
||||||
|
} else {
|
||||||
|
existing.remove();
|
||||||
|
}
|
||||||
|
|
||||||
// Insertar nueva entrada al tope (después del header)
|
// Insertar nueva entrada al tope (después del header)
|
||||||
const preview = e.body ? e.body : '📎 Archivo adjunto';
|
const preview = e.body ? e.body : '📎 Archivo adjunto';
|
||||||
@@ -232,6 +235,26 @@ waitForEcho(() => {
|
|||||||
list.querySelector('.dropdown-header').insertAdjacentElement('afterend', item);
|
list.querySelector('.dropdown-header').insertAdjacentElement('afterend', item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Cuando el usuario responde desde el chat, quitar esa conversación del dropdown
|
||||||
|
window.addEventListener('chat:replied', (ev) => {
|
||||||
|
const receiverId = ev.detail.receiverId;
|
||||||
|
const list = document.querySelector('#messagesDropdown + .dropdown-list');
|
||||||
|
if (!list) return;
|
||||||
|
const entry = list.querySelector(`[data-sender-id="${receiverId}"]`);
|
||||||
|
if (entry) {
|
||||||
|
entry.remove();
|
||||||
|
updateBadge('msg-badge', -1);
|
||||||
|
// Si ya no quedan conversaciones, mostrar el mensaje vacío
|
||||||
|
const remaining = list.querySelectorAll('.dropdown-item[data-sender-id]');
|
||||||
|
if (remaining.length === 0) {
|
||||||
|
const empty = document.createElement('div');
|
||||||
|
empty.className = 'dropdown-item text-center text-muted py-3';
|
||||||
|
empty.innerHTML = '<i class="fas fa-check-circle text-success mr-1"></i> Sin mensajes nuevos';
|
||||||
|
list.querySelector('.dropdown-header').insertAdjacentElement('afterend', empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Badge de notificaciones
|
// Badge de notificaciones
|
||||||
window.Echo.channel(`notifications.${authId}`)
|
window.Echo.channel(`notifications.${authId}`)
|
||||||
.listen('NewNotification', (e) => {
|
.listen('NewNotification', (e) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user