freealberta/map/app/public/js/admin-users.js

390 lines
16 KiB
JavaScript

/**
* Admin Users Management Module
* Handles user CRUD operations, email broadcasting, and user administration
*/
// User management state
let allUsersData = [];
// 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) {
displayUsers(data.users);
} 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) {
usersTableContainer.innerHTML = '<p class="empty-message">No users found.</p>';
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>`;
}
}
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>
</div>
<p id="users-loading" class="loading-message" style="display: none;">Loading...</p>
`;
usersTableContainer.innerHTML = tableHtml;
setupUserActionListeners();
}
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);
// Get the updated reference
const updatedContainer = document.querySelector('.users-list');
updatedContainer.addEventListener('click', 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();
}
});
}
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');
}
}
// Setup user-related event listeners
function setupUserEventListeners() {
// 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;
}
}
});
}
}
// Export user management functions
window.adminUsers = {
loadUsers,
displayUsers,
deleteUser,
sendLoginDetailsToUser,
createUser,
clearUserForm,
setupUserEventListeners,
getAllUsersData: () => allUsersData,
setAllUsersData: (data) => { allUsersData = data; }
};