// ─────────────────────────────────────────────────────────── // Huio Control Panel — Group Chat Logs View (cp-chat-logs-view.jsx) // ─────────────────────────────────────────────────────────── if (!window.CP_STYLES) throw new Error('[cp-chat-logs-view] window.CP_STYLES not loaded (check cp-styles.jsx)'); function renderChatLogs({ lang, t, appStyles: rawAppStyles, chatLogs = [], chatLogsSearch = '', setChatLogsSearch, chatLogsDateFilter = '', setChatLogsDateFilter, chatLogsGroupFilter = 'all', setChatLogsGroupFilter, chatLogsCurrentPage = 1, setChatLogsCurrentPage, chatLogsPerPage = 15, chatReadReceipts = [], users = [], usersRef, signedGroupImgUrls = {}, setZoomImg, onRefresh, }) { const appStyles = rawAppStyles || window.CP_STYLES || {}; const tr = typeof t === 'function' ? t : ((k) => k); const logs = Array.isArray(chatLogs) ? chatLogs : []; // High-Load Pre-normalization (hoist invariants outside loop for O(N) efficiency) const query = (chatLogsSearch && typeof chatLogsSearch === 'string') ? chatLogsSearch.trim().toLowerCase() : ''; const targetDate = (chatLogsDateFilter && typeof chatLogsDateFilter === 'string') ? chatLogsDateFilter.trim() : ''; const targetGroup = (chatLogsGroupFilter != null) ? String(chatLogsGroupFilter) : 'all'; // Filter chat logs with zero-allocation date check & NaN protection const filteredChatLogs = logs.filter(log => { if (!log) return false; // Date filter if (targetDate) { let logDateStr = ''; if (typeof log.created_at === 'string' && log.created_at.length >= 10) { logDateStr = log.created_at.substring(0, 10); } else if (log.created_at) { const d = new Date(log.created_at); if (!isNaN(d.getTime())) { logDateStr = d.toISOString().substring(0, 10); } } if (logDateStr !== targetDate) return false; } // Group filter if (targetGroup !== 'all') { if (String(log.group_id) !== targetGroup) return false; } // Search query if (query) { const content = log.content ? String(log.content).toLowerCase() : ''; const sender = log.sender_name ? String(log.sender_name).toLowerCase() : ''; const senderId = log.sender_id != null ? String(log.sender_id).toLowerCase() : ''; const groupName = log.hui_groups && log.hui_groups.name ? String(log.hui_groups.name).toLowerCase() : ''; if (!content.includes(query) && !sender.includes(query) && !senderId.includes(query) && !groupName.includes(query)) { return false; } } return true; }); // Unique groups from actual logs for dropdown filter (single-pass Map aggregation) const uniqueGroupsMap = new Map(); for (let i = 0; i < logs.length; i++) { const log = logs[i]; if (log && log.group_id) { const existing = uniqueGroupsMap.get(log.group_id); const groupName = (log.hui_groups && log.hui_groups.name) || existing || 'Unknown Group'; uniqueGroupsMap.set(log.group_id, groupName); } } const uniqueChatGroups = Array.from(uniqueGroupsMap.entries()).map(([id, name]) => ({ id, name })); // Pagination with boundary clamping const perPage = Math.max(1, Number(chatLogsPerPage) || 15); const totalItems = filteredChatLogs.length; const totalPages = Math.max(1, Math.ceil(totalItems / perPage)); const safeCurrentPage = Math.min(Math.max(1, Number(chatLogsCurrentPage) || 1), totalPages); const startIndex = (safeCurrentPage - 1) * perPage; const endIndex = startIndex + perPage; const paginatedChatLogs = filteredChatLogs.slice(startIndex, endIndex); // Pre-build users lookup map for O(1) read receipts name resolution const userList = Array.isArray(users) ? users : (usersRef && usersRef.current && Array.isArray(usersRef.current)) ? usersRef.current : []; const userMap = new Map(); for (let i = 0; i < userList.length; i++) { const u = userList[i]; if (u && u.id) { userMap.set(u.id, u.name || u.phone || `User ${u.id.substring(0, 6)}`); } } // Pre-group read receipts by last_read_message_id for O(1) row lookup const receiptsByMsgId = new Map(); const receipts = Array.isArray(chatReadReceipts) ? chatReadReceipts : []; for (let i = 0; i < receipts.length; i++) { const r = receipts[i]; if (r && r.last_read_message_id) { let list = receiptsByMsgId.get(r.last_read_message_id); if (!list) { list = []; receiptsByMsgId.set(r.last_read_message_id, list); } list.push(r); } } // Pagination windowing helper (prevents DOM degradation if totalPages is large) const getPaginationPages = () => { if (totalPages <= 7) { return Array.from({ length: totalPages }, (_, i) => i + 1); } const pages = [1]; const left = Math.max(2, safeCurrentPage - 1); const right = Math.min(totalPages - 1, safeCurrentPage + 1); if (left > 2) pages.push('…'); for (let p = left; p <= right; p++) { pages.push(p); } if (right < totalPages - 1) pages.push('…'); pages.push(totalPages); return pages; }; return (

{lang === 'vi' ? 'Nhật ký Chat Nhóm' : 'Group Chat Logs'}

{lang === 'vi' ? 'Giám sát lịch sử hội thoại thời gian thực của các nhóm hụi trong hệ thống.' : 'Monitor real-time communication history of all hụi groups in the system.'}

{/* Metrics Dashboard */}
{/* Logs Table Card */}
{/* Filters Row */}
{/* Date Filter */}
{lang === 'vi' ? 'Ngày:' : 'Date:'} { setChatLogsDateFilter(e.target.value); setChatLogsCurrentPage(1); }} className="hu-input" style={{ height: '34px', fontSize: '12.5px', padding: '0 8px', borderRadius: 'var(--hu-r-md)', border: '1px solid var(--hu-border)', background: 'var(--hu-bg-elev)', color: 'var(--hu-fg)', width: '145px' }} /> {chatLogsDateFilter && ( )}
{/* Group Filter */}
{lang === 'vi' ? 'Nhóm hụi:' : 'Group:'}
{/* Search Bar */}
{ setChatLogsSearch(e.target.value); setChatLogsCurrentPage(1); }} placeholder={tr('common.search')} className="hu-input" style={{ height: '100%', fontSize: '13px' }} />
{/* Logs Table */}
{paginatedChatLogs.length === 0 ? ( ) : ( paginatedChatLogs.map((log, index) => { let formattedTime = '—'; let formattedDate = '—'; if (log.created_at) { const dateObj = new Date(log.created_at); if (!isNaN(dateObj.getTime())) { formattedTime = dateObj.toTimeString().split(' ')[0]; formattedDate = dateObj.toLocaleDateString(lang === 'vi' ? 'vi-VN' : 'en-US', { month: 'short', day: 'numeric', year: 'numeric' }); } } const groupName = log.hui_groups ? log.hui_groups.name : 'Unknown Group'; let typeBg = 'rgba(28,26,21,0.05)'; let typeColor = 'var(--hu-fg-2)'; if (log.message_type === 'bid') { typeBg = 'rgba(193,148,51,0.12)'; typeColor = 'var(--hu-ochre)'; } else if (log.message_type === 'system') { typeBg = 'rgba(110,103,87,0.1)'; typeColor = 'var(--hu-fg-3)'; } else if (log.message_type === 'image') { typeBg = 'rgba(79,107,61,0.12)'; typeColor = 'var(--hu-forest)'; } else if (log.message_type === 'audio') { typeBg = 'rgba(74, 144, 226, 0.12)'; typeColor = '#4a90e2'; } else if (log.message_type === 'document') { typeBg = 'rgba(245, 166, 35, 0.12)'; typeColor = '#f5a623'; } const rowKey = log.id || `${log.created_at}_${log.sender_id}_${index}`; return ( ); }) )}
{lang === 'vi' ? 'Thời gian' : 'Time'} {lang === 'vi' ? 'Nhóm hụi' : 'Group'} {lang === 'vi' ? 'Người gửi' : 'Sender'} {lang === 'vi' ? 'Phân loại' : 'Type'} {lang === 'vi' ? 'Nội dung' : 'Content'} {lang === 'vi' ? 'Đính kèm' : 'Attachment'}
{lang === 'vi' ? 'Không tìm thấy nhật ký chat nào' : 'No chat messages found'}
{formattedTime}
{formattedDate}
{groupName}
{log.sender_name || 'Someone'}
ID: {log.sender_id != null ? String(log.sender_id) : '—'}
{log.message_type || 'text'} {log.message_type === 'system' ? ( {log.content} ) : (
{log.message_type === 'audio' ? (
) : log.message_type === 'document' ? (
📄
{log.document_name || 'Document'}
{log.document_size ? `${(Number(log.document_size) / 1024).toFixed(1)} KB` : ''}
) : (
{log.content}
)} {(log.message_type === 'audio' || log.message_type === 'document') && log.content && (
{log.content}
)} {log.reactions && typeof log.reactions === 'object' && Object.keys(log.reactions).length > 0 && (
{Object.entries(log.reactions).map(([emoji, usersArr]) => { if (!usersArr) return null; const count = Array.isArray(usersArr) ? usersArr.length : (usersArr ? 1 : 0); if (count === 0) return null; const titleText = Array.isArray(usersArr) ? usersArr.join(', ') : String(usersArr); return ( {emoji} {count} ); })}
)} {(() => { const msgReceipts = log.id ? receiptsByMsgId.get(log.id) : null; if (!msgReceipts || msgReceipts.length === 0) return null; return (
👁️ {lang === 'vi' ? 'Đã đọc bởi:' : 'Read by:'} {msgReceipts.map(r => { const userName = userMap.get(r.profile_id) || 'Unknown User'; let readTimeStr = ''; if (r.last_read_at) { const d = new Date(r.last_read_at); if (!isNaN(d.getTime())) { readTimeStr = d.toLocaleString(); } } return ( {userName} ); })}
); })()}
)}
{log.image_url ? ( log.image_url.includes('/chat-attachments/') ? ( signedGroupImgUrls[log.image_url] === 'pending' || !signedGroupImgUrls[log.image_url] ? (
⏳
) : ( {`Attachment { ev.target.style.display = 'none'; }} style={{ width: '40px', height: '40px', borderRadius: '6px', objectFit: 'cover', border: '1.5px solid var(--hu-border-strong)', cursor: 'zoom-in', boxShadow: 'var(--hu-shadow-sm)', transition: 'transform 0.15s ease, box-shadow 0.15s ease', }} onMouseEnter={ev => { ev.currentTarget.style.transform = 'scale(1.1)'; ev.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)'; }} onMouseLeave={ev => { ev.currentTarget.style.transform = 'scale(1)'; ev.currentTarget.style.boxShadow = 'var(--hu-shadow-sm)'; }} onClick={() => setZoomImg && setZoomImg(signedGroupImgUrls[log.image_url])} /> ) ) : ( {`Attachment { ev.target.style.display = 'none'; }} style={{ width: '40px', height: '40px', borderRadius: '6px', objectFit: 'cover', border: '1.5px solid var(--hu-border-strong)', cursor: 'zoom-in', boxShadow: 'var(--hu-shadow-sm)', transition: 'transform 0.15s ease, box-shadow 0.15s ease', }} onMouseEnter={ev => { ev.currentTarget.style.transform = 'scale(1.1)'; ev.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)'; }} onMouseLeave={ev => { ev.currentTarget.style.transform = 'scale(1)'; ev.currentTarget.style.boxShadow = 'var(--hu-shadow-sm)'; }} onClick={() => setZoomImg && setZoomImg(log.image_url)} /> ) ) : log.message_type === 'audio' && log.audio_url ? ( 🔊 ) : log.message_type === 'document' && log.document_url ? ( 📥 ) : ( — )}
{/* Pagination Controls */} {totalPages > 1 && (
{lang === 'vi' ? `Hiển thị ${startIndex + 1}-${Math.min(endIndex, totalItems)} trong tổng số ${totalItems} kết quả` : `Showing ${startIndex + 1}-${Math.min(endIndex, totalItems)} of ${totalItems} results`}
{getPaginationPages().map((p, idx) => ( p === '…' ? ( … ) : ( ) ))}
)}
); } window.CpChatLogsView = { renderChatLogs, };