Updates to user management

This commit is contained in:
2025-09-11 13:42:07 -06:00
parent 006cbcf9c3
commit b61e48f4fc
8 changed files with 810 additions and 256 deletions

View File

@@ -262,8 +262,18 @@ function loadSectionData(sectionId) {
}
break;
case 'users':
if (typeof loadUsers === 'function') {
loadUsers();
// Load users and set up event listeners
if (window.adminUsers && typeof window.adminUsers.loadUsers === 'function') {
if (!window.adminUsers.isInitialized) {
console.log('Loading users for first time...');
window.adminUsers.loadUsers();
} else {
console.log('Users already initialized');
// Ensure event listeners are set up even if already initialized
if (typeof window.adminUsers.setupUserEventListeners === 'function') {
window.adminUsers.setupUserEventListeners();
}
}
}
break;
case 'start-location':

View File

@@ -5,6 +5,11 @@
// User management state
let allUsersData = [];
let filteredUsersData = [];
let currentSearchTerm = '';
let currentFilterType = 'all';
let eventListenersSetup = false;
let usersInitialized = false;
// User Management Functions
async function loadUsers() {
@@ -23,7 +28,17 @@ async function loadUsers() {
if (loadingEl) loadingEl.style.display = 'none';
if (data.success && data.users) {
displayUsers(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');
}
@@ -61,113 +76,171 @@ function displayUsers(users) {
}
if (!users || users.length === 0) {
usersTableContainer.innerHTML = '<p class="empty-message">No users found.</p>';
const hasFilter = currentSearchTerm || currentFilterType !== 'all';
const emptyMessage = hasFilter ?
'No users match your search criteria.' :
'No users found.';
usersTableContainer.innerHTML = `<div class="no-users-found">${emptyMessage}</div>`;
return;
}
const tableHtml = `
<div class="users-table-wrapper">
<table class="users-table">
<thead>
<tr>
<th>Email</th>
<th>Name</th>
<th>Phone</th>
<th>Role</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="users-table-body">
${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 || (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 = `<span class="expiration-info expiration-warning">Expired ${Math.abs(daysUntilExpiration)} days ago</span>`;
} else if (daysUntilExpiration <= 3) {
expirationInfo = `<span class="expiration-info expiration-warning">Expires in ${daysUntilExpiration} day${daysUntilExpiration !== 1 ? 's' : ''}</span>`;
} else {
expirationInfo = `<span class="expiration-info">Expires: ${expirationDate.toLocaleDateString()}</span>`;
}
}
const cardsHtml = `
<div class="users-cards-container">
${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 = `<span class="expiration-info expiration-warning">Expired ${Math.abs(daysUntilExpiration)} days ago</span>`;
} else if (daysUntilExpiration <= 3) {
expirationInfo = `<span class="expiration-info expiration-warning">Expires in ${daysUntilExpiration} day${daysUntilExpiration !== 1 ? 's' : ''}</span>`;
} else {
expirationInfo = `<span class="expiration-info">Expires: ${expirationDate.toLocaleDateString()}</span>`;
}
}
return `
<tr ${user.ExpiresAt && new Date(user.ExpiresAt) < new Date() ? 'class="expired"' : (user.ExpiresAt && new Date(user.ExpiresAt) - new Date() < 3 * 24 * 60 * 60 * 1000 ? 'class="expires-soon"' : '')}>
<td data-label="Email">${window.adminCore.escapeHtml(user.email || user.Email || 'N/A')}</td>
<td data-label="Name">${window.adminCore.escapeHtml(user.name || user.Name || 'N/A')}</td>
<td data-label="Phone">${window.adminCore.escapeHtml(user.phone || user.Phone || 'N/A')}</td>
<td data-label="Role">
<span class="user-role ${userType}">
${userType.charAt(0).toUpperCase() + userType.slice(1)}
</span>
${expirationInfo}
</td>
<td data-label="Created">${formattedDate}</td>
<td data-label="Actions">
<div class="user-actions">
<div class="user-communication-actions">
<a href="mailto:${window.adminCore.escapeHtml(user.email || user.Email)}"
class="btn btn-sm btn-outline-primary"
title="Email ${window.adminCore.escapeHtml(user.name || user.Name || 'User')}">
📧
</a>
<a href="sms:${window.adminCore.escapeHtml(user.phone || user.Phone || '')}"
class="btn btn-sm btn-outline-secondary ${!(user.phone || user.Phone) ? 'disabled' : ''}"
title="Text ${window.adminCore.escapeHtml(user.name || user.Name || 'User')}${!(user.phone || user.Phone) ? ' (No phone number)' : ''}">
💬
</a>
<a href="tel:${window.adminCore.escapeHtml(user.phone || user.Phone || '')}"
class="btn btn-sm btn-outline-success ${!(user.phone || user.Phone) ? 'disabled' : ''}"
title="Call ${window.adminCore.escapeHtml(user.name || user.Name || 'User')}${!(user.phone || user.Phone) ? ' (No phone number)' : ''}">
📞
</a>
</div>
<div class="user-admin-actions">
<button class="btn btn-secondary btn-sm send-login-btn" data-user-id="${userId}" data-user-email="${window.adminCore.escapeHtml(user.email || user.Email)}">
Send Login Details
</button>
<button class="btn btn-danger btn-sm delete-user-btn" data-user-id="${userId}" data-user-email="${window.adminCore.escapeHtml(user.email || user.Email)}">
Delete
</button>
</div>
</div>
</td>
</tr>
`;
}).join('')}
</tbody>
</table>
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 `
<div class="${cardClass}">
<div class="user-card-header">
<div class="user-info-primary">
<h4 class="user-name">${window.adminCore.escapeHtml(user.name || user.Name || 'N/A')}</h4>
<p class="user-email">${window.adminCore.escapeHtml(user.email || user.Email || 'N/A')}</p>
</div>
<div class="user-role-badge">
<span class="user-role ${userType}">
${userType.charAt(0).toUpperCase() + userType.slice(1)}
</span>
${expirationInfo}
</div>
</div>
<div class="user-card-body">
<div class="user-details-compact">
📞 ${window.adminCore.escapeHtml(user.phone || user.Phone || 'No phone')} • 📅 ${formattedDate}
</div>
</div>
<div class="user-card-footer">
<div class="user-actions">
<div class="user-communication-actions">
<a href="mailto:${window.adminCore.escapeHtml(user.email || user.Email)}"
class="btn btn-sm btn-outline-primary"
title="Email ${window.adminCore.escapeHtml(user.name || user.Name || 'User')}">
📧
</a>
<a href="sms:${window.adminCore.escapeHtml(user.phone || user.Phone || '')}"
class="btn btn-sm btn-outline-secondary ${!(user.phone || user.Phone) ? 'disabled' : ''}"
title="Text ${window.adminCore.escapeHtml(user.name || user.Name || 'User')}${!(user.phone || user.Phone) ? ' (No phone number)' : ''}">
💬
</a>
<a href="tel:${window.adminCore.escapeHtml(user.phone || user.Phone || '')}"
class="btn btn-sm btn-outline-success ${!(user.phone || user.Phone) ? 'disabled' : ''}"
title="Call ${window.adminCore.escapeHtml(user.name || user.Name || 'User')}${!(user.phone || user.Phone) ? ' (No phone number)' : ''}">
📞
</a>
</div>
<div class="user-admin-actions">
<button class="btn btn-secondary btn-sm send-login-btn" data-user-id="${userId}" data-user-email="${window.adminCore.escapeHtml(user.email || user.Email)}">
Send Login
</button>
<button class="btn btn-sm delete-user-btn" data-user-id="${userId}" data-user-email="${window.adminCore.escapeHtml(user.email || user.Email)}" title="Delete User">
🗑️
</button>
</div>
</div>
</div>
</div>
`;
}).join('')}
</div>
<p id="users-loading" class="loading-message" style="display: none;">Loading...</p>
`;
usersTableContainer.innerHTML = tableHtml;
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;
// Remove existing event listeners by cloning the container
const newContainer = container.cloneNode(true);
container.parentNode.replaceChild(newContainer, container);
// Use event delegation instead of replacing containers
// This avoids destroying search controls and their event listeners
// Get the updated reference
const updatedContainer = document.querySelector('.users-list');
updatedContainer.addEventListener('click', function(e) {
// 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');
@@ -182,7 +255,11 @@ function setupUserActionListeners() {
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) {
@@ -334,8 +411,72 @@ function clearUserForm() {
}
}
// 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) {
@@ -373,6 +514,60 @@ function setupUserEventListeners() {
}
});
}
// 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
@@ -384,6 +579,16 @@ window.adminUsers = {
createUser,
clearUserForm,
setupUserEventListeners,
searchUsers,
filterUsers,
clearSearch,
updateSearchResults,
debugUserSearch,
getAllUsersData: () => allUsersData,
setAllUsersData: (data) => { allUsersData = data; }
getFilteredUsersData: () => filteredUsersData,
setAllUsersData: (data) => {
allUsersData = data;
filteredUsersData = [...allUsersData];
},
get isInitialized() { return usersInitialized; }
};