From 2705527934dc7ace60e26ca8fd3576d16f2642f3 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Mon, 18 May 2026 15:51:07 +0330 Subject: [PATCH] admin can search users and create a new chat room on the admin panel --- static/css/live_chat.css | 28 +++++ static/js/live_chat.js | 169 +++++++++++++++++++++------- templates/admin/chat/live_chat.html | 47 +++++++- utils/admin.py | 4 +- 4 files changed, 199 insertions(+), 49 deletions(-) diff --git a/static/css/live_chat.css b/static/css/live_chat.css index 0b884ea..1c88c4d 100644 --- a/static/css/live_chat.css +++ b/static/css/live_chat.css @@ -292,4 +292,32 @@ html { .send-btn-telegram:active { transform: scale(0.95); +} + +/* ── Users Modal Solid Backgrounds ── */ +#users-modal { + background-color: #f8fafc !important; +} + +#users-modal > div:nth-child(2) { + background-color: #ffffff !important; + border-bottom: 1px solid #e5e7eb !important; +} + +#users-list { + background-color: #f8fafc !important; +} + +/* Dark Mode Support */ +.dark #users-modal { + background-color: #1f2937 !important; +} + +.dark #users-modal > div:nth-child(2) { + background-color: #111827 !important; + border-bottom: 1px solid #374151 !important; +} + +.dark #users-list { + background-color: #1f2937 !important; } \ No newline at end of file diff --git a/static/js/live_chat.js b/static/js/live_chat.js index 069eb51..bb87e4e 100644 --- a/static/js/live_chat.js +++ b/static/js/live_chat.js @@ -1,15 +1,11 @@ document.addEventListener('DOMContentLoaded', function () { - // متغیرهای وضعیت let currentRoomId = null; let lastMessageId = 0; let messagePollInterval = null; let roomPollInterval = null; - - // 🟢 متغیر قفل برای جلوگیری از دریافت پیام‌های تکراری (Race Condition) let isFetchingMessages = false; - // المان‌های DOM const UI = { roomList: document.getElementById('room-list'), noChatSelected: document.getElementById('no-chat-selected'), @@ -19,21 +15,119 @@ document.addEventListener('DOMContentLoaded', function () { inputArea: document.getElementById('chat-input-area'), form: document.getElementById('chat-form'), input: document.getElementById('chat-input'), - indicator: document.getElementById('polling-indicator') + indicator: document.getElementById('polling-indicator'), + + // 🟢 متغیرهای جدید برای Modal کاربران + newChatBtn: document.getElementById('new-chat-btn'), + usersModal: document.getElementById('users-modal'), + closeModalBtn: document.getElementById('close-users-modal'), + usersList: document.getElementById('users-list'), + searchInput: document.getElementById('user-search-input') + }; + + // ── منطق مودال کاربران ── + if (UI.newChatBtn) { + UI.newChatBtn.addEventListener('click', () => { + UI.usersModal.style.transform = 'translateY(0)'; // نمایش مودال (بالا آمدن) + fetchUsers(''); // لود اولیه لیست کاربران + }); + } + + if (UI.closeModalBtn) { + UI.closeModalBtn.addEventListener('click', () => { + UI.usersModal.style.transform = 'translateY(100%)'; // مخفی کردن مودال (پایین رفتن) + }); + } + + if (UI.searchInput) { + let timeout = null; + UI.searchInput.addEventListener('input', (e) => { + clearTimeout(timeout); + // دی‌باونس (Debounce) برای جلوگیری از ریکوئست‌های تکراری + timeout = setTimeout(() => { + fetchUsers(e.target.value); + }, 300); + }); + } + + async function fetchUsers(query) { + UI.usersList.innerHTML = '
autorenew
'; + try { + const res = await fetch(`${window.CHAT_CONFIG.apiUrlUsers}?q=${encodeURIComponent(query)}`); + const data = await res.json(); + renderUsers(data.users); + } catch (e) { + console.error("Failed to fetch users:", e); + UI.usersList.innerHTML = '

Failed to load users.

'; + } + } + + function renderUsers(users) { + if (users.length === 0) { + UI.usersList.innerHTML = '

No users found.

'; + return; + } + + UI.usersList.innerHTML = users.map(user => { + const initial = user.name ? user.name.charAt(0).toUpperCase() : 'U'; + + // استفاده از کلاس‌های room-item و room-avatar برای هماهنگی 100 درصدی + return ` +
+ +
+ ${initial} +
+ +
+
+ ${user.name} +
+
+ ${user.role} +
+
+ +
+ `; + }).join(''); + } + + window.startChatWithUser = async function (userId) { + // بستن مودال بلافاصله بعد از کلیک + UI.usersModal.style.transform = 'translateY(100%)'; + + try { + const res = await fetch(window.CHAT_CONFIG.apiUrlCreateRoom, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': window.CHAT_CONFIG.csrfToken + }, + body: JSON.stringify({ user_id: userId }) + }); + const data = await res.json(); + + if (data.success) { + // اگر با موفقیت ساخته یا پیدا شد، مستقیماً وارد چت می‌شویم + selectRoom(data.room_id, data.title); + } + } catch (e) { + console.error("Failed to start chat:", e); + } }; + // ── پایان منطق مودال کاربران ── - // گرفتن لیست چت‌ها از سرور + + // ── بقیه کدهای قبلی (fetchRooms, renderRooms, fetchMessages و...) بدون تغییر در اینجا قرار دارند ── async function fetchRooms() { try { const res = await fetch(window.CHAT_CONFIG.apiUrlRooms); const data = await res.json(); renderRooms(data.rooms); - } catch (e) { - console.error("Failed to fetch rooms:", e); - } + } catch (e) { console.error("Failed to fetch rooms:", e); } } - // رندر کردن لیست چت‌ها function renderRooms(rooms) { if (rooms.length === 0) { UI.roomList.innerHTML = '

No active chats found.

'; @@ -43,27 +137,35 @@ document.addEventListener('DOMContentLoaded', function () { UI.roomList.innerHTML = rooms.map(room => { const isActive = currentRoomId === room.id; const safeTitle = room.title.replace(/'/g, "\\'"); + const initial = room.title ? room.title.charAt(0).toUpperCase() : 'G'; + return `
-
- ${room.title} - ${room.time} + class="room-item flex items-center gap-3 p-3 cursor-pointer ${isActive ? 'room-item-active' : ''}"> + +
+ ${initial}
-
- ${room.type} - ${room.unread > 0 ? `${room.unread}` : ''} + +
+
+ ${room.title} + ${room.time} +
+
+ ${room.type === 'group' ? '👥 Group Chat' : '👤 Private Chat'} + ${room.unread > 0 ? `${room.unread}` : ''} +
`; }).join(''); } - // انتخاب یک چت window.selectRoom = function (id, title) { currentRoomId = id; lastMessageId = 0; - isFetchingMessages = false; // ریست کردن قفل + isFetchingMessages = false; UI.noChatSelected.classList.add('hidden'); UI.chatHeader.classList.remove('invisible'); @@ -84,12 +186,9 @@ document.addEventListener('DOMContentLoaded', function () { UI.input.focus(); }; - // گرفتن پیام‌ها از سرور async function fetchMessages() { - // 🟢 اگر هیچ رومی انتخاب نشده، یا درخواست قبلی هنوز تمام نشده، برگرد! if (!currentRoomId || isFetchingMessages) return; - - isFetchingMessages = true; // 🟢 فعال کردن قفل + isFetchingMessages = true; try { UI.indicator.classList.remove('hidden'); @@ -105,20 +204,15 @@ document.addEventListener('DOMContentLoaded', function () { lastMessageId = data.messages[data.messages.length - 1].id; scrollToBottom(); } else if (lastMessageId === 0) { - UI.messages.innerHTML = ` -
- forum -

No messages yet. Say hi!

-
`; + UI.messages.innerHTML = '
chat

No messages yet. Start the conversation!

'; } } catch (e) { console.error("Failed to fetch messages:", e); } finally { - isFetchingMessages = false; // 🟢 باز کردن قفل در هر شرایطی (چه موفق چه ارور) + isFetchingMessages = false; } } - // رندر کردن حباب‌های پیام function renderMessages(messages) { const html = messages.map(m => { const isMe = m.is_me; @@ -148,15 +242,10 @@ document.addEventListener('DOMContentLoaded', function () { UI.messages.insertAdjacentHTML('beforeend', html); } - // اسکرول به پایین function scrollToBottom() { - UI.messages.scrollTo({ - top: UI.messages.scrollHeight, - behavior: 'smooth' - }); + UI.messages.scrollTo({ top: UI.messages.scrollHeight, behavior: 'smooth' }); } - // ارسال پیام جدید UI.form.addEventListener('submit', async (e) => { e.preventDefault(); const text = UI.input.value.trim(); @@ -173,15 +262,11 @@ document.addEventListener('DOMContentLoaded', function () { }, body: JSON.stringify({ content: text }) }); - // 🟢 صدا زدن دریافت پیام به صورت ایمن await fetchMessages(); fetchRooms(); - } catch (e) { - console.error("Failed to send message:", e); - } + } catch (e) { console.error("Failed to send message:", e); } }); - // راه‌اندازی Polling function startPolling() { messagePollInterval = setInterval(fetchMessages, 3000); } @@ -190,8 +275,6 @@ document.addEventListener('DOMContentLoaded', function () { if (messagePollInterval) clearInterval(messagePollInterval); } - // مقداردهی اولیه fetchRooms(); roomPollInterval = setInterval(fetchRooms, 10000); - }); \ No newline at end of file diff --git a/templates/admin/chat/live_chat.html b/templates/admin/chat/live_chat.html index 8208098..0cfd1a6 100644 --- a/templates/admin/chat/live_chat.html +++ b/templates/admin/chat/live_chat.html @@ -15,12 +15,13 @@ {% endblock %} {% block content %} -
+
-
+
forum @@ -29,17 +30,50 @@ {% trans "Chat Rooms" %}
+ +
-
+
autorenew
+ +
+ +
+ +

{% trans "Select Contact" %}

+
+ +
+
+ search + +
+
+ +
+
+ autorenew +
+
+
-
diff --git a/utils/admin.py b/utils/admin.py index c326460..5083e78 100644 --- a/utils/admin.py +++ b/utils/admin.py @@ -156,7 +156,7 @@ class FormulaAdminSite(UnfoldAdminSite): def get_urls(self): urls = super().get_urls() - from apps.chat.admin_views import live_chat_dashboard_view, api_get_rooms, api_get_messages, api_send_message + from apps.chat.admin_views import live_chat_dashboard_view, api_get_rooms, api_get_messages, api_send_message, api_create_or_get_room , api_get_users custom_urls = [ path('toggle_sidebar/', toggle_sidebar, name='toggle_sidebar'), @@ -167,6 +167,8 @@ class FormulaAdminSite(UnfoldAdminSite): path('live-chat/api/rooms/', self.admin_view(api_get_rooms), name='live_chat_api_rooms'), path('live-chat/api/messages//', self.admin_view(api_get_messages), name='live_chat_api_messages'), path('live-chat/api/send//', self.admin_view(api_send_message), name='live_chat_api_send'), + path('live-chat/api/create-or-get-room/', self.admin_view(api_create_or_get_room), name='live_chat_api_create_room'), + path('live-chat/api/get-users/', self.admin_view(api_get_users), name='live_chat_api_users'), ] return custom_urls + urls