2025-05-24 15:25:58 +02:00

485 lines
19 KiB
JavaScript

// User management functionality
// Load users
async function loadUsers() {
if (!config.isConfigured()) {
showAlert('Please configure your API settings first.', 'warning');
return;
}
const container = document.getElementById('usersContainer');
container.innerHTML = '<div class="text-center"><div class="loading-spinner"></div> Loading users...</div>';
try {
const response = await apiClient.getUsers();
// Handle structured response - extract users array from response object
const users = response.users || response;
displayUsers(users);
} catch (error) {
handleApiError(error, 'loading users');
container.innerHTML = '<div class="alert alert-danger">Failed to load users</div>';
}
}
// Display users
function displayUsers(users) {
const container = document.getElementById('usersContainer');
if (!users || users.length === 0) {
container.innerHTML = `
<div class="text-center py-5">
<i class="fas fa-user fa-3x text-muted mb-3"></i>
<h4>No users found</h4>
<p class="text-muted">Create your first user to get started!</p>
<button class="btn btn-primary" onclick="showCreateUserModal()">
<i class="fas fa-plus me-1"></i>Create User
</button>
</div>
`;
return;
}
const usersHtml = `
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Team</th>
<th>Role</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
${users.map(user => `
<tr>
<td>
<div class="d-flex align-items-center">
<div class="avatar-circle me-2">
${user.name.charAt(0).toUpperCase()}
</div>
${escapeHtml(user.name)}
</div>
</td>
<td>${escapeHtml(user.email)}</td>
<td>
<span class="badge bg-info" id="team-${user.team_id}">
Loading...
</span>
</td>
<td>
<span class="badge ${user.is_admin ? 'bg-danger' : 'bg-secondary'}">
${user.is_admin ? 'Admin' : 'User'}
</span>
</td>
<td class="text-muted small">${formatDate(user.created_at)}</td>
<td>
<div class="btn-group" role="group">
<button class="btn btn-sm btn-outline-primary" onclick="viewUser('${user.id}')">
<i class="fas fa-eye"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="editUser('${user.id}')">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-sm btn-outline-danger" onclick="deleteUser('${user.id}')">
<i class="fas fa-trash"></i>
</button>
</div>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
`;
container.innerHTML = usersHtml;
// Load team names
loadTeamNames();
}
// Load team names for display
async function loadTeamNames() {
try {
const response = await apiClient.getTeams();
// Handle structured response - extract teams array from response object
const teams = response.teams || response;
teams.forEach(team => {
const teamBadges = document.querySelectorAll(`#team-${team.id}`);
teamBadges.forEach(badge => {
badge.textContent = team.name;
});
});
} catch (error) {
console.error('Failed to load team names:', error);
}
}
// Show create user modal
async function showCreateUserModal() {
try {
const response = await apiClient.getTeams();
// Handle structured response - extract teams array from response object
const teams = response.teams || response;
const modalBody = `
<form id="createUserForm">
<div class="mb-3">
<label for="userName" class="form-label">Full Name *</label>
<input type="text" class="form-control" id="userName" required>
</div>
<div class="mb-3">
<label for="userEmail" class="form-label">Email *</label>
<input type="email" class="form-control" id="userEmail" required>
</div>
<div class="mb-3">
<label for="userPassword" class="form-label">Password *</label>
<input type="password" class="form-control" id="userPassword" required>
<div class="form-text">Minimum 8 characters</div>
</div>
<div class="mb-3">
<label for="userTeam" class="form-label">Team *</label>
<select class="form-select" id="userTeam" required>
<option value="">Select a team...</option>
${teams.map(team => `
<option value="${team.id}">${escapeHtml(team.name)}</option>
`).join('')}
</select>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="userIsAdmin">
<label class="form-check-label" for="userIsAdmin">
Administrator privileges
</label>
<div class="form-text">Admins can manage teams, users, and system settings</div>
</div>
</div>
</form>
`;
const modalFooter = `
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="createUser()">
<i class="fas fa-plus me-1"></i>Create User
</button>
`;
const modal = createModal('createUserModal', 'Create New User', modalBody, modalFooter);
// Handle form submission
document.getElementById('createUserForm').addEventListener('submit', (e) => {
e.preventDefault();
createUser();
});
modal.show();
// Focus on name field
setTimeout(() => document.getElementById('userName').focus(), 100);
} catch (error) {
handleApiError(error, 'loading teams for user creation');
}
}
// Create user
async function createUser() {
const name = document.getElementById('userName').value.trim();
const email = document.getElementById('userEmail').value.trim();
const password = document.getElementById('userPassword').value;
const teamId = document.getElementById('userTeam').value;
const isAdmin = document.getElementById('userIsAdmin').checked;
if (!name || !email || !password || !teamId) {
showAlert('All fields are required', 'danger');
return;
}
if (password.length < 8) {
showAlert('Password must be at least 8 characters long', 'danger');
return;
}
const createButton = document.querySelector('#createUserModal .btn-primary');
setLoadingState(createButton);
try {
await apiClient.createUser({
name,
email,
password,
team_id: teamId,
is_admin: isAdmin
});
showAlert('User created successfully!', 'success');
// Close modal and refresh users
bootstrap.Modal.getInstance(document.getElementById('createUserModal')).hide();
removeModal('createUserModal');
loadUsers();
} catch (error) {
handleApiError(error, 'creating user');
} finally {
setLoadingState(createButton, false);
}
}
// View user details
async function viewUser(userId) {
try {
const usersResponse = await apiClient.getUsers();
// Handle structured response - extract users array from response object
const users = usersResponse.users || usersResponse;
const user = users.find(u => u.id === userId);
if (!user) {
showAlert('User not found', 'danger');
return;
}
const teamsResponse = await apiClient.getTeams();
// Handle structured response - extract teams array from response object
const teams = teamsResponse.teams || teamsResponse;
const userTeam = teams.find(t => t.id === user.team_id);
const modalBody = `
<div class="row">
<div class="col-12">
<div class="text-center mb-4">
<div class="avatar-circle-large mx-auto mb-3">
${user.name.charAt(0).toUpperCase()}
</div>
<h5>${escapeHtml(user.name)}</h5>
<p class="text-muted">${escapeHtml(user.email)}</p>
<span class="badge ${user.is_admin ? 'bg-danger' : 'bg-secondary'} fs-6">
${user.is_admin ? 'Administrator' : 'User'}
</span>
</div>
<hr>
<div class="row">
<div class="col-md-6">
<h6>User Information</h6>
<p><strong>Team:</strong> ${userTeam ? escapeHtml(userTeam.name) : 'Unknown'}</p>
<p><strong>Created:</strong> ${formatDate(user.created_at)}</p>
<p><strong>ID:</strong> <code>${user.id}</code></p>
</div>
<div class="col-md-6">
<h6>Permissions</h6>
<p><strong>Admin Access:</strong> ${user.is_admin ? 'Yes' : 'No'}</p>
<p><strong>Status:</strong> <span class="badge bg-success">Active</span></p>
</div>
</div>
</div>
</div>
`;
const modalFooter = `
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="editUser('${userId}')">
<i class="fas fa-edit me-1"></i>Edit User
</button>
`;
const modal = createModal('viewUserModal', 'User Details', modalBody, modalFooter);
modal.show();
} catch (error) {
handleApiError(error, 'loading user details');
}
}
// Edit user
async function editUser(userId) {
try {
const usersResponse = await apiClient.getUsers();
// Handle structured response - extract users array from response object
const users = usersResponse.users || usersResponse;
const user = users.find(u => u.id === userId);
if (!user) {
showAlert('User not found', 'danger');
return;
}
const teamsResponse = await apiClient.getTeams();
// Handle structured response - extract teams array from response object
const teams = teamsResponse.teams || teamsResponse;
const modalBody = `
<form id="editUserForm">
<div class="mb-3">
<label for="editUserName" class="form-label">Full Name *</label>
<input type="text" class="form-control" id="editUserName"
value="${escapeHtml(user.name)}" required>
</div>
<div class="mb-3">
<label for="editUserEmail" class="form-label">Email *</label>
<input type="email" class="form-control" id="editUserEmail"
value="${escapeHtml(user.email)}" required>
</div>
<div class="mb-3">
<label for="editUserTeam" class="form-label">Team *</label>
<select class="form-select" id="editUserTeam" required>
${teams.map(team => `
<option value="${team.id}" ${team.id === user.team_id ? 'selected' : ''}>
${escapeHtml(team.name)}
</option>
`).join('')}
</select>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="editUserIsAdmin"
${user.is_admin ? 'checked' : ''}>
<label class="form-check-label" for="editUserIsAdmin">
Administrator privileges
</label>
</div>
</div>
<div class="mb-3">
<label for="editUserPassword" class="form-label">New Password</label>
<input type="password" class="form-control" id="editUserPassword">
<div class="form-text">Leave blank to keep current password</div>
</div>
</form>
`;
const modalFooter = `
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="saveUserChanges('${userId}')">
<i class="fas fa-save me-1"></i>Save Changes
</button>
`;
const modal = createModal('editUserModal', 'Edit User', modalBody, modalFooter);
// Handle form submission
document.getElementById('editUserForm').addEventListener('submit', (e) => {
e.preventDefault();
saveUserChanges(userId);
});
modal.show();
} catch (error) {
handleApiError(error, 'loading user for editing');
}
}
// Save user changes
async function saveUserChanges(userId) {
const name = document.getElementById('editUserName').value.trim();
const email = document.getElementById('editUserEmail').value.trim();
const teamId = document.getElementById('editUserTeam').value;
const isAdmin = document.getElementById('editUserIsAdmin').checked;
const password = document.getElementById('editUserPassword').value;
if (!name || !email || !teamId) {
showAlert('Name, email, and team are required', 'danger');
return;
}
const saveButton = document.querySelector('#editUserModal .btn-primary');
setLoadingState(saveButton);
try {
const updateData = {
name,
email,
team_id: teamId,
is_admin: isAdmin
};
if (password) {
if (password.length < 8) {
showAlert('Password must be at least 8 characters long', 'danger');
return;
}
updateData.password = password;
}
await apiClient.updateUser(userId, updateData);
showAlert('User updated successfully!', 'success');
// Close modal and refresh users
bootstrap.Modal.getInstance(document.getElementById('editUserModal')).hide();
removeModal('editUserModal');
loadUsers();
// Close view modal if open
const viewModal = document.getElementById('viewUserModal');
if (viewModal) {
bootstrap.Modal.getInstance(viewModal)?.hide();
removeModal('viewUserModal');
}
} catch (error) {
handleApiError(error, 'updating user');
} finally {
setLoadingState(saveButton, false);
}
}
// Delete user
function deleteUser(userId) {
confirmAction('Are you sure you want to delete this user? This action cannot be undone.', async () => {
try {
await apiClient.deleteUser(userId);
showAlert('User deleted successfully!', 'success');
loadUsers();
// Close any open modals
const modals = ['viewUserModal', 'editUserModal'];
modals.forEach(modalId => {
const modalElement = document.getElementById(modalId);
if (modalElement) {
bootstrap.Modal.getInstance(modalElement)?.hide();
removeModal(modalId);
}
});
} catch (error) {
handleApiError(error, 'deleting user');
}
});
}
// Add CSS for avatar circles
document.addEventListener('DOMContentLoaded', () => {
const style = document.createElement('style');
style.textContent = `
.avatar-circle {
width: 32px;
height: 32px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 14px;
}
.avatar-circle-large {
width: 80px;
height: 80px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 32px;
}
`;
document.head.appendChild(style);
});