- Add Modules and UserModules database tables - Create home page with module selection grid - Implement per-user module assignment in user management - Add route guards for module access control - Refactor navigation: login -> home -> modules, admin console via button - Add Font Awesome icons
324 lines
12 KiB
HTML
324 lines
12 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Manage Users - ScanLook{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="dashboard-container">
|
|
<div class="dashboard-header">
|
|
<div class="header-left">
|
|
<h1 class="page-title">Manage Users</h1>
|
|
<label class="filter-toggle">
|
|
<input type="checkbox" id="showInactive" onchange="toggleInactiveUsers()">
|
|
<span class="filter-label">Show Inactive Users</span>
|
|
</label>
|
|
</div>
|
|
<button class="btn btn-primary" onclick="openAddUser()">
|
|
<span class="btn-icon">+</span> Add User
|
|
</button>
|
|
</div>
|
|
|
|
{% if users %}
|
|
<div class="users-table-container">
|
|
<table class="users-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Username</th>
|
|
<th>Full Name</th>
|
|
<th>Email</th>
|
|
<th>Role</th>
|
|
<th>Location</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for user in users %}
|
|
<tr class="{{ 'inactive-user' if not user.is_active else '' }}">
|
|
<td><strong>{{ user.username }}</strong></td>
|
|
<td>{{ user.full_name }}</td>
|
|
<td>{{ user.email or '-' }}</td>
|
|
<td><span class="role-pill role-{{ user.role }}">{{ user.role }}</span></td>
|
|
<td>{{ user.branch }}</td>
|
|
<td>
|
|
{% if user.is_active %}
|
|
<span class="status-badge status-success">Active</span>
|
|
{% else %}
|
|
<span class="status-badge status-neutral">Inactive</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="action-buttons">
|
|
<button class="btn-action btn-edit" onclick="openEditUser({{ user.user_id }})" title="Edit">✏️</button>
|
|
{% if user.user_id != session.user_id %}
|
|
<button class="btn-action btn-delete" onclick="deleteUser({{ user.user_id }}, '{{ user.username }}')" title="Delete">🗑️</button>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<div class="empty-icon">👥</div>
|
|
<h2 class="empty-title">No Users Found</h2>
|
|
<p class="empty-text">Add your first user to get started</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Add/Edit User Modal -->
|
|
<div id="userModal" class="modal">
|
|
<div class="modal-content modal-large">
|
|
<div class="modal-header-bar">
|
|
<h3 class="modal-title" id="modalTitle">Add User</h3>
|
|
<button type="button" class="btn-close-modal" onclick="closeUserModal()">✕</button>
|
|
</div>
|
|
|
|
<form id="userForm" class="user-form">
|
|
<input type="hidden" id="userId" value="">
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Username *</label>
|
|
<input type="text" id="username" class="form-input" required autocomplete="off">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Password <span id="passwordOptional" style="display:none;">(leave blank to keep current)</span></label>
|
|
<input type="password" id="password" class="form-input" autocomplete="new-password">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">First Name *</label>
|
|
<input type="text" id="firstName" class="form-input" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Last Name *</label>
|
|
<input type="text" id="lastName" class="form-input" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Email</label>
|
|
<input type="email" id="email" class="form-input">
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Role *</label>
|
|
<select id="role" class="form-select" required>
|
|
{% if session.role == 'owner' %}
|
|
<option value="owner">Owner</option>
|
|
<option value="admin">Admin</option>
|
|
{% endif %}
|
|
<option value="staff">Staff</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Location</label>
|
|
<input type="text" id="branch" class="form-input" value="Main">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group" id="activeToggleGroup" style="display: none;">
|
|
<label class="form-label">
|
|
<input type="checkbox" id="isActive" checked> Active
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Module Access</label>
|
|
<div class="module-checkboxes" id="moduleCheckboxes">
|
|
{% for module in modules %}
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" name="modules" value="{{ module.module_id }}" class="module-checkbox">
|
|
{{ module.module_name }}
|
|
</label>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-actions">
|
|
<button type="button" class="btn btn-secondary" onclick="closeUserModal()">Cancel</button>
|
|
<button type="submit" class="btn btn-primary">Save User</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let editingUserId = null;
|
|
|
|
// Hide inactive users by default on page load
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
toggleInactiveUsers();
|
|
});
|
|
|
|
function toggleInactiveUsers() {
|
|
const showInactive = document.getElementById('showInactive').checked;
|
|
const inactiveRows = document.querySelectorAll('tr.inactive-user');
|
|
|
|
inactiveRows.forEach(row => {
|
|
row.style.display = showInactive ? '' : 'none';
|
|
});
|
|
}
|
|
|
|
function openAddUser() {
|
|
editingUserId = null;
|
|
document.getElementById('modalTitle').textContent = 'Add User';
|
|
document.getElementById('userForm').reset();
|
|
document.getElementById('userId').value = '';
|
|
document.getElementById('passwordOptional').style.display = 'none';
|
|
document.getElementById('password').required = true;
|
|
document.getElementById('activeToggleGroup').style.display = 'none';
|
|
// Uncheck all modules for new user
|
|
document.querySelectorAll('.module-checkbox').forEach(cb => cb.checked = false);
|
|
document.getElementById('userModal').style.display = 'flex';
|
|
}
|
|
function openEditUser(userId) {
|
|
editingUserId = userId;
|
|
document.getElementById('modalTitle').textContent = 'Edit User';
|
|
document.getElementById('passwordOptional').style.display = 'inline';
|
|
document.getElementById('password').required = false;
|
|
document.getElementById('activeToggleGroup').style.display = 'block';
|
|
|
|
// Fetch user data
|
|
fetch('/settings/users/' + userId)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const user = data.user;
|
|
document.getElementById('userId').value = user.user_id;
|
|
document.getElementById('username').value = user.username;
|
|
document.getElementById('firstName').value = user.first_name;
|
|
document.getElementById('lastName').value = user.last_name;
|
|
document.getElementById('email').value = user.email || '';
|
|
document.getElementById('role').value = user.role;
|
|
document.getElementById('branch').value = user.branch;
|
|
document.getElementById('isActive').checked = user.is_active == 1;
|
|
|
|
// If editing yourself, disable role change
|
|
const isEditingSelf = user.user_id === {{ session.user_id }};
|
|
document.getElementById('role').disabled = isEditingSelf;
|
|
|
|
// Load user's modules
|
|
fetch('/settings/users/' + userId + '/modules')
|
|
.then(resp => resp.json())
|
|
.then(moduleData => {
|
|
// Uncheck all first
|
|
document.querySelectorAll('.module-checkbox').forEach(cb => cb.checked = false);
|
|
// Check assigned modules
|
|
if (moduleData.success) {
|
|
moduleData.module_ids.forEach(id => {
|
|
const cb = document.querySelector(`.module-checkbox[value="${id}"]`);
|
|
if (cb) cb.checked = true;
|
|
});
|
|
}
|
|
// Show modal after modules are loaded
|
|
document.getElementById('userModal').style.display = 'flex';
|
|
});
|
|
} else {
|
|
alert(data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert('Error loading user data');
|
|
console.error(error);
|
|
});
|
|
}
|
|
function closeUserModal() {
|
|
document.getElementById('userModal').style.display = 'none';
|
|
document.getElementById('userForm').reset();
|
|
}
|
|
|
|
document.getElementById('userForm').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
const userId = document.getElementById('userId').value;
|
|
const userData = {
|
|
username: document.getElementById('username').value,
|
|
password: document.getElementById('password').value,
|
|
first_name: document.getElementById('firstName').value,
|
|
last_name: document.getElementById('lastName').value,
|
|
email: document.getElementById('email').value,
|
|
role: document.getElementById('role').value,
|
|
branch: document.getElementById('branch').value,
|
|
is_active: document.getElementById('isActive').checked ? 1 : 0
|
|
};
|
|
|
|
const url = userId ? `/settings/users/${userId}/update` : '/settings/users/add';
|
|
|
|
fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(userData)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// Save modules if editing existing user
|
|
if (userId) {
|
|
const moduleIds = Array.from(document.querySelectorAll('.module-checkbox:checked'))
|
|
.map(cb => parseInt(cb.value));
|
|
|
|
fetch(`/settings/users/${userId}/modules`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ module_ids: moduleIds })
|
|
}).then(() => {
|
|
closeUserModal();
|
|
location.reload();
|
|
});
|
|
} else {
|
|
closeUserModal();
|
|
location.reload();
|
|
}
|
|
} else {
|
|
alert(data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert('Error saving user');
|
|
console.error(error);
|
|
});
|
|
});
|
|
|
|
function deleteUser(userId, username) {
|
|
if (!confirm(`Delete user "${username}"? This will deactivate their account.`)) {
|
|
return;
|
|
}
|
|
|
|
fetch(`/settings/users/${userId}/delete`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert(data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert('Error deleting user');
|
|
console.error(error);
|
|
});
|
|
}
|
|
|
|
// Close modal on escape
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
closeUserModal();
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|