Add admin user management template

This commit is contained in:
2025-12-10 10:44:00 +11:00
parent c1e589ae18
commit 5bf24ac51c

278
templates/admin_users.html Normal file
View File

@@ -0,0 +1,278 @@
{% extends "base.html" %}
{% block title %}User Management - Raccoon Timekeeper{% endblock %}
{% block tagline %}Manage users{% endblock %}
{% block content %}
<section class="card">
<div class="card-header">
<h2>User Management</h2>
<button class="btn btn-primary" onclick="openAddUserModal()">
<span class="btn-icon">+</span>
Add User
</button>
</div>
<div id="usersList" class="users-list">
<div class="loading">Loading users...</div>
</div>
</section>
<!-- Add User Modal -->
<div id="addUserModal" class="modal">
<div class="modal-content modal-large">
<div class="modal-header">
<h3>Add New User</h3>
<button class="modal-close" onclick="closeModal('addUserModal')">×</button>
</div>
<div class="modal-body">
<div class="form-grid">
<div class="form-group">
<label for="newUsername">Username *</label>
<input type="text" id="newUsername" placeholder="Username (min 3 chars)" minlength="3">
</div>
<div class="form-group">
<label for="newEmail">Email *</label>
<input type="email" id="newEmail" placeholder="user@example.com">
</div>
<div class="form-group">
<label for="newDisplayName">Display Name</label>
<input type="text" id="newDisplayName" placeholder="Display name (optional)">
</div>
<div class="form-group">
<label for="newPassword">Password *</label>
<input type="password" id="newPassword" placeholder="Min 8 characters" minlength="8">
</div>
<div class="form-group checkbox-group">
<label class="checkbox-label">
<input type="checkbox" id="newIsAdmin">
<span>Administrator</span>
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('addUserModal')">Cancel</button>
<button class="btn btn-primary" onclick="saveNewUser()">Create User</button>
</div>
</div>
</div>
<!-- Edit User Modal -->
<div id="editUserModal" class="modal">
<div class="modal-content modal-large">
<div class="modal-header">
<h3>Edit User</h3>
<button class="modal-close" onclick="closeModal('editUserModal')">×</button>
</div>
<div class="modal-body">
<input type="hidden" id="editUserId">
<div class="form-grid">
<div class="form-group">
<label for="editUsername">Username</label>
<input type="text" id="editUsername" disabled>
</div>
<div class="form-group">
<label for="editEmail">Email</label>
<input type="email" id="editEmail" placeholder="user@example.com">
</div>
<div class="form-group">
<label for="editDisplayName">Display Name</label>
<input type="text" id="editDisplayName" placeholder="Display name">
</div>
<div class="form-group">
<label for="editPassword">New Password</label>
<input type="password" id="editPassword" placeholder="Leave blank to keep current">
</div>
<div class="form-group checkbox-group">
<label class="checkbox-label">
<input type="checkbox" id="editIsAdmin">
<span>Administrator</span>
</label>
</div>
<div class="form-group checkbox-group">
<label class="checkbox-label">
<input type="checkbox" id="editIsActive">
<span>Active</span>
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-danger" onclick="deleteUser()">Delete User</button>
<button class="btn btn-secondary" onclick="closeModal('editUserModal')">Cancel</button>
<button class="btn btn-primary" onclick="saveEditUser()">Save Changes</button>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
const currentUserId = {{ current_user.id }};
document.addEventListener('DOMContentLoaded', loadUsers);
async function loadUsers() {
try {
const response = await fetch('/api/admin/users');
const users = await response.json();
renderUsers(users);
} catch (error) {
showToast('Error loading users', 'error');
}
}
function renderUsers(users) {
const container = document.getElementById('usersList');
let html = '<table class="data-table"><thead><tr>' +
'<th>User</th><th>Email</th><th>Role</th><th>Status</th><th>Last Login</th><th></th>' +
'</tr></thead><tbody>';
users.forEach(user => {
const lastLogin = user.last_login ? new Date(user.last_login).toLocaleDateString() : 'Never';
const isCurrentUser = user.id === currentUserId;
html += `<tr class="${!user.is_active ? 'inactive' : ''}">
<td>
<div class="user-cell">
<span class="user-avatar">${(user.display_name || user.username)[0].toUpperCase()}</span>
<div>
<strong>${user.display_name || user.username}</strong>
<small>@${user.username}</small>
</div>
</div>
</td>
<td>${user.email}</td>
<td>${user.is_admin ? '<span class="badge admin-badge">Admin</span>' : '<span class="badge">User</span>'}</td>
<td>${user.is_active ? '<span class="status-active">Active</span>' : '<span class="status-inactive">Inactive</span>'}</td>
<td>${lastLogin}</td>
<td>
<button class="btn btn-icon" onclick='openEditUserModal(${JSON.stringify(user)})'>✏️</button>
</td>
</tr>`;
});
html += '</tbody></table>';
container.innerHTML = html;
}
function openAddUserModal() {
document.getElementById('addUserModal').classList.add('active');
document.getElementById('newUsername').value = '';
document.getElementById('newEmail').value = '';
document.getElementById('newDisplayName').value = '';
document.getElementById('newPassword').value = '';
document.getElementById('newIsAdmin').checked = false;
document.getElementById('newUsername').focus();
}
function openEditUserModal(user) {
document.getElementById('editUserId').value = user.id;
document.getElementById('editUsername').value = user.username;
document.getElementById('editEmail').value = user.email;
document.getElementById('editDisplayName').value = user.display_name || '';
document.getElementById('editPassword').value = '';
document.getElementById('editIsAdmin').checked = user.is_admin;
document.getElementById('editIsActive').checked = user.is_active;
document.getElementById('editUserModal').classList.add('active');
}
function closeModal(modalId) {
document.getElementById(modalId).classList.remove('active');
}
async function saveNewUser() {
const data = {
username: document.getElementById('newUsername').value.trim(),
email: document.getElementById('newEmail').value.trim(),
display_name: document.getElementById('newDisplayName').value.trim(),
password: document.getElementById('newPassword').value,
is_admin: document.getElementById('newIsAdmin').checked
};
if (!data.username || !data.email || !data.password) {
showToast('Fill in all required fields', 'error');
return;
}
try {
const response = await fetch('/api/admin/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
showToast('User created!', 'success');
closeModal('addUserModal');
loadUsers();
} else {
showToast(result.message, 'error');
}
} catch (error) {
showToast('Error creating user', 'error');
}
}
async function saveEditUser() {
const id = document.getElementById('editUserId').value;
const data = {
email: document.getElementById('editEmail').value.trim(),
display_name: document.getElementById('editDisplayName').value.trim(),
is_admin: document.getElementById('editIsAdmin').checked,
is_active: document.getElementById('editIsActive').checked
};
const password = document.getElementById('editPassword').value;
if (password) data.password = password;
try {
const response = await fetch(`/api/admin/users/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
showToast('User updated!', 'success');
closeModal('editUserModal');
loadUsers();
} else {
showToast(result.message, 'error');
}
} catch (error) {
showToast('Error updating user', 'error');
}
}
async function deleteUser() {
const id = document.getElementById('editUserId').value;
if (parseInt(id) === currentUserId) {
showToast('Cannot delete your own account', 'error');
return;
}
if (!confirm('Delete this user? All their data will be permanently removed.')) return;
try {
const response = await fetch(`/api/admin/users/${id}`, { method: 'DELETE' });
const result = await response.json();
if (result.success) {
showToast('User deleted', 'success');
closeModal('editUserModal');
loadUsers();
} else {
showToast(result.message, 'error');
}
} catch (error) {
showToast('Error deleting user', 'error');
}
}
</script>
{% endblock %}