/** * Admin Users Management Module * Handles user CRUD operations, email broadcasting, and user administration */ // User management state let allUsersData = []; let filteredUsersData = []; let currentSearchTerm = ''; let currentFilterType = 'all'; let eventListenersSetup = false; let usersInitialized = false; // User Management Functions async function loadUsers() { const loadingEl = document.getElementById('users-loading'); const emptyEl = document.getElementById('users-empty'); const tableBody = document.getElementById('users-table-body'); if (loadingEl) loadingEl.style.display = 'block'; if (emptyEl) emptyEl.style.display = 'none'; if (tableBody) tableBody.innerHTML = ''; try { const response = await fetch('/api/users'); const data = await response.json(); if (loadingEl) loadingEl.style.display = 'none'; if (data.success && data.users) { allUsersData = data.users; filteredUsersData = [...allUsersData]; displayUsers(filteredUsersData); updateSearchResults(); // Make sure event listeners are set up after loading users if (!eventListenersSetup) { setupUserEventListeners(); } usersInitialized = true; } else { throw new Error(data.error || 'Failed to load users'); } } catch (error) { console.error('Error loading users:', error); if (loadingEl) loadingEl.style.display = 'none'; if (emptyEl) { emptyEl.textContent = 'Failed to load users'; emptyEl.style.display = 'block'; } window.adminCore.showStatus('Failed to load users', 'error'); } } function displayUsers(users) { const container = document.querySelector('.users-list'); if (!container) return; // Find or create the users table container, preserving the header let usersTableContainer = container.querySelector('.users-table-container'); if (!usersTableContainer) { // If container doesn't exist, create it after the header const header = container.querySelector('.users-list-header'); usersTableContainer = document.createElement('div'); usersTableContainer.className = 'users-table-container'; if (header && header.nextSibling) { container.insertBefore(usersTableContainer, header.nextSibling); } else if (header) { container.appendChild(usersTableContainer); } else { container.appendChild(usersTableContainer); } } if (!users || users.length === 0) { const hasFilter = currentSearchTerm || currentFilterType !== 'all'; const emptyMessage = hasFilter ? 'No users match your search criteria.' : 'No users found.'; usersTableContainer.innerHTML = `
${emptyMessage}
`; return; } const cardsHtml = `
${users.map(user => { const createdDate = user.created_at || user['Created At'] || user.createdAt; const formattedDate = createdDate ? new Date(createdDate).toLocaleDateString() : 'N/A'; const isAdmin = user.admin || user.Admin || false; const userType = user.UserType || user.userType || user['User Type'] || (isAdmin ? 'admin' : 'user'); const userId = user.Id || user.id || user.ID; // Handle expiration info let expirationInfo = ''; if (user.ExpiresAt) { const expirationDate = new Date(user.ExpiresAt); const now = new Date(); const daysUntilExpiration = Math.floor((expirationDate - now) / (1000 * 60 * 60 * 24)); if (daysUntilExpiration < 0) { expirationInfo = `Expired ${Math.abs(daysUntilExpiration)} days ago`; } else if (daysUntilExpiration <= 3) { expirationInfo = `Expires in ${daysUntilExpiration} day${daysUntilExpiration !== 1 ? 's' : ''}`; } else { expirationInfo = `Expires: ${expirationDate.toLocaleDateString()}`; } } const cardClass = user.ExpiresAt && new Date(user.ExpiresAt) < new Date() ? 'user-card expired' : (user.ExpiresAt && new Date(user.ExpiresAt) - new Date() < 3 * 24 * 60 * 60 * 1000 ? 'user-card expires-soon' : 'user-card'); return `
${userType.charAt(0).toUpperCase() + userType.slice(1)} ${expirationInfo}
📞 ${window.adminCore.escapeHtml(user.phone || user.Phone || 'No phone')} • 📅 ${formattedDate}
`; }).join('')}
`; usersTableContainer.innerHTML = cardsHtml; setupUserActionListeners(); } // Keep for backward compatibility function searchUsers(searchTerm, filterType = 'all') { // Update the form inputs and call filterUsers const searchInput = document.getElementById('users-search'); const filterSelect = document.getElementById('users-filter-type'); if (searchInput) searchInput.value = searchTerm; if (filterSelect) filterSelect.value = filterType; filterUsers(); } function updateSearchResults() { const resultsElement = document.getElementById('users-search-results'); const resultsText = document.querySelector('.search-results-text'); if (!resultsElement || !resultsText) return; // Get current values from form const searchInput = document.getElementById('users-search'); const filterSelect = document.getElementById('users-filter-type'); const searchTerm = searchInput ? searchInput.value.trim() : ''; const filterType = filterSelect ? filterSelect.value : 'all'; const hasFilter = searchTerm || filterType !== 'all'; if (hasFilter) { resultsElement.style.display = 'block'; let message = `Showing ${filteredUsersData.length} of ${allUsersData.length} users`; if (searchTerm && filterType !== 'all') { message += ` matching "${searchTerm}" in ${filterType} accounts`; } else if (searchTerm) { message += ` matching "${searchTerm}"`; } else if (filterType !== 'all') { message += ` with ${filterType} accounts`; } resultsText.textContent = message; } else { resultsElement.style.display = 'none'; } } function clearSearch() { const searchInput = document.getElementById('users-search'); const filterSelect = document.getElementById('users-filter-type'); if (searchInput) searchInput.value = ''; if (filterSelect) filterSelect.value = 'all'; filterUsers(); } function setupUserActionListeners() { const container = document.querySelector('.users-list'); if (!container) return; // Use event delegation instead of replacing containers // This avoids destroying search controls and their event listeners // Remove any existing click listeners on the container to avoid duplicates if (container._userActionHandler) { container.removeEventListener('click', container._userActionHandler); } // Create the new handler const newHandler = function(e) { if (e.target.classList.contains('delete-user-btn')) { const userId = e.target.getAttribute('data-user-id'); const userEmail = e.target.getAttribute('data-user-email'); console.log('Delete button clicked for user:', userId); deleteUser(userId, userEmail); } else if (e.target.classList.contains('send-login-btn')) { const userId = e.target.getAttribute('data-user-id'); const userEmail = e.target.getAttribute('data-user-email'); console.log('Send login details button clicked for user:', userId); sendLoginDetailsToUser(userId, userEmail); } else if (e.target.id === 'email-all-users-btn') { console.log('Email All Users button clicked'); showEmailUsersModal(); } }; // Add the new handler and store reference for removal later container.addEventListener('click', newHandler); container._userActionHandler = newHandler; } async function deleteUser(userId, userEmail) { if (!confirm(`Are you sure you want to delete user "${userEmail}"? This action cannot be undone.`)) { return; } try { const response = await fetch(`/api/users/${userId}`, { method: 'DELETE' }); const data = await response.json(); if (data.success) { window.adminCore.showStatus(`User "${userEmail}" deleted successfully`, 'success'); loadUsers(); // Reload the users list } else { throw new Error(data.error || 'Failed to delete user'); } } catch (error) { console.error('Error deleting user:', error); window.adminCore.showStatus(`Failed to delete user: ${error.message}`, 'error'); } } async function sendLoginDetailsToUser(userId, userEmail) { if (!confirm(`Send login details to "${userEmail}"?`)) { return; } try { const response = await fetch(`/api/users/${userId}/send-login-details`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); if (data.success) { window.adminCore.showStatus(`Login details sent to "${userEmail}" successfully`, 'success'); } else { throw new Error(data.error || 'Failed to send login details'); } } catch (error) { console.error('Error sending login details:', error); window.adminCore.showStatus(`Failed to send login details: ${error.message}`, 'error'); } } async function createUser(e) { e.preventDefault(); const emailInput = document.getElementById('user-email'); const passwordInput = document.getElementById('user-password'); const nameInput = document.getElementById('user-name'); const phoneInput = document.getElementById('user-phone'); const userTypeSelect = document.getElementById('user-type'); const expireDaysInput = document.getElementById('user-expire-days'); const adminCheckbox = document.getElementById('user-is-admin'); const email = emailInput?.value.trim(); const password = passwordInput?.value; const name = nameInput?.value.trim(); const phone = phoneInput?.value.trim(); const userType = userTypeSelect?.value; const expireDays = userType === 'temp' ? parseInt(expireDaysInput?.value) : null; const admin = adminCheckbox?.checked; if (!email || !password) { window.adminCore.showStatus('Email and password are required', 'error'); return; } if (password.length < 6) { window.adminCore.showStatus('Password must be at least 6 characters long', 'error'); return; } if (userType === 'temp' && (!expireDays || expireDays < 1 || expireDays > 365)) { window.adminCore.showStatus('Expiration days must be between 1 and 365 for temporary users', 'error'); return; } try { const userData = { email, password, name: name || '', phone: phone || '', isAdmin: userType === 'admin' || admin, userType, expireDays }; const response = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(userData) }); const data = await response.json(); if (data.success) { window.adminCore.showStatus('User created successfully', 'success'); clearUserForm(); loadUsers(); // Reload the users list } else { throw new Error(data.error || 'Failed to create user'); } } catch (error) { console.error('Error creating user:', error); window.adminCore.showStatus(`Failed to create user: ${error.message}`, 'error'); } } function clearUserForm() { const form = document.getElementById('create-user-form'); if (form) { form.reset(); // Reset user type to default const userTypeSelect = document.getElementById('user-type'); if (userTypeSelect) { userTypeSelect.value = 'user'; } // Hide expiration group const expirationGroup = document.getElementById('expiration-group'); if (expirationGroup) { expirationGroup.style.display = 'none'; } // Re-enable admin checkbox const isAdminCheckbox = document.getElementById('user-is-admin'); if (isAdminCheckbox) { isAdminCheckbox.disabled = false; } window.adminCore.showStatus('User form cleared', 'info'); } } // Simple search function like cuts function filterUsers() { const searchTerm = document.getElementById('users-search').value.toLowerCase(); const filterType = document.getElementById('users-filter-type').value; let filteredUsers = allUsersData; // Apply type filter first if (filterType !== 'all') { filteredUsers = filteredUsers.filter(user => { const userType = user.UserType || user.userType || user['User Type'] || (user.admin || user.Admin ? 'admin' : 'user'); return userType === filterType; }); } // Apply search filter if (searchTerm) { filteredUsers = filteredUsers.filter(user => { // Handle different possible field names and null/undefined values const userName = user.name || user.Name || ''; const userEmail = user.email || user.Email || ''; const userPhone = user.phone || user.Phone || ''; // Prioritize name matches - if name matches, return true immediately if (userName.toLowerCase().includes(searchTerm)) { return true; } // Then check email and phone return userEmail.toLowerCase().includes(searchTerm) || userPhone.toLowerCase().includes(searchTerm); }); // Sort results to prioritize name matches at the top filteredUsers.sort((a, b) => { const nameA = a.name || a.Name || ''; const nameB = b.name || b.Name || ''; const nameAMatches = nameA.toLowerCase().includes(searchTerm); const nameBMatches = nameB.toLowerCase().includes(searchTerm); // If both match by name or both don't match by name, maintain original order if (nameAMatches === nameBMatches) { return 0; } // Prioritize name matches (true comes before false) return nameBMatches - nameAMatches; }); } filteredUsersData = filteredUsers; displayUsers(filteredUsersData); updateSearchResults(); } // Setup user-related event listeners function setupUserEventListeners() { // Prevent duplicate event listener setup if (eventListenersSetup) { console.log('User event listeners already set up, skipping'); return; } console.log('Setting up user event listeners...'); // User form submission const userForm = document.getElementById('create-user-form'); if (userForm) { userForm.addEventListener('submit', createUser); } // Clear user form button const clearUserBtn = document.getElementById('clear-user-form'); if (clearUserBtn) { clearUserBtn.addEventListener('click', clearUserForm); } // User type change listener const userTypeSelect = document.getElementById('user-type'); if (userTypeSelect) { userTypeSelect.addEventListener('change', (e) => { const expirationGroup = document.getElementById('expiration-group'); const isAdminCheckbox = document.getElementById('user-is-admin'); if (e.target.value === 'temp') { if (expirationGroup) expirationGroup.style.display = 'block'; if (isAdminCheckbox) { isAdminCheckbox.checked = false; isAdminCheckbox.disabled = true; } } else { if (expirationGroup) expirationGroup.style.display = 'none'; if (isAdminCheckbox) isAdminCheckbox.disabled = false; if (e.target.value === 'admin') { if (isAdminCheckbox) isAdminCheckbox.checked = true; } else { if (isAdminCheckbox) isAdminCheckbox.checked = false; } } }); } // Set up search and filter (simple like cuts manager) const searchInput = document.getElementById('users-search'); if (searchInput) { console.log('✅ Setting up users search input listener'); searchInput.addEventListener('input', () => filterUsers()); } else { console.log('⚠️ users-search input not found'); } const filterSelect = document.getElementById('users-filter-type'); if (filterSelect) { console.log('✅ Setting up users filter select listener'); filterSelect.addEventListener('change', () => filterUsers()); } else { console.log('⚠️ users-filter-type select not found'); } // Clear search button const clearSearchBtn = document.getElementById('clear-users-search'); if (clearSearchBtn) { clearSearchBtn.addEventListener('click', clearSearch); } // Mark event listeners as set up eventListenersSetup = true; } // Debug function to test search manually function debugUserSearch(testTerm = 'test') { console.log('=== DEBUG USER SEARCH ==='); console.log('All users count:', allUsersData.length); console.log('Current search term:', currentSearchTerm); console.log('Current filter type:', currentFilterType); console.log('Filtered users count:', filteredUsersData.length); // Set test term in search input and trigger filter const searchInput = document.getElementById('users-search'); const filterSelect = document.getElementById('users-filter-type'); if (searchInput) searchInput.value = testTerm; if (filterSelect) filterSelect.value = 'all'; console.log('Testing search with term:', testTerm); filterUsers(); console.log('After search - Filtered users count:', filteredUsersData.length); console.log('========================'); return { allUsers: allUsersData.length, filteredUsers: filteredUsersData.length, searchTerm: currentSearchTerm, filterType: currentFilterType }; } // Export user management functions window.adminUsers = { loadUsers, displayUsers, deleteUser, sendLoginDetailsToUser, createUser, clearUserForm, setupUserEventListeners, searchUsers, filterUsers, clearSearch, updateSearchResults, debugUserSearch, getAllUsersData: () => allUsersData, getFilteredUsersData: () => filteredUsersData, setAllUsersData: (data) => { allUsersData = data; filteredUsersData = [...allUsersData]; }, get isInitialized() { return usersInitialized; } };