Files
GOV_QR_Codes_Management/templates/create_user.html
T
2026-03-20 17:14:58 -04:00

670 lines
23 KiB
HTML

{% extends "base_authenticated.html" %} {% block title %}Create New User - QR
Code Management{% endblock %} {% block content %}
<div class="create-user-page">
<div class="page-header">
<div class="header-content">
<h1>
<i class="fas fa-user-plus"></i>
Create New User
</h1>
<p>Add a new user to the system with appropriate role permissions</p>
</div>
</div>
<div class="create-user-container">
<form
id="createUserForm"
method="POST"
action="{{ url_for('users.create_user') }}"
>
<div class="form-section">
<h3>
<i class="fas fa-user"></i>
User Information
</h3>
<div class="form-row">
<div class="form-group">
<label for="full_name">
<i class="fas fa-id-card"></i>
Full Name
</label>
<input
type="text"
id="full_name"
name="full_name"
required
placeholder="Enter full name"
/>
<small class="form-help">First and last name</small>
</div>
<div class="form-group">
<label for="email">
<i class="fas fa-envelope"></i>
Email Address
</label>
<input
type="email"
id="email"
name="email"
required
placeholder="user@example.com"
/>
<small class="form-help">Must be unique in the system</small>
</div>
</div>
<div class="form-group">
<label for="username">
<i class="fas fa-user"></i>
Username
</label>
<input
type="text"
id="username"
name="username"
required
pattern="[a-zA-Z0-9_]+"
placeholder="Enter username"
/>
<small class="form-help"
>Letters, numbers, and underscores only</small
>
</div>
<div class="form-group">
<label for="role">
<i class="fas fa-user-shield"></i>
User Role
</label>
<select id="role" name="role" required>
<option value="">Select Role</option>
<option value="staff">Staff User</option>
<option value="payroll">Payroll Specialist</option>
<option value="accounting">Accounting Specialist</option>
<option value="project_manager">Project Manager</option>
<option value="admin">Administrator</option>
</select>
<small class="form-help"
>Determines user permissions and system access level</small
>
</div>
</div>
<div class="form-group">
<label for="password">
<i class="fas fa-lock"></i>
Initial Password
</label>
<input
type="password"
id="password"
name="password"
required
minlength="6"
placeholder="Enter initial password"
/>
<div class="password-strength" id="passwordStrength"></div>
<small class="form-help"
>Minimum 6 characters. User should change on first login</small
>
</div>
<!-- Project Manager Permissions Section -->
<div class="permissions-section" id="pmPermissionsSection" style="display: none;">
<div class="section-header">
<h3>
<i class="fas fa-shield-alt"></i>
Project Manager Permissions
</h3>
<p class="section-description">
Follow the steps below to configure access permissions for this Project Manager.
</p>
</div>
<!-- Step 1: Select Projects -->
<div class="permission-step" id="projectStep">
<div class="step-header">
<span class="step-number">1</span>
<div class="step-info">
<h4>Select Projects</h4>
<p>Choose which projects this Project Manager can access</p>
</div>
</div>
<div class="form-group">
<div class="checkbox-group" id="projectCheckboxGroup">
{% if projects %}
{% for project in projects %}
<label class="checkbox-label">
<input
type="checkbox"
name="assigned_projects"
value="{{ project.id }}"
class="checkbox-input project-checkbox"
data-project-name="{{ project.name }}"
>
<span class="checkbox-text">{{ project.name }}</span>
<span class="project-qr-count" title="QR codes in this project">
<i class="fas fa-qrcode"></i> {{ project.qr_count or 0 }}
</span>
</label>
{% endfor %}
{% else %}
<p class="text-muted">No active projects available</p>
{% endif %}
</div>
</div>
<div class="step-footer">
<div class="selection-summary" id="projectSummary">
<i class="fas fa-info-circle"></i>
<span>No projects selected</span>
</div>
</div>
</div>
<!-- Step 2: Select Locations -->
<div class="permission-step" id="locationStep" style="display: none;">
<div class="step-header">
<span class="step-number">2</span>
<div class="step-info">
<h4>Select Locations</h4>
<p>Choose specific locations within the selected projects</p>
</div>
</div>
<div class="form-group">
<div class="location-loading" id="locationLoading" style="display: none;">
<i class="fas fa-spinner fa-spin"></i>
<span>Loading locations...</span>
</div>
<div class="checkbox-group" id="locationCheckboxGroup">
<!-- Locations will be populated dynamically -->
</div>
<div class="location-empty" id="locationEmpty" style="display: none;">
<i class="fas fa-info-circle"></i>
<p>No locations found for the selected projects.</p>
<small>QR codes in these projects may not have location data.</small>
</div>
</div>
<div class="step-footer">
<div class="selection-summary" id="locationSummary">
<i class="fas fa-info-circle"></i>
<span>No locations selected</span>
</div>
</div>
</div>
<!-- Selection Overview -->
<div class="permissions-overview" id="permissionsOverview" style="display: none;">
<div class="overview-header">
<i class="fas fa-check-circle"></i>
<h4>Permission Summary</h4>
</div>
<div class="overview-content">
<div class="overview-item">
<strong>Projects:</strong>
<span id="overviewProjects">-</span>
</div>
<div class="overview-item">
<strong>Locations:</strong>
<span id="overviewLocations">-</span>
</div>
</div>
</div>
</div>
<!-- Role Information Panel -->
<div class="info-panel" id="roleInfo" style="display: none">
<div class="info-header">
<i class="fas fa-info-circle"></i>
<span>Role Permissions</span>
</div>
<div class="info-content" id="roleContent">
<!-- Dynamic content based on selected role -->
</div>
</div>
<!-- User Creation Summary -->
<div class="creation-summary" id="creationSummary" style="display: none">
<h3>
<i class="fas fa-check-circle"></i>
User Creation Summary
</h3>
<div class="summary-grid">
<div class="summary-item">
<strong>Name:</strong> <span id="summaryName">-</span>
</div>
<div class="summary-item">
<strong>Email:</strong> <span id="summaryEmail">-</span>
</div>
<div class="summary-item">
<strong>Username:</strong> <span id="summaryUsername">-</span>
</div>
<div class="summary-item">
<strong>Role:</strong> <span id="summaryRole">-</span>
</div>
</div>
</div>
<div class="form-actions">
<a href="{{ url_for('users.users') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i>
Back to Users
</a>
<button type="button" class="btn btn-info" id="previewUser">
<i class="fas fa-eye"></i>
Preview User
</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-user-plus"></i>
Create User
</button>
</div>
</form>
</div>
</div>
{% endblock %} {% block extra_scripts %}
<script>
document.addEventListener("DOMContentLoaded", function () {
const form = document.getElementById("createUserForm");
const roleSelect = document.getElementById("role");
const roleInfo = document.getElementById("roleInfo");
const roleContent = document.getElementById("roleContent");
const previewBtn = document.getElementById("previewUser");
const creationSummary = document.getElementById("creationSummary");
// Permission section elements
const pmSection = document.getElementById('pmPermissionsSection');
const projectStep = document.getElementById('projectStep');
const locationStep = document.getElementById('locationStep');
const permissionsOverview = document.getElementById('permissionsOverview');
const projectCheckboxes = document.querySelectorAll('.project-checkbox');
const projectSummary = document.getElementById('projectSummary');
const locationSummary = document.getElementById('locationSummary');
const locationCheckboxGroup = document.getElementById('locationCheckboxGroup');
const locationLoading = document.getElementById('locationLoading');
const locationEmpty = document.getElementById('locationEmpty');
// Form inputs
const fullNameInput = document.getElementById("full_name");
const emailInput = document.getElementById("email");
const usernameInput = document.getElementById("username");
const passwordInput = document.getElementById("password");
const passwordStrength = document.getElementById("passwordStrength");
// UPDATED: Role information with new roles
const rolePermissions = {
staff: {
title: "Staff User Permissions",
permissions: [
"Create and edit QR codes",
"View all QR codes in the system",
"Download QR code images",
"Update personal profile information",
"Access dashboard and reports",
],
restrictions: [
"Cannot delete QR codes",
"Cannot manage other users",
"Cannot access admin settings",
],
},
payroll: {
title: "Payroll Specialist Permissions",
permissions: [
"Create and edit QR codes",
"View all QR codes in the system",
"Download QR code images",
"Update personal profile information",
"Access dashboard and reports",
"Same permissions as Staff (additional features coming soon)",
],
restrictions: [
"Cannot delete QR codes",
"Cannot manage other users",
"Cannot access admin settings",
],
},
project_manager: {
title: "Project Manager Permissions",
permissions: [
"View assigned projects only",
"View assigned locations only",
"Create and edit QR codes for assigned projects",
"View attendance data for assigned projects/locations",
"Download QR code images",
"Update personal profile information",
"Access dashboard with filtered data",
],
restrictions: [
"Cannot delete QR codes",
"Cannot manage other users",
"Cannot access admin settings",
"Can only view data for assigned projects and locations",
],
},
admin: {
title: "Administrator Permissions",
permissions: [
"Full QR code management (create, edit, delete)",
"Complete user management capabilities",
"System configuration access",
"View all system analytics",
"Bulk operations and data export",
"Access to all admin features",
],
restrictions: ["With great power comes great responsibility!"],
},
};
// Role selection handler
roleSelect.addEventListener("change", function () {
const selectedRole = this.value;
if (selectedRole && rolePermissions[selectedRole]) {
const roleData = rolePermissions[selectedRole];
let permissionsHtml = `
<h4>${roleData.title}</h4>
<div class="permissions-section">
<h5><i class="fas fa-check-circle text-success"></i> Permissions</h5>
<ul class="permissions-list">
`;
roleData.permissions.forEach((permission) => {
permissionsHtml += `<li><i class="fas fa-check"></i> ${permission}</li>`;
});
permissionsHtml += `
</ul>
<h5><i class="fas fa-times-circle text-warning"></i> Restrictions</h5>
<ul class="restrictions-list">
`;
roleData.restrictions.forEach((restriction) => {
permissionsHtml += `<li><i class="fas fa-times"></i> ${restriction}</li>`;
});
permissionsHtml += `</ul></div>`;
roleContent.innerHTML = permissionsHtml;
roleInfo.style.display = "block";
setTimeout(() => roleInfo.classList.add("fade-in"), 100);
} else {
roleInfo.style.display = "none";
roleInfo.classList.remove("fade-in");
}
// Show/hide Project Manager permissions section
if (selectedRole === 'project_manager') {
pmSection.style.display = 'block';
setupPermissionFlow();
} else {
pmSection.style.display = 'none';
resetPermissionFlow();
}
});
// Setup cascading permission flow
function setupPermissionFlow() {
// Reset state
locationStep.style.display = 'none';
permissionsOverview.style.display = 'none';
locationCheckboxGroup.innerHTML = '';
// Add event listeners to project checkboxes
projectCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', handleProjectSelection);
});
}
// Handle project selection changes
function handleProjectSelection() {
const selectedProjects = Array.from(projectCheckboxes)
.filter(cb => cb.checked)
.map(cb => ({
id: cb.value,
name: cb.dataset.projectName
}));
// Update project summary
if (selectedProjects.length > 0) {
projectSummary.innerHTML = `
<i class="fas fa-check-circle" style="color: #10b981;"></i>
<span><strong>${selectedProjects.length}</strong> project${selectedProjects.length > 1 ? 's' : ''} selected</span>
`;
// Show location step and load locations
locationStep.style.display = 'block';
loadLocationsByProjects(selectedProjects.map(p => p.id));
} else {
projectSummary.innerHTML = `
<i class="fas fa-info-circle"></i>
<span>No projects selected</span>
`;
locationStep.style.display = 'none';
permissionsOverview.style.display = 'none';
}
}
// Load locations for selected projects
async function loadLocationsByProjects(projectIds) {
locationLoading.style.display = 'flex';
locationCheckboxGroup.innerHTML = '';
locationEmpty.style.display = 'none';
locationSummary.innerHTML = `
<i class="fas fa-info-circle"></i>
<span>Loading locations...</span>
`;
try {
const response = await fetch('/api/locations-by-projects', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ project_ids: projectIds })
});
const data = await response.json();
locationLoading.style.display = 'none';
if (data.success && data.locations.length > 0) {
// Populate location checkboxes
locationCheckboxGroup.innerHTML = data.locations.map(location => `
<label class="checkbox-label">
<input
type="checkbox"
name="assigned_locations"
value="${location}"
class="checkbox-input location-checkbox"
>
<span class="checkbox-text">${location}</span>
</label>
`).join('');
// Add event listeners to location checkboxes
document.querySelectorAll('.location-checkbox').forEach(cb => {
cb.addEventListener('change', handleLocationSelection);
});
locationSummary.innerHTML = `
<i class="fas fa-info-circle"></i>
<span>${data.count} location${data.count > 1 ? 's' : ''} available</span>
`;
} else {
locationEmpty.style.display = 'block';
locationSummary.innerHTML = `
<i class="fas fa-exclamation-circle"></i>
<span>No locations available</span>
`;
}
updatePermissionsOverview();
} catch (error) {
console.error('Error loading locations:', error);
locationLoading.style.display = 'none';
locationEmpty.style.display = 'block';
locationSummary.innerHTML = `
<i class="fas fa-exclamation-triangle" style="color: #ef4444;"></i>
<span>Error loading locations</span>
`;
}
}
// Handle location selection changes
function handleLocationSelection() {
const selectedLocations = Array.from(document.querySelectorAll('.location-checkbox:checked'));
if (selectedLocations.length > 0) {
locationSummary.innerHTML = `
<i class="fas fa-check-circle" style="color: #10b981;"></i>
<span><strong>${selectedLocations.length}</strong> location${selectedLocations.length > 1 ? 's' : ''} selected</span>
`;
} else {
const totalLocations = document.querySelectorAll('.location-checkbox').length;
locationSummary.innerHTML = `
<i class="fas fa-info-circle"></i>
<span>${totalLocations} location${totalLocations > 1 ? 's' : ''} available</span>
`;
}
updatePermissionsOverview();
}
// Update permissions overview
function updatePermissionsOverview() {
const selectedProjects = Array.from(projectCheckboxes)
.filter(cb => cb.checked)
.map(cb => cb.dataset.projectName);
const selectedLocations = Array.from(document.querySelectorAll('.location-checkbox:checked'))
.map(cb => cb.value);
if (selectedProjects.length > 0 || selectedLocations.length > 0) {
permissionsOverview.style.display = 'block';
document.getElementById('overviewProjects').textContent =
selectedProjects.length > 0
? selectedProjects.join(', ')
: 'All projects';
document.getElementById('overviewLocations').textContent =
selectedLocations.length > 0
? selectedLocations.join(', ')
: 'All locations in selected projects';
} else {
permissionsOverview.style.display = 'none';
}
}
// Reset permission flow
function resetPermissionFlow() {
projectCheckboxes.forEach(cb => {
cb.checked = false;
cb.removeEventListener('change', handleProjectSelection);
});
locationStep.style.display = 'none';
permissionsOverview.style.display = 'none';
locationCheckboxGroup.innerHTML = '';
}
// Password strength indicator
passwordInput.addEventListener("input", function () {
const password = this.value;
const strength = calculatePasswordStrength(password);
passwordStrength.className = `password-strength ${strength.class}`;
passwordStrength.textContent = strength.text;
});
// Preview user functionality
previewBtn.addEventListener("click", function () {
const fullName = fullNameInput.value.trim();
const email = emailInput.value.trim();
const username = usernameInput.value.trim();
const role = roleSelect.value;
if (!fullName || !email || !username || !role) {
alert("Please fill in all required fields before previewing.");
return;
}
// Update summary
document.getElementById("summaryName").textContent = fullName;
document.getElementById("summaryEmail").textContent = email;
document.getElementById("summaryUsername").textContent = username;
// Get role display name
const roleDisplayNames = {
staff: "Staff User",
payroll: "Payroll Specialist",
project_manager: "Project Manager",
admin: "Administrator",
};
document.getElementById("summaryRole").textContent =
roleDisplayNames[role] || role;
creationSummary.style.display = "block";
setTimeout(() => creationSummary.classList.add("fade-in"), 100);
// Scroll to summary
creationSummary.scrollIntoView({ behavior: "smooth" });
});
// Form validation
form.addEventListener("submit", function (e) {
const password = passwordInput.value;
if (password.length < 6) {
e.preventDefault();
alert("Password must be at least 6 characters long.");
passwordInput.focus();
return;
}
if (!roleSelect.value) {
e.preventDefault();
alert("Please select a user role.");
roleSelect.focus();
return;
}
});
// Username validation
usernameInput.addEventListener("input", function () {
this.value = this.value.replace(/[^a-zA-Z0-9_]/g, "");
});
});
function calculatePasswordStrength(password) {
if (password.length === 0) {
return { class: "", text: "" };
}
let score = 0;
// Length
if (password.length >= 6) score += 1;
if (password.length >= 8) score += 1;
if (password.length >= 12) score += 1;
// Character variety
if (/[a-z]/.test(password)) score += 1;
if (/[A-Z]/.test(password)) score += 1;
if (/[0-9]/.test(password)) score += 1;
if (/[^a-zA-Z0-9]/.test(password)) score += 1;
if (score < 3) {
return { class: "weak", text: "Weak password" };
} else if (score < 5) {
return { class: "medium", text: "Medium strength" };
} else {
return { class: "strong", text: "Strong password" };
}
}
</script>
{% endblock %}