485 lines
19 KiB
JavaScript
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);
|
|
});
|