Sindbad~EG File Manager
<?php
// Admin Chat Inbox Widget - Shows incoming chats from members/visitors
?>
<style>
#admin-chat-inbox {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 9999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
#admin-inbox-button {
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
color: white;
border: none;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
display: flex;
align-items: center;
justify-content: center;
font-size: 26px;
transition: all 0.3s ease;
position: relative;
}
#admin-inbox-button:hover {
transform: scale(1.1);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
}
#admin-inbox-button.has-unread {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 0 0 0 rgba(16, 185, 129, 0.7);
}
50% {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 0 0 10px rgba(16, 185, 129, 0);
}
100% {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), 0 0 0 0 rgba(16, 185, 129, 0);
}
}
.unread-badge {
position: absolute;
top: -5px;
right: -5px;
background: linear-gradient(135deg, #EF4444 0%, #DC2626 100%);
color: white;
min-width: 24px;
height: 24px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: bold;
border: 2px solid white;
padding: 0 6px;
}
#admin-inbox-panel {
display: none;
position: fixed;
bottom: 100px;
right: 20px;
width: 420px;
max-width: calc(100vw - 40px);
height: 600px;
max-height: 80vh;
background: white;
border-radius: 16px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
flex-direction: column;
overflow: hidden;
}
#admin-inbox-panel.open {
display: flex;
animation: slideUp 0.3s ease;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.inbox-header {
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
color: white;
padding: 16px 20px;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
.inbox-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.inbox-close {
background: none;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background 0.2s;
}
.inbox-close:hover {
background: rgba(255, 255, 255, 0.2);
}
.inbox-tabs {
display: flex;
background: #f9fafb;
border-bottom: 1px solid #e5e7eb;
flex-shrink: 0;
}
.inbox-tab {
flex: 1;
padding: 12px;
background: none;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 500;
color: #6b7280;
border-bottom: 2px solid transparent;
transition: all 0.2s;
}
.inbox-tab.active {
color: #10B981;
border-bottom-color: #10B981;
background: white;
}
.inbox-list {
flex: 1;
overflow-y: auto;
background: #f9fafb;
}
.conversation-item {
padding: 16px;
background: white;
border-bottom: 1px solid #e5e7eb;
cursor: pointer;
transition: all 0.2s;
display: flex;
gap: 12px;
align-items: start;
}
.conversation-item:hover {
background: #f0fdf4;
}
.conversation-item.unread {
background: #ecfdf5;
border-left: 3px solid #10B981;
}
.conversation-avatar {
width: 44px;
height: 44px;
border-radius: 50%;
background: linear-gradient(135deg, #1E40AF 0%, #9333EA 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 16px;
flex-shrink: 0;
}
.conversation-content {
flex: 1;
min-width: 0;
}
.conversation-header {
display: flex;
justify-content: space-between;
align-items: start;
margin-bottom: 4px;
}
.conversation-name {
font-weight: 600;
color: #1f2937;
font-size: 14px;
}
.conversation-time {
font-size: 12px;
color: #9ca3af;
}
.conversation-preview {
font-size: 13px;
color: #6b7280;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.conversation-item.unread .conversation-preview {
font-weight: 500;
color: #1f2937;
}
.empty-state {
padding: 40px 20px;
text-align: center;
color: #9ca3af;
}
.empty-state i {
font-size: 48px;
margin-bottom: 16px;
color: #d1d5db;
}
/* Conversation View */
#conversation-view {
display: none;
position: fixed;
bottom: 100px;
right: 20px;
width: 420px;
max-width: calc(100vw - 40px);
height: 600px;
max-height: 80vh;
background: white;
border-radius: 16px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
flex-direction: column;
}
#conversation-view.open {
display: flex;
animation: slideUp 0.3s ease;
}
.conversation-header-bar {
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
color: white;
padding: 16px 20px;
display: flex;
align-items: center;
gap: 12px;
}
.back-button {
background: none;
border: none;
color: white;
font-size: 20px;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background 0.2s;
}
.back-button:hover {
background: rgba(255, 255, 255, 0.2);
}
.conversation-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
background: #f9fafb;
}
.message-item {
margin-bottom: 16px;
display: flex;
gap: 8px;
align-items: start;
}
.message-item.admin {
flex-direction: row-reverse;
}
.message-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: linear-gradient(135deg, #1E40AF 0%, #9333EA 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: bold;
flex-shrink: 0;
}
.message-item.admin .message-avatar {
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
}
.message-bubble {
max-width: 70%;
padding: 10px 14px;
border-radius: 16px;
background: white;
color: #1f2937;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.message-item.admin .message-bubble {
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
color: white;
}
.message-time {
font-size: 11px;
color: #9ca3af;
margin-top: 4px;
text-align: right;
}
.message-item.admin .message-time {
color: rgba(255,255,255,0.8);
}
.conversation-input-area {
padding: 12px;
background: white;
border-top: 1px solid #e5e7eb;
}
.conversation-input-form {
display: flex;
gap: 8px;
}
.conversation-input {
flex: 1;
padding: 10px 14px;
border: 1px solid #d1d5db;
border-radius: 20px;
font-size: 14px;
outline: none;
resize: none;
}
.conversation-input:focus {
border-color: #10B981;
}
.send-button {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
color: white;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.2s;
}
.send-button:hover {
transform: scale(1.1);
}
@media (max-width: 768px) {
#admin-inbox-panel, #conversation-view {
width: calc(100% - 40px);
right: 20px;
left: 20px;
}
}
</style>
<div id="admin-chat-inbox">
<!-- Inbox Button -->
<button id="admin-inbox-button" onclick="toggleInbox()" aria-label="Chat Inbox">
<i class="fas fa-inbox"></i>
<span id="unread-badge" class="unread-badge" style="display: none;">0</span>
</button>
<!-- Inbox Panel -->
<div id="admin-inbox-panel">
<div class="inbox-header">
<h3><i class="fas fa-inbox mr-2"></i>Member Chats</h3>
<button class="inbox-close" onclick="closeInbox()">×</button>
</div>
<div class="inbox-tabs">
<button class="inbox-tab active" onclick="filterConversations('all')">
All <span id="count-all">(0)</span>
</button>
<button class="inbox-tab" onclick="filterConversations('unread')">
Unread <span id="count-unread">(0)</span>
</button>
</div>
<div id="inbox-list" class="inbox-list">
<div class="empty-state">
<i class="fas fa-comments"></i>
<p>No conversations yet</p>
</div>
</div>
</div>
<!-- Conversation View -->
<div id="conversation-view">
<div class="conversation-header-bar">
<button class="back-button" onclick="backToInbox()">
<i class="fas fa-arrow-left"></i>
</button>
<div>
<div class="conversation-name" id="current-conversation-name"></div>
<div class="conversation-time" id="current-conversation-type" style="font-size: 12px; opacity: 0.9;"></div>
</div>
</div>
<div id="conversation-messages" class="conversation-messages"></div>
<div class="conversation-input-area">
<form id="reply-form" class="conversation-input-form">
<textarea id="reply-input" class="conversation-input" rows="1" placeholder="Type your reply..."></textarea>
<button type="submit" class="send-button">
<i class="fas fa-paper-plane"></i>
</button>
</form>
</div>
</div>
</div>
<script>
let inboxOpen = false;
let conversationViewOpen = false;
let currentFilter = 'all';
let currentConversationId = null;
let conversations = [];
// Toggle Inbox
function toggleInbox() {
inboxOpen = !inboxOpen;
const panel = document.getElementById('admin-inbox-panel');
if (inboxOpen) {
panel.classList.add('open');
closeConversationView();
loadConversations();
} else {
panel.classList.remove('open');
}
}
function closeInbox() {
document.getElementById('admin-inbox-panel').classList.remove('open');
inboxOpen = false;
}
// Load Conversations
async function loadConversations() {
try {
const res = await fetch('<?php echo BASE_URL; ?>api/admin_chat_list.php');
const data = await res.json();
if (data.success) {
conversations = data.conversations || [];
updateConversationCounts();
renderConversations();
}
} catch (e) {
console.error('Failed to load conversations', e);
}
}
function updateConversationCounts() {
const all = conversations.length;
const unread = conversations.filter(c => c.unread_count > 0).length;
document.getElementById('count-all').textContent = `(${all})`;
document.getElementById('count-unread').textContent = `(${unread})`;
const badge = document.getElementById('unread-badge');
const button = document.getElementById('admin-inbox-button');
if (unread > 0) {
badge.textContent = unread > 99 ? '99+' : unread;
badge.style.display = 'flex';
button.classList.add('has-unread');
} else {
badge.style.display = 'none';
button.classList.remove('has-unread');
}
}
function renderConversations() {
const list = document.getElementById('inbox-list');
let filtered = conversations;
if (currentFilter === 'unread') {
filtered = conversations.filter(c => c.unread_count > 0);
}
if (filtered.length === 0) {
list.innerHTML = `
<div class="empty-state">
<i class="fas fa-comments"></i>
<p>${currentFilter === 'unread' ? 'No unread chats' : 'No conversations yet'}</p>
</div>
`;
return;
}
list.innerHTML = filtered.map(conv => `
<div class="conversation-item ${conv.unread_count > 0 ? 'unread' : ''}" onclick="openConversation(${conv.id})">
<div class="conversation-avatar">
${getInitials(conv.name)}
</div>
<div class="conversation-content">
<div class="conversation-header">
<span class="conversation-name">${escapeHtml(conv.name)}</span>
<span class="conversation-time">${formatTime(conv.last_message_time)}</span>
</div>
<div class="conversation-preview">
${escapeHtml(conv.last_message || 'No messages yet')}
</div>
</div>
</div>
`).join('');
}
function filterConversations(filter) {
currentFilter = filter;
document.querySelectorAll('.inbox-tab').forEach(tab => tab.classList.remove('active'));
event.target.classList.add('active');
renderConversations();
}
// Open Conversation
async function openConversation(conversationId) {
currentConversationId = conversationId;
const conv = conversations.find(c => c.id === conversationId);
if (!conv) return;
document.getElementById('current-conversation-name').textContent = conv.name;
document.getElementById('current-conversation-type').textContent = conv.type || 'Member';
closeInbox();
document.getElementById('conversation-view').classList.add('open');
conversationViewOpen = true;
await loadMessages(conversationId);
}
function backToInbox() {
document.getElementById('conversation-view').classList.remove('open');
conversationViewOpen = false;
toggleInbox();
}
function closeConversationView() {
document.getElementById('conversation-view').classList.remove('open');
conversationViewOpen = false;
}
// Load Messages
async function loadMessages(conversationId) {
try {
const params = new URLSearchParams({ conversation_id: conversationId });
const res = await fetch('<?php echo BASE_URL; ?>api/chat_fetch.php?' + params.toString());
const data = await res.json();
if (data.success) {
renderMessages(data.messages || []);
}
} catch (e) {
console.error('Failed to load messages', e);
}
}
function renderMessages(messages) {
const container = document.getElementById('conversation-messages');
container.innerHTML = messages.map(msg => `
<div class="message-item ${msg.sender_type === 'admin' ? 'admin' : ''}">
<div class="message-avatar">
${msg.sender_type === 'admin' ? 'A' : getInitials(msg.sender_name || 'U')}
</div>
<div>
<div class="message-bubble">
${escapeHtml(msg.message_text || '')}
</div>
<div class="message-time">${formatTime(msg.created_at)}</div>
</div>
</div>
`).join('');
container.scrollTop = container.scrollHeight;
}
// Send Reply
document.getElementById('reply-form')?.addEventListener('submit', async (e) => {
e.preventDefault();
const input = document.getElementById('reply-input');
const message = input.value.trim();
if (!message || !currentConversationId) return;
try {
const formData = new FormData();
formData.append('conversation_id', currentConversationId);
formData.append('message', message);
const res = await fetch('<?php echo BASE_URL; ?>api/admin_chat_reply.php', {
method: 'POST',
body: formData
});
const data = await res.json();
if (data.success) {
input.value = '';
loadMessages(currentConversationId);
loadConversations(); // Update conversation list
}
} catch (e) {
console.error('Failed to send reply', e);
}
});
// Utility Functions
function getInitials(name) {
const parts = name.split(' ');
if (parts.length >= 2) {
return (parts[0][0] + parts[1][0]).toUpperCase();
}
return name.substring(0, 2).toUpperCase();
}
function formatTime(timestamp) {
if (!timestamp) return '';
const date = new Date(timestamp);
const now = new Date();
const diff = now - date;
if (diff < 60000) return 'Just now';
if (diff < 3600000) return Math.floor(diff / 60000) + 'm ago';
if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago';
return date.toLocaleDateString();
}
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return String(text).replace(/[&<>"']/g, m => map[m]);
}
// Auto-refresh conversations every 10 seconds
setInterval(() => {
if (inboxOpen) {
loadConversations();
}
if (conversationViewOpen && currentConversationId) {
loadMessages(currentConversationId);
}
}, 10000);
// Initial load
loadConversations();
setInterval(loadConversations, 30000); // Check for new messages every 30s
</script>
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists