Updated PM role with view permissions

This commit is contained in:
2025-10-31 17:31:05 -04:00
parent a4c584468c
commit 6a30f08a5e
7 changed files with 1395 additions and 37 deletions
+330 -1
View File
@@ -51,7 +51,8 @@
<i class="fas fa-user"></i>
Username
</label>
<input type="text" id="username" value="{{ user.username }}" disabled>
<input type="text" id="username_display" value="{{ user.username }}" disabled>
<input type="hidden" name="username" value="{{ user.username }}">
<small class="form-help">Username cannot be changed</small>
</div>
@@ -81,6 +82,114 @@
</div>
</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>
<!-- Password Section -->
<div class="password-section">
<h3>
@@ -186,6 +295,7 @@
const passwordStrength = document.getElementById('passwordStrength');
const resetPasswordBtn = document.getElementById('resetPassword');
// Role change warning system
roleSelect.addEventListener('change', function() {
const newRole = this.value;
@@ -249,6 +359,225 @@
}
});
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');
// Show/hide Project Manager permissions based on role selection
if (roleSelect) {
roleSelect.addEventListener('change', function() {
if (this.value === 'project_manager') {
pmSection.style.display = 'block';
setupPermissionFlow();
} else {
pmSection.style.display = 'none';
resetPermissionFlow();
}
});
}
// Initialize permission flow if Project Manager role is already selected
if (roleSelect && roleSelect.value === 'project_manager') {
pmSection.style.display = 'block';
setupPermissionFlow();
}
// Setup cascading permission flow
function setupPermissionFlow() {
// Add event listeners to project checkboxes
projectCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', handleProjectSelection);
});
// Pre-select assigned projects if in edit mode
{% if assigned_project_ids %}
const assignedProjectIds = {{ assigned_project_ids | tojson }};
projectCheckboxes.forEach(cb => {
if (assignedProjectIds.includes(parseInt(cb.value))) {
cb.checked = true;
}
});
// Trigger initial load if projects are assigned
if (assignedProjectIds.length > 0) {
handleProjectSelection().then(() => {
// After locations are loaded, pre-select assigned locations
{% if assigned_location_names %}
setTimeout(() => {
const assignedLocations = {{ assigned_location_names | tojson }};
document.querySelectorAll('.location-checkbox').forEach(cb => {
if (assignedLocations.includes(cb.value)) {
cb.checked = true;
}
});
handleLocationSelection();
}, 500);
{% endif %}
});
}
{% endif %}
}
// Handle project selection changes
async 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';
await 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 = '';
}
function hideRoleWarning() {
roleWarning.classList.remove('fade-in');
setTimeout(() => roleWarning.style.display = 'none', 300);