Files
CGW-Quote-Builder/product_app/templates/user_manager.html
T
2026-04-11 00:04:09 -05:00

499 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>User Manager</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
margin-bottom: 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
color: #333;
}
.card {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
margin-bottom: 20px;
}
.card h2 {
color: #333;
margin-bottom: 20px;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 600;
}
.form-group input, .form-group select {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 5px;
font-size: 16px;
}
.form-group input:focus, .form-group select:focus {
outline: none;
border-color: #667eea;
}
.btn {
padding: 12px 30px;
border: none;
border-radius: 5px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
}
.btn-danger {
background: #f56565;
color: white;
}
.btn-danger:hover {
background: #e53e3e;
}
.btn-secondary {
background: #cbd5e0;
color: #333;
}
.btn-secondary:hover {
background: #a0aec0;
}
.user-item {
background: #f7fafc;
padding: 15px;
border-radius: 5px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
border-left: 4px solid #667eea;
}
.user-item.inactive {
opacity: 0.6;
border-left-color: #cbd5e0;
}
.user-info {
flex: 1;
}
.user-info strong {
color: #333;
display: block;
margin-bottom: 5px;
}
.user-info span {
color: #666;
font-size: 14px;
}
.user-actions {
display: flex;
gap: 10px;
}
.user-actions button {
padding: 8px 16px;
font-size: 14px;
}
.alert {
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
display: none;
}
.alert.show {
display: block;
}
.alert-success {
background: #c6f6d5;
color: #22543d;
border-left: 4px solid #48bb78;
}
.alert-error {
background: #fed7d7;
color: #742a2a;
border-left: 4px solid #f56565;
}
.active-toggle {
display: flex;
align-items: center;
gap: 8px;
}
.active-toggle input {
width: 20px;
height: 20px;
cursor: pointer;
}
.location-checkboxes {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-top: 10px;
}
.location-item {
display: flex;
align-items: center;
gap: 10px;
}
.location-item input {
width: 20px;
height: 20px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>👥 User Manager</h1>
<a href="/" style="color: #333; text-decoration: none;">← Back to Home</a>
</div>
<div class="alert alert-success" id="successAlert"></div>
<div class="alert alert-error" id="errorAlert"></div>
<!-- Add User Form -->
<div class="card">
<h2>Add New User</h2>
<form id="addUserForm">
<div class="form-group">
<label for="newUsername">Username</label>
<input type="text" id="newUsername" required>
</div>
<div class="form-group">
<label for="newPassword">Password</label>
<input type="password" id="newPassword" required minlength="6">
</div>
<div class="form-group">
<label for="defaultLocation">Default Location</label>
<select id="defaultLocation" required>
<option value="">Select Location</option>
<option value="LINDS">LINDS</option>
<option value="IOLA">IOLA</option>
<option value="KC">KC</option>
<option value="BMD">BMD</option>
</select>
</div>
<div class="form-group">
<label>Accessible Locations</label>
<div class="location-checkboxes" id="accessibleLocations">
<div class="location-item">
<input type="checkbox" id="loc_LINDS" value="LINDS">
<label for="loc_LINDS">LINDS</label>
</div>
<div class="location-item">
<input type="checkbox" id="loc_IOLA" value="IOLA">
<label for="loc_IOLA">IOLA</label>
</div>
<div class="location-item">
<input type="checkbox" id="loc_KC" value="KC">
<label for="loc_KC">KC</label>
</div>
<div class="location-item">
<input type="checkbox" id="loc_BMD" value="BMD">
<label for="loc_BMD">BMD</label>
</div>
</div>
</div>
<div class="form-group">
<label>Permissions</label>
<div class="location-checkboxes">
<div class="location-item">
<input type="checkbox" id="perm_manage_users" value="manage_users">
<label for="perm_manage_users">Manage Users</label>
</div>
<div class="location-item">
<input type="checkbox" id="perm_create_quotes" value="create_quotes">
<label for="perm_create_quotes">Create Quotes</label>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Add User</button>
</form>
</div>
<!-- User List -->
<div class="card">
<h2>Existing Users</h2>
<div id="userList"></div>
</div>
</div>
<script>
let users = [];
// Load users on page load
loadUsers();
async function loadUsers() {
try {
const response = await fetch('/users/api');
const data = await response.json();
if (data.status === 'success') {
users = data.users;
renderUsers();
} else {
showError(data.message);
}
} catch (error) {
showError('Error loading users: ' + error.message);
}
}
function renderUsers() {
const userList = document.getElementById('userList');
if (users.length === 0) {
userList.innerHTML = '<p style="color: #666;">No users found. Add a user above.</p>';
return;
}
userList.innerHTML = users.map((user, index) => `
<div class="user-item ${user.active ? '' : 'inactive'}">
<div class="user-info">
<strong>${user.username}</strong>
<span>Location: ${user.defaultLocation || 'Not set'} |
Status: ${user.active ? '<span style="color: #48bb78;">Active</span>' : '<span style="color: #f56565;">Inactive</span>'}</span>
</div>
<div class="user-actions">
<div class="active-toggle">
<input type="checkbox" ${user.active ? 'checked' : ''}
onchange="toggleActive(${index}, this.checked)">
<span>Active</span>
</div>
<button class="btn btn-secondary" onclick="changePassword(${index})">Change Password</button>
<button class="btn btn-danger" onclick="deleteUser(${index})">Delete</button>
</div>
</div>
`).join('');
}
// Add user form submission
document.getElementById('addUserForm').addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('newUsername').value;
const password = document.getElementById('newPassword').value;
const defaultLocation = document.getElementById('defaultLocation').value;
// Get accessible locations
const locationSettings = {};
['LINDS', 'IOLA', 'KC', 'BMD'].forEach(loc => {
const checkbox = document.getElementById(`loc_${loc}`);
if (checkbox.checked) {
locationSettings[loc] = { accessible: true, permissions: {} };
}
});
// Get permissions
const permissions = {};
if (document.getElementById('perm_manage_users').checked) {
permissions.manage_users = true;
}
if (document.getElementById('perm_create_quotes').checked) {
permissions.create_quotes = true;
}
try {
const response = await fetch('/users/api', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username,
password,
defaultLocation,
locationSettings,
permissions,
active: true
})
});
const data = await response.json();
if (data.status === 'success') {
showSuccess('User added successfully!');
users = data.users;
renderUsers();
e.target.reset();
} else {
showError(data.message);
}
} catch (error) {
showError('Error adding user: ' + error.message);
}
});
async function toggleActive(index, active) {
try {
const response = await fetch(`/users/api/${index}/active`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ active })
});
const data = await response.json();
if (data.status === 'success') {
showSuccess('User status updated!');
users = data.users;
renderUsers();
} else {
showError(data.message);
renderUsers(); // Revert checkbox
}
} catch (error) {
showError('Error updating user: ' + error.message);
renderUsers(); // Revert checkbox
}
}
async function changePassword(index) {
const newPassword = prompt('Enter new password (min 6 characters):');
if (!newPassword || newPassword.length < 6) {
alert('Password must be at least 6 characters long');
return;
}
const currentPassword = prompt('Enter YOUR password to confirm this change:');
if (!currentPassword) return;
try {
const response = await fetch(`/users/api/${index}/change-password`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
newPassword: newPassword,
currentUserPassword: currentPassword
})
});
const data = await response.json();
if (data.status === 'success') {
showSuccess(data.message);
} else {
showError(data.message);
}
} catch (error) {
showError('Error changing password: ' + error.message);
}
}
async function deleteUser(index) {
if (!confirm(`Are you sure you want to delete ${users[index].username}?`)) {
return;
}
try {
const response = await fetch(`/users/api/${index}`, {
method: 'DELETE'
});
const data = await response.json();
if (data.status === 'success') {
showSuccess('User deleted successfully!');
users = data.users;
renderUsers();
} else {
showError(data.message);
}
} catch (error) {
showError('Error deleting user: ' + error.message);
}
}
function showSuccess(message) {
const alert = document.getElementById('successAlert');
alert.textContent = message;
alert.classList.add('show');
setTimeout(() => alert.classList.remove('show'), 5000);
}
function showError(message) {
const alert = document.getElementById('errorAlert');
alert.textContent = message;
alert.classList.add('show');
setTimeout(() => alert.classList.remove('show'), 5000);
}
// Auto-check location when selected as default
document.getElementById('defaultLocation').addEventListener('change', (e) => {
const loc = e.target.value;
if (loc) {
document.getElementById(`loc_${loc}`).checked = true;
}
});
</script>
</body>
</html>