Dilip8756's picture
Upload 100 files
58c1398 verified
/**
* Admin Panel - User Management Logic (Premium Edition)
*/
let allMgmtUsers = [];
let filteredMgmtUsers = [];
let deleteTargetId = null;
let activeExpandedId = null;
async function loadUserManagement() {
const grid = document.getElementById('users-mgmt-grid');
if (!grid) return;
grid.innerHTML = '<div class="loading-state" style="padding:60px;"><i class="fa-solid fa-spinner fa-spin"></i><span>Fetching user directory...</span></div>';
try {
const res = await fetch('/api/admin/users');
allMgmtUsers = await res.json();
updateUserMgmtStats(allMgmtUsers);
applyUserMgmtFilters();
} catch (err) {
grid.innerHTML = '<div class="error-state">Error loading user management data.</div>';
}
}
function updateUserMgmtStats(users) {
const totalEl = document.getElementById('total-users-val');
const activeEl = document.getElementById('active-users-val');
const disabledEl = document.getElementById('disabled-users-val');
if (totalEl) totalEl.innerText = users.length;
if (activeEl) activeEl.innerText = users.filter(u => u.is_active).length;
if (disabledEl) disabledEl.innerText = users.filter(u => !u.is_active).length;
}
function applyUserMgmtFilters() {
const searchInput = document.getElementById('user-mgmt-search');
const searchTerm = searchInput ? searchInput.value.trim().toLowerCase() : '';
// We'll also use a global 'currentMgmtFilter' set by the tabs
const activeTab = document.querySelector('.u-tab-m.active');
const filterRole = activeTab ? activeTab.getAttribute('onclick').match(/'([^']+)'/)[1] : 'all';
filteredMgmtUsers = allMgmtUsers.filter(u => {
const searchable = `${u.id} ${u.name} ${u.phone} ${u.email}`.toLowerCase();
const matchesSearch = searchTerm === '' || searchable.includes(searchTerm);
const matchesRole = filterRole === 'all' || u.role === filterRole;
return matchesSearch && matchesRole;
});
renderUserMgmtGrid(filteredMgmtUsers);
}
function filterUserMgmt(role, btn) {
document.querySelectorAll('.u-tab-m').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
applyUserMgmtFilters();
}
function renderUserMgmtGrid(users) {
const grid = document.getElementById('users-mgmt-grid');
if (!grid) return;
if (users.length === 0) {
grid.innerHTML = '<div class="empty-state" style="padding:60px; grid-column: 1/-1;"><i class="fa-solid fa-user-slash"></i><span>No matching users found.</span></div>';
return;
}
grid.innerHTML = users.map(u => {
const initial = (u.name || 'U').charAt(0).toUpperCase();
const status = u.is_active ? 'active' : 'disabled';
const isExpanded = activeExpandedId === u.id;
// Role-Based Styling
let crystalClass = 'crystal-user';
let roleLabel = 'General User';
let roleIcon = 'fa-user-shield';
if (u.role === 'admin') {
crystalClass = 'crystal-admin';
roleLabel = 'System Admin';
roleIcon = 'fa-lock';
} else if (u.role === 'admin-user') {
crystalClass = 'crystal-agent';
roleLabel = 'Agent / Moderator';
roleIcon = 'fa-user-tie';
}
return `
<div class="card-u-standard ${!u.is_active ? 'is-disabled' : ''} ${isExpanded ? 'expanded' : ''}" id="user-m-card-${u.id}">
<!-- Header -->
<div class="card-u-header">
<div class="card-u-avatar ${crystalClass}">${initial}</div>
<div class="card-u-info">
<div class="card-u-name">${escapeHtml(u.name)}</div>
<div class="card-u-phone"><i class="fa-solid fa-phone"></i> ${escapeHtml(u.phone)}</div>
</div>
<div class="status-pill-u ${status}">${status}</div>
</div>
<!-- Body Swap Zone -->
<div class="card-u-body-wrap">
<div class="card-u-meta animate-scale" id="user-meta-${u.id}">
<span><i class="fa-solid fa-fingerprint"></i> ${u.id.slice(0, 10)}...</span>
<span><i class="fa-solid fa-clock-rotate-left"></i> ${escapeHtml(u.joined_at.split(' ')[0])}</span>
</div>
<div class="card-u-expansion animate-scale" id="user-expansion-${u.id}" style="display: none;"></div>
</div>
<!-- Actions -->
<div class="card-u-actions" id="user-actions-${u.id}">
<button class="btn-u-action" id="user-manage-btn-${u.id}" onclick="toggleUserCardExpansion('${u.id}')">
<i class="fa-solid fa-gears"></i> Manage Access
</button>
${u.role !== 'admin' ? `
<button class="btn-u-action ${u.is_active ? 'warning' : 'success'}" onclick="toggleUserMgmtStatus('${u.id}')">
<i class="fa-solid ${u.is_active ? 'fa-ban' : 'fa-check'}"></i>
${u.is_active ? 'Disable' : 'Enable'}
</button>
` : `
<button class="btn-u-action disabled" disabled title="Admin users cannot be disabled">
<i class="fa-solid fa-shield-halved"></i> Protected
</button>
`}
</div>
</div>`;
}).join('');
}
function toggleUserCardExpansion(userId) {
console.log('toggleUserCardExpansion called for:', userId);
const card = document.getElementById(`user-m-card-${userId}`);
const metaDiv = document.getElementById(`user-meta-${userId}`);
const expansionDiv = document.getElementById(`user-expansion-${userId}`);
const manageBtn = document.getElementById(`user-manage-btn-${userId}`);
const actionsDiv = document.getElementById(`user-actions-${userId}`);
console.log('Elements found:', { card: !!card, metaDiv: !!metaDiv, expansionDiv: !!expansionDiv, manageBtn: !!manageBtn, actionsDiv: !!actionsDiv });
if (!card || !metaDiv || !expansionDiv) {
console.error('Required elements not found!');
return;
}
const isExpanded = card.classList.contains('expanded');
console.log('Is expanded:', isExpanded);
if (!isExpanded) {
// Expand this card
console.log('Expanding card...');
card.classList.add('expanded');
metaDiv.style.display = 'none';
expansionDiv.style.display = 'flex';
// Find user data
const user = allMgmtUsers.find(u => u.id === userId) || filteredMgmtUsers.find(u => u.id === userId);
console.log('User data:', user);
if (!user) return;
// Build expansion content
const isAdmin = user.role === 'admin';
expansionDiv.innerHTML = `
<div class="u-tool-section">
<h5>Security Override</h5>
<div class="u-input-row">
<input type="password" id="new-pass-${userId}" class="u-input-premium" placeholder="Set New Password">
</div>
</div>
<div class="u-tool-section">
<div style="display: flex; gap: 10px;">
<button class="btn-u-action primary" onclick="openChangePasswordModal('${userId}')" style="flex: 1;">
<i class="fa-solid fa-key"></i> Change Password
</button>
${!isAdmin ? `
<button class="btn-u-action danger" onclick="openDeleteUserModal('${userId}', '${escapeHtml(user.name)}')" style="flex: 1;">
<i class="fa-solid fa-trash-can"></i> Delete User
</button>
` : `
<button class="btn-u-action disabled" disabled title="Admin users cannot be deleted" style="flex: 1;">
<i class="fa-solid fa-shield-halved"></i> Protected
</button>
`}
</div>
</div>
`;
// Update button to Cancel
if (manageBtn) manageBtn.innerHTML = '<i class="fa-solid fa-xmark"></i> Cancel';
// Hide Enable/Disable button in expanded mode
const enableDisableBtn = actionsDiv ? actionsDiv.querySelector('[onclick^="toggleUserMgmtStatus"]') : null;
if (enableDisableBtn) enableDisableBtn.style.display = 'none';
activeExpandedId = userId;
} else {
// Collapse this card
console.log('Collapsing card...');
card.classList.remove('expanded');
metaDiv.style.display = 'flex';
expansionDiv.style.display = 'none';
expansionDiv.innerHTML = '';
// Update button back to Manage Access
if (manageBtn) manageBtn.innerHTML = '<i class="fa-solid fa-gears"></i> Manage Access';
// Show Enable/Disable button again
const enableDisableBtn = actionsDiv ? actionsDiv.querySelector('[onclick^="toggleUserMgmtStatus"]') : null;
if (enableDisableBtn) enableDisableBtn.style.display = 'flex';
activeExpandedId = null;
}
}
async function toggleUserMgmtStatus(userId) {
try {
const res = await fetch(`/api/admin/users/${userId}/toggle-status`, { method: 'POST' });
const data = await res.json();
if (res.ok) {
// Collapse any expanded card and refresh list
activeExpandedId = null;
loadUserManagement();
if (typeof showToast === 'function') showToast('success', 'Status Updated', 'User access level has been modified.');
} else {
if (typeof showToast === 'function') showToast('error', 'Action Denied', data.error || 'Failed to update status.');
}
} catch (e) {
if (typeof showToast === 'function') showToast('error', 'Network Error', 'Failed to connect to server.');
}
}
async function updateUserMgmtPassword(userId) {
const passInput = document.getElementById(`new-pass-${userId}`);
const newPass = passInput ? passInput.value.trim() : '';
if (!newPass) {
alert('Please enter a password.');
return;
}
try {
const res = await fetch(`/api/admin/users/${userId}/change-password`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ new_password: newPass })
});
if (res.ok) {
passInput.value = '';
if (typeof showToast === 'function') showToast('success', 'Security Update', 'User password has been manually overridden.');
}
} catch (e) {}
}
/* ─────────────────────────────────────────────────────
CHANGE PASSWORD MODAL LOGIC
───────────────────────────────────────────────────── */
let changePassTargetId = null;
function openChangePasswordModal(userId) {
changePassTargetId = userId;
const modal = document.getElementById('user-change-pass-modal');
const newPassEl = document.getElementById('change-pass-new');
const errorEl = document.getElementById('change-pass-error');
// Get the password from the input field
const passInput = document.getElementById(`new-pass-${userId}`);
const newPassword = passInput ? passInput.value : '';
if (!newPassword) {
if (typeof showToast === 'function') showToast('error', 'Error', 'Please enter a new password first.');
return;
}
if (newPassEl) newPassEl.value = newPassword;
if (errorEl) errorEl.innerText = '';
if (modal) modal.style.display = 'flex';
// Set up the confirm button
const finalBtn = document.getElementById('final-change-pass-btn');
if (finalBtn) {
finalBtn.onclick = confirmChangePassword;
}
}
function closeChangePasswordModal() {
const modal = document.getElementById('user-change-pass-modal');
if (modal) modal.style.display = 'none';
changePassTargetId = null;
}
async function confirmChangePassword() {
if (!changePassTargetId) return;
const newPassEl = document.getElementById('change-pass-new');
const errorEl = document.getElementById('change-pass-error');
const newPassword = newPassEl ? newPassEl.value : '';
if (errorEl) errorEl.innerText = 'Updating password...';
try {
const res = await fetch(`/api/admin/users/${changePassTargetId}/change-password`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ new_password: newPassword })
});
const data = await res.json();
if (res.ok) {
closeChangePasswordModal();
// Clear the password input
const passInput = document.getElementById(`new-pass-${changePassTargetId}`);
if (passInput) passInput.value = '';
if (typeof showToast === 'function') showToast('success', 'Success', 'User password has been changed.');
} else {
if (errorEl) errorEl.innerText = data.error || 'Failed to change password.';
}
} catch (e) {
if (errorEl) errorEl.innerText = 'Network error occurred.';
}
}
/* ─────────────────────────────────────────────────────
SECURE DELETION LOGIC
───────────────────────────────────────────────────── */
function openDeleteUserModal(id, name) {
deleteTargetId = id;
const modal = document.getElementById('user-delete-modal');
const nameEl = document.getElementById('delete-target-name');
const errorEl = document.getElementById('delete-error');
const passInput = document.getElementById('admin-auth-pass');
if (nameEl) nameEl.innerText = name;
if (errorEl) errorEl.innerText = '';
if (passInput) passInput.value = '';
if (modal) modal.style.display = 'flex';
// Set up the final button
const finalBtn = document.getElementById('final-delete-btn');
if (finalBtn) {
finalBtn.onclick = confirmDeleteUser;
}
}
function closeDeleteModal() {
const modal = document.getElementById('user-delete-modal');
if (modal) modal.style.display = 'none';
deleteTargetId = null;
}
async function confirmDeleteUser() {
if (!deleteTargetId) return;
const passInput = document.getElementById('admin-auth-pass');
const adminPass = passInput ? passInput.value : '';
const errorEl = document.getElementById('delete-error');
if (!adminPass) {
if (errorEl) errorEl.innerText = 'Authorization required.';
return;
}
if (errorEl) errorEl.innerText = 'Deleting account...';
try {
const res = await fetch(`/api/admin/users/${deleteTargetId}/delete`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ admin_password: adminPass })
});
const data = await res.json();
if (res.ok) {
// Show success toast first
if (typeof showToast === 'function') showToast('success', 'Success', 'User deleted successfully.');
// Wait 1 second before closing modal and refreshing
setTimeout(() => {
closeDeleteModal();
loadUserManagement();
}, 1000);
} else {
if (errorEl) errorEl.innerText = data.error || 'Authorization failed.';
if (typeof showToast === 'function') showToast('error', 'Error', data.error || 'Failed to delete user.');
}
} catch (e) {
if (errorEl) errorEl.innerText = 'Network error occurred.';
}
}
// Helper: Escape HTML
function escapeHtml(value) {
return String(value)
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}