From 9055e06e048ff6fed9f472c2c2868bc6753e2301 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Tue, 19 May 2026 11:35:31 +0330 Subject: [PATCH] unread messages handles for the admin panel. --- apps/chat/admin_views.py | 27 ++++++++++++++++++++++++--- static/css/live_chat.css | 18 ++++++++++++++++++ static/js/live_chat.js | 18 +++++++++++++----- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/apps/chat/admin_views.py b/apps/chat/admin_views.py index 28ffa2b..a5d95be 100644 --- a/apps/chat/admin_views.py +++ b/apps/chat/admin_views.py @@ -5,7 +5,7 @@ from django.conf import settings from django.http import JsonResponse from django.shortcuts import render, get_object_or_404 from django.views.decorators.http import require_http_methods -from django.db.models import Q +from django.db.models import Q, Count from django.utils.translation import gettext_lazy as _ from apps.chat.models import RoomMessage, ChatMessage @@ -39,7 +39,13 @@ def live_chat_dashboard_view(request): def api_get_rooms(request): """گرفتن لیست روم‌ها بر اساس دسترسی و حریم خصوصی مطلق""" user = request.user - queryset = RoomMessage.objects.all().order_by('-updated_at') + + queryset = RoomMessage.objects.annotate( + admin_unread_count=Count( + 'messages', + filter=Q(messages__is_read=False) & ~Q(messages__sender=user) + ) + ).order_by('-updated_at') personal_q = Q(initiator=user) | Q(recipient=user) @@ -61,11 +67,13 @@ def api_get_rooms(request): if r.room_type == 'group' and r.course: title = r.course.title + unread_count = getattr(r, 'admin_unread_count', 0) + data.append({ "id": r.id, "title": title, "type": r.room_type, - "unread": r.unread_messages_count, + "unread": unread_count, "time": r.updated_at.strftime("%H:%M") if r.updated_at else "" }) @@ -81,6 +89,19 @@ def api_get_messages(request, room_id): id__gt=last_id, # جادوی پولینگ: فقط آیدی‌های بزرگتر از آخرین پیامی که داریم رو بگیر is_deleted=False ).order_by('id') + + # 🟢 مارک کردن پیام‌ها به عنوان خوانده شده (سین کردن) برای تمام انواع چت + if messages.exists(): + # پیام‌های طرف مقابل را سین کن + unread_msgs = messages.filter(is_read=False).exclude(sender=request.user) + if unread_msgs.exists(): + unread_msgs.update(is_read=True) + + if room.room_type == RoomMessage.RoomTypeChoices.GROUP: + # همگام‌سازی با فیلد استاتیک برای جلوگیری از مشکلات احتمالی در FastAPI + if room.unread_messages_count > 0: + room.unread_messages_count = 0 + room.save(update_fields=['unread_messages_count']) data = [] for m in messages: diff --git a/static/css/live_chat.css b/static/css/live_chat.css index c794a08..3ba6247 100644 --- a/static/css/live_chat.css +++ b/static/css/live_chat.css @@ -147,6 +147,24 @@ html { /* آواتار روم فعال با گرادیان سبز اصلی و تیره */ } +.unread-badge { + background: #0A522E; + color: #FAF6E9; + margin-left: 8px; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + padding: 1px 6px 0 6px; /* افزودن یک پیکسل به بالا برای تراز چشمی فونت */ + line-height: 1; +} + +.dark .unread-badge { + background: #1a854d; +} + /* ── Telegram-style Header ── */ .telegram-header { background: linear-gradient(135deg, #0A522E, #052B18); diff --git a/static/js/live_chat.js b/static/js/live_chat.js index b4c0327..4e8475e 100644 --- a/static/js/live_chat.js +++ b/static/js/live_chat.js @@ -155,7 +155,7 @@ document.addEventListener('DOMContentLoaded', function () {
${room.type === 'group' ? '👥 Group Chat' : '👤 Private Chat'} - ${room.unread > 0 ? `${room.unread}` : ''} + ${room.unread > 0 ? `${room.unread}` : ''}
@@ -168,6 +168,15 @@ document.addEventListener('DOMContentLoaded', function () { lastMessageId = 0; isFetchingMessages = false; + // 🟢 Optimistic UI: Update active state and remove unread badge immediately + document.querySelectorAll('.room-item').forEach(el => el.classList.remove('room-item-active')); + const clickedRoom = document.querySelector(`.room-item[onclick*="selectRoom(${id}"]`); + if (clickedRoom) { + clickedRoom.classList.add('room-item-active'); + const badge = clickedRoom.querySelector('.unread-badge'); + if (badge) badge.remove(); // Hide badge instantly + } + // 🟢 تغییر invisible به hidden برای کارکرد درست بلوک‌ها UI.noChatSelected.classList.add('hidden'); UI.chatHeaderInfo.classList.remove('hidden'); // 🟢 تغییر یافته @@ -177,12 +186,11 @@ document.addEventListener('DOMContentLoaded', function () { UI.chatTitle.textContent = title; UI.messages.innerHTML = '
autorenew
'; - fetchRooms(); - stopPolling(); fetchMessages().then(() => { startPolling(); scrollToBottom(); + fetchRooms(); // Sync with backend to ensure exact unread counts }); UI.input.focus(); @@ -249,12 +257,12 @@ document.addEventListener('DOMContentLoaded', function () { } // ── Textarea Auto-resize & Key Handling ── - UI.input.addEventListener('input', function() { + UI.input.addEventListener('input', function () { this.style.height = 'auto'; this.style.height = (this.scrollHeight) + 'px'; }); - UI.input.addEventListener('keydown', function(e) { + UI.input.addEventListener('keydown', function (e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); // Trigger form submit