updated control access
This commit is contained in:
@@ -4,11 +4,16 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Flask App{% endblock %}</title>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Font Awesome for icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<!-- Base CSS for common styles -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}">
|
||||
<!-- Legacy CSS for backward compatibility (temporarily) -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
<!-- Page-specific CSS -->
|
||||
{% block extra_css %}{% endblock %}
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body class="light-mode">
|
||||
@@ -56,5 +61,8 @@
|
||||
{% if request.endpoint != 'main.fg_quality' %}
|
||||
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||
{% endif %}
|
||||
|
||||
<!-- Bootstrap JavaScript -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
440
py_app/app/templates/fg_quality.html
Normal file
440
py_app/app/templates/fg_quality.html
Normal file
@@ -0,0 +1,440 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}FG Quality Module{% endblock %}
|
||||
{% block content %}
|
||||
<div class="scan-container">
|
||||
<!-- Reports Card -->
|
||||
<div class="card report-form-card">
|
||||
<h3>FG Quality Reports</h3>
|
||||
|
||||
<!-- Reports List with Label-Button Layout -->
|
||||
<div class="reports-grid">
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Daily report will export all FG orders scanned at quality scanning points</label>
|
||||
<button class="btn report-btn" data-report="1">Daily Complete FG Orders Report</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Select day for daily FG report will export all orders scanned at quality scanning points</label>
|
||||
<button class="btn report-btn" id="select-day-report">Select Day Daily FG Report</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Select date range for custom FG report - from start date 00:00 to end date 23:59</label>
|
||||
<button class="btn report-btn" id="date-range-report">Date Range FG Report</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">5-day report will export all FG orders scanned at quality scanning points</label>
|
||||
<button class="btn report-btn" data-report="2">5-Day Complete FG Orders Report</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Report on FG items with quality issues for the current day</label>
|
||||
<button class="btn report-btn" data-report="3">Report on FG Items with Defects for the Current Day</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Select specific day for FG quality defects report - items with quality issues</label>
|
||||
<button class="btn report-btn" id="select-day-defects-report">Select Day FG Quality Defects Report</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Select date range for FG quality defects report - items with quality issues between two dates</label>
|
||||
<button class="btn report-btn" id="date-range-defects-report">Date Range FG Quality Defects Report</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Report on FG items with quality issues for the last 5 days</label>
|
||||
<button class="btn report-btn" data-report="4">Report on FG Items with Defects for the Last 5 Days</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Report all FG entries from the database</label>
|
||||
<button class="btn report-btn" data-report="5">Report FG Database</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Separator -->
|
||||
<div class="report-separator"></div>
|
||||
|
||||
<!-- Export Section -->
|
||||
<div class="export-section">
|
||||
<div class="form-centered last-buttons">
|
||||
<label class="export-description">Export current report as:</label>
|
||||
<div class="button-row">
|
||||
<button class="btn export-btn" id="export-csv">Export CSV</button>
|
||||
<!-- <button class="btn export-btn" id="export-excel">Export excell</button> -->
|
||||
{% if session.get('role') == 'superadmin' %}
|
||||
<button class="btn export-btn test-db-btn" id="test-database">Test FG Database</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Display Card -->
|
||||
<div class="card report-table-card">
|
||||
<h3 id="report-title">No data to display, please select a report.</h3>
|
||||
<div class="report-table-container">
|
||||
<table class="scan-table" id="report-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<!-- Table headers will be dynamically populated -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Table data will be dynamically populated -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Calendar Popup Modal -->
|
||||
<div id="calendar-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4>Select Date for Daily FG Report</h4>
|
||||
<span class="close-modal">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="calendar-container">
|
||||
<div class="calendar-header">
|
||||
<button id="prev-month" class="calendar-nav"><</button>
|
||||
<h3 id="calendar-month-year"></h3>
|
||||
<button id="next-month" class="calendar-nav">></button>
|
||||
</div>
|
||||
<div class="calendar-grid">
|
||||
<div class="calendar-weekdays">
|
||||
<div>Sun</div>
|
||||
<div>Mon</div>
|
||||
<div>Tue</div>
|
||||
<div>Wed</div>
|
||||
<div>Thu</div>
|
||||
<div>Fri</div>
|
||||
<div>Sat</div>
|
||||
</div>
|
||||
<div class="calendar-days" id="calendar-days">
|
||||
<!-- Days will be populated by JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" id="cancel-date">Cancel</button>
|
||||
<button class="btn btn-primary" id="confirm-date" disabled>Generate Report</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date Range Popup Modal -->
|
||||
<div id="date-range-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4>Select Date Range for FG Report</h4>
|
||||
<span class="close-modal" id="close-date-range">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="date-range-container">
|
||||
<div class="date-input-group">
|
||||
<label for="start-date">Start Date:</label>
|
||||
<input type="date" id="start-date" class="date-input" />
|
||||
<small class="date-help">Report will include data from 00:00:00</small>
|
||||
</div>
|
||||
<div class="date-input-group">
|
||||
<label for="end-date">End Date:</label>
|
||||
<input type="date" id="end-date" class="date-input" />
|
||||
<small class="date-help">Report will include data until 23:59:59</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" id="cancel-date-range">Cancel</button>
|
||||
<button class="btn btn-primary" id="confirm-date-range" disabled>Generate Report</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<script>
|
||||
// Debug check - this will run immediately when the page loads
|
||||
console.log('FG Quality page loaded - checking script conflicts');
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('FG Quality DOM loaded');
|
||||
// Add a visible indicator that our script loaded
|
||||
setTimeout(() => {
|
||||
const titleElement = document.getElementById('report-title');
|
||||
if (titleElement && titleElement.textContent === 'No data to display, please select a report.') {
|
||||
titleElement.textContent = '🟢 FG Quality Module Ready - Select a report';
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='fg_quality.js') }}"></script>
|
||||
<script>
|
||||
// Theme functionality for FG Quality page
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const body = document.body;
|
||||
const themeToggleButton = document.getElementById('theme-toggle');
|
||||
|
||||
// Helper function to update the theme toggle button text
|
||||
function updateThemeToggleButtonText() {
|
||||
if (body.classList.contains('dark-mode')) {
|
||||
themeToggleButton.textContent = 'Change to Light Mode';
|
||||
} else {
|
||||
themeToggleButton.textContent = 'Change to Dark Mode';
|
||||
}
|
||||
}
|
||||
|
||||
// Check and apply the saved theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme) {
|
||||
body.classList.toggle('dark-mode', savedTheme === 'dark');
|
||||
}
|
||||
|
||||
// Update the button text based on the current theme
|
||||
if (themeToggleButton) {
|
||||
updateThemeToggleButtonText();
|
||||
|
||||
// Toggle the theme on button click
|
||||
themeToggleButton.addEventListener('click', () => {
|
||||
const isDarkMode = body.classList.toggle('dark-mode');
|
||||
localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
|
||||
updateThemeToggleButtonText(); // Update the button text after toggling
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Override script - runs after both script.js and fg_quality.js
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
// Wait a bit to ensure all scripts have loaded
|
||||
setTimeout(() => {
|
||||
console.log('🔧 FG Quality Override Script - Fixing event handlers');
|
||||
|
||||
// Override the problematic calendar functionality
|
||||
const selectDayReport = document.getElementById('select-day-report');
|
||||
const selectDayDefectsReport = document.getElementById('select-day-defects-report');
|
||||
const dateRangeReport = document.getElementById('date-range-report');
|
||||
const dateRangeDefectsReport = document.getElementById('date-range-defects-report');
|
||||
|
||||
let currentFGReportType = null;
|
||||
let selectedFGDate = null;
|
||||
|
||||
// Clear and re-add event listeners with FG-specific functionality
|
||||
if (selectDayReport) {
|
||||
// Remove all existing listeners by cloning
|
||||
const newSelectDayReport = selectDayReport.cloneNode(true);
|
||||
selectDayReport.parentNode.replaceChild(newSelectDayReport, selectDayReport);
|
||||
|
||||
newSelectDayReport.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('🎯 FG Select Day Report clicked');
|
||||
currentFGReportType = '6';
|
||||
showFGCalendarModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (dateRangeReport) {
|
||||
const newDateRangeReport = dateRangeReport.cloneNode(true);
|
||||
dateRangeReport.parentNode.replaceChild(newDateRangeReport, dateRangeReport);
|
||||
|
||||
newDateRangeReport.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('🎯 FG Date Range Report clicked');
|
||||
currentFGReportType = '7';
|
||||
showFGDateRangeModal();
|
||||
});
|
||||
}
|
||||
|
||||
// FG-specific calendar modal functionality
|
||||
function showFGCalendarModal() {
|
||||
const calendarModal = document.getElementById('calendar-modal');
|
||||
if (calendarModal) {
|
||||
calendarModal.style.display = 'block';
|
||||
generateFGCalendar();
|
||||
}
|
||||
}
|
||||
|
||||
function showFGDateRangeModal() {
|
||||
const dateRangeModal = document.getElementById('date-range-modal');
|
||||
if (dateRangeModal) {
|
||||
dateRangeModal.style.display = 'block';
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('start-date').value = today;
|
||||
document.getElementById('end-date').value = today;
|
||||
}
|
||||
}
|
||||
|
||||
function generateFGCalendar() {
|
||||
const calendarDays = document.getElementById('calendar-days');
|
||||
const monthYear = document.getElementById('calendar-month-year');
|
||||
|
||||
if (!calendarDays || !monthYear) return;
|
||||
|
||||
const currentDate = new Date();
|
||||
const year = currentDate.getFullYear();
|
||||
const month = currentDate.getMonth();
|
||||
|
||||
monthYear.textContent = `${currentDate.toLocaleString('default', { month: 'long' })} ${year}`;
|
||||
|
||||
// Clear previous days
|
||||
calendarDays.innerHTML = '';
|
||||
|
||||
// Get first day of month and number of days
|
||||
const firstDay = new Date(year, month, 1).getDay();
|
||||
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||
|
||||
// Add empty cells for previous month
|
||||
for (let i = 0; i < firstDay; i++) {
|
||||
const emptyDay = document.createElement('div');
|
||||
emptyDay.className = 'calendar-day empty';
|
||||
calendarDays.appendChild(emptyDay);
|
||||
}
|
||||
|
||||
// Add days of current month
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const dayElement = document.createElement('div');
|
||||
dayElement.className = 'calendar-day';
|
||||
dayElement.textContent = day;
|
||||
|
||||
// Highlight October 15 as it has FG data
|
||||
if (month === 9 && day === 15) { // October is month 9
|
||||
dayElement.style.backgroundColor = '#e3f2fd';
|
||||
dayElement.style.border = '2px solid #2196f3';
|
||||
dayElement.title = 'FG data available';
|
||||
}
|
||||
|
||||
dayElement.addEventListener('click', () => {
|
||||
// Remove previous selection
|
||||
document.querySelectorAll('.calendar-day.selected').forEach(el => {
|
||||
el.classList.remove('selected');
|
||||
});
|
||||
|
||||
// Add selection to clicked day
|
||||
dayElement.classList.add('selected');
|
||||
|
||||
// Set selected date
|
||||
selectedFGDate = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||
console.log('🗓️ FG Calendar date selected:', selectedFGDate);
|
||||
|
||||
// Enable confirm button
|
||||
const confirmButton = document.getElementById('confirm-date');
|
||||
if (confirmButton) {
|
||||
confirmButton.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
calendarDays.appendChild(dayElement);
|
||||
}
|
||||
}
|
||||
|
||||
// Override confirm button
|
||||
const confirmDateBtn = document.getElementById('confirm-date');
|
||||
if (confirmDateBtn) {
|
||||
const newConfirmBtn = confirmDateBtn.cloneNode(true);
|
||||
confirmDateBtn.parentNode.replaceChild(newConfirmBtn, confirmDateBtn);
|
||||
|
||||
newConfirmBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('✅ FG Calendar confirm clicked:', selectedFGDate, 'Report type:', currentFGReportType);
|
||||
|
||||
if (selectedFGDate && currentFGReportType) {
|
||||
const url = `/generate_fg_report?report=${currentFGReportType}&date=${selectedFGDate}`;
|
||||
console.log('🚀 Calling FG endpoint:', url);
|
||||
|
||||
document.getElementById('report-title').textContent = 'Loading FG data...';
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('📊 FG Data received:', data);
|
||||
|
||||
const table = document.getElementById('report-table');
|
||||
const thead = table.querySelector('thead tr');
|
||||
const tbody = table.querySelector('tbody');
|
||||
|
||||
// Clear existing content
|
||||
thead.innerHTML = '';
|
||||
tbody.innerHTML = '';
|
||||
|
||||
// Add headers
|
||||
if (data.headers) {
|
||||
data.headers.forEach(header => {
|
||||
const th = document.createElement('th');
|
||||
th.textContent = header;
|
||||
thead.appendChild(th);
|
||||
});
|
||||
}
|
||||
|
||||
// Add rows
|
||||
if (data.rows && data.rows.length > 0) {
|
||||
data.rows.forEach(row => {
|
||||
const tr = document.createElement('tr');
|
||||
row.forEach(cell => {
|
||||
const td = document.createElement('td');
|
||||
td.textContent = cell || '';
|
||||
tr.appendChild(td);
|
||||
});
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
document.getElementById('report-title').textContent = `FG Daily Report for ${selectedFGDate} (${data.rows.length} records)`;
|
||||
} else {
|
||||
const tr = document.createElement('tr');
|
||||
const td = document.createElement('td');
|
||||
td.colSpan = data.headers ? data.headers.length : 1;
|
||||
td.textContent = data.message || 'No FG data found';
|
||||
td.style.textAlign = 'center';
|
||||
tr.appendChild(td);
|
||||
tbody.appendChild(tr);
|
||||
document.getElementById('report-title').textContent = `FG Daily Report for ${selectedFGDate} - No data`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ Error fetching FG data:', error);
|
||||
document.getElementById('report-title').textContent = 'Error loading FG data';
|
||||
});
|
||||
|
||||
// Hide modal
|
||||
document.getElementById('calendar-modal').style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Override date range confirm button
|
||||
const confirmDateRangeBtn = document.getElementById('confirm-date-range');
|
||||
if (confirmDateRangeBtn) {
|
||||
const newConfirmRangeBtn = confirmDateRangeBtn.cloneNode(true);
|
||||
confirmDateRangeBtn.parentNode.replaceChild(newConfirmRangeBtn, confirmDateRangeBtn);
|
||||
|
||||
newConfirmRangeBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const startDate = document.getElementById('start-date').value;
|
||||
const endDate = document.getElementById('end-date').value;
|
||||
|
||||
console.log('📅 FG Date range confirm:', startDate, 'to', endDate);
|
||||
|
||||
if (startDate && endDate && currentFGReportType) {
|
||||
const url = `/generate_fg_report?report=${currentFGReportType}&start_date=${startDate}&end_date=${endDate}`;
|
||||
console.log('🚀 Calling FG range endpoint:', url);
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('📊 FG Range data received:', data);
|
||||
// Handle response similar to above
|
||||
document.getElementById('report-title').textContent = `FG Date Range Report (${startDate} to ${endDate})`;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ Error fetching FG range data:', error);
|
||||
});
|
||||
|
||||
// Hide modal
|
||||
document.getElementById('date-range-modal').style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✅ FG Quality Override Script - Event handlers fixed');
|
||||
}, 500); // Wait 500ms to ensure all other scripts are loaded
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
40
py_app/app/templates/main_page_reports.html
Normal file
40
py_app/app/templates/main_page_reports.html
Normal file
@@ -0,0 +1,40 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Reports Module{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="reports-container">
|
||||
<h1>Reports Module</h1>
|
||||
<p>Access different quality and production reporting modules for data analysis and verification.</p>
|
||||
|
||||
<!-- Row of evenly distributed cards -->
|
||||
<div class="dashboard-container">
|
||||
<!-- Card 1: Quality Reports -->
|
||||
<div class="dashboard-card">
|
||||
<h3>Quality Reports</h3>
|
||||
<p>Access quality scanning reports and analysis for production process verification.</p>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<a href="{{ url_for('main.quality') }}" class="btn">Launch Quality Reports</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 2: FG Quality Reports -->
|
||||
<div class="dashboard-card">
|
||||
<h3>FG Quality Reports</h3>
|
||||
<p>Finished Goods quality reports and analysis for final product verification.</p>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<a href="{{ url_for('main.fg_quality') }}" class="btn">Launch FG Quality Reports</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 3: Additional Reports (Placeholder for future expansion) -->
|
||||
<div class="dashboard-card">
|
||||
<h3>Additional Reports</h3>
|
||||
<p>Access additional reporting modules and data analysis tools.</p>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<button class="btn" disabled style="opacity: 0.5;">Coming Soon</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,454 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Role Permissions Management{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<style>
|
||||
.permissions-container {
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
.permissions-table-container {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
|
||||
overflow: hidden;
|
||||
margin: 0 auto 30px auto;
|
||||
border: 2px solid #dee2e6;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.permissions-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.permissions-table thead {
|
||||
background: linear-gradient(135deg, #007bff, #0056b3);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.permissions-table th {
|
||||
padding: 15px 12px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
border-bottom: 2px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
.permissions-table th:nth-child(1) { width: 15%; }
|
||||
.permissions-table th:nth-child(2) { width: 20%; }
|
||||
.permissions-table th:nth-child(3) { width: 25%; }
|
||||
.permissions-table th:nth-child(4) { width: 40%; }
|
||||
|
||||
.permission-row {
|
||||
border-bottom: 2px solid #dee2e6 !important;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.permission-row:hover {
|
||||
background: linear-gradient(135deg, #e3f2fd, #f0f8ff) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 4px 12px rgba(0,123,255,0.15) !important;
|
||||
}
|
||||
|
||||
.role-cell, .module-cell, .page-cell, .functions-cell {
|
||||
padding: 15px 12px !important;
|
||||
vertical-align: top !important;
|
||||
border-right: 1px solid #f1f3f4 !important;
|
||||
}
|
||||
|
||||
.role-cell {
|
||||
border-left: 4px solid #007bff !important;
|
||||
}
|
||||
|
||||
.module-cell {
|
||||
border-left: 2px solid #28a745 !important;
|
||||
}
|
||||
|
||||
.page-cell {
|
||||
border-left: 2px solid #ffc107 !important;
|
||||
}
|
||||
|
||||
.functions-cell {
|
||||
border-left: 2px solid #dc3545 !important;
|
||||
}
|
||||
|
||||
.role-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: #e3f2fd;
|
||||
padding: 8px 12px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.functions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.function-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.function-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
background: #ccc;
|
||||
border-radius: 20px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.toggle-slider::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked + .toggle-slider {
|
||||
background: #007bff;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked + .toggle-slider::before {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.function-text {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.role-separator, .module-separator {
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.separator-line {
|
||||
padding: 12px 20px;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
background: linear-gradient(135deg, #e9ecef, #f8f9fa);
|
||||
}
|
||||
|
||||
.module-badge {
|
||||
padding: 8px 15px;
|
||||
background: linear-gradient(135deg, #28a745, #20c997);
|
||||
color: white;
|
||||
border-radius: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.action-buttons-container {
|
||||
text-align: center;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #0056b3;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,123,255,0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #545b62;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(108,117,125,0.3);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="permissions-container">
|
||||
<div style="text-align: center; margin-bottom: 40px;">
|
||||
<h1 style="color: #2c3e50; margin-bottom: 15px; font-weight: 700; font-size: 32px;">
|
||||
🔐 Role Permissions Management
|
||||
</h1>
|
||||
<p style="color: #6c757d; font-size: 16px;">
|
||||
Configure granular access permissions for each role in the system
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 4-Column Permissions Table -->
|
||||
<div class="permissions-table-container">
|
||||
<table class="permissions-table" id="permissionsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>👤 Role Name</th>
|
||||
<th>🏢 Module Name</th>
|
||||
<th>📄 Page Name</th>
|
||||
<th>⚙️ Functions & Permissions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% set current_role = '' %}
|
||||
{% set current_module = '' %}
|
||||
{% for role_name, role_data in roles.items() %}
|
||||
{% for page_key, page_data in pages.items() %}
|
||||
{% for section_key, section_data in page_data.sections.items() %}
|
||||
|
||||
<!-- Role separator row -->
|
||||
{% if current_role != role_name %}
|
||||
{% set current_role = role_name %}
|
||||
<tr class="role-separator">
|
||||
<td colspan="4">
|
||||
<div class="separator-line">
|
||||
<span>{{ role_data.display_name }} (Level {{ role_data.level }})</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
<!-- Module separator -->
|
||||
{% if current_module != page_key %}
|
||||
{% set current_module = page_key %}
|
||||
<tr class="module-separator">
|
||||
<td></td>
|
||||
<td colspan="3">
|
||||
<div style="padding: 8px 15px;">
|
||||
<span class="module-badge">{{ page_data.name }}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
<tr class="permission-row" data-role="{{ role_name }}" data-module="{{ page_key }}">
|
||||
<td class="role-cell">
|
||||
<div class="role-badge">
|
||||
<span>👤</span>
|
||||
<span>{{ role_data.display_name }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="module-cell">
|
||||
<span>{{ page_data.name }}</span>
|
||||
</td>
|
||||
<td class="page-cell">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span>📋</span>
|
||||
<span>{{ section_data.name }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="functions-cell">
|
||||
<div class="functions-grid">
|
||||
{% for action in section_data.actions %}
|
||||
{% set permission_key = page_key + '.' + section_key + '.' + action %}
|
||||
<div class="function-item" data-permission="{{ permission_key }}" data-role="{{ role_name }}">
|
||||
<label class="function-toggle">
|
||||
<input type="checkbox"
|
||||
data-role="{{ role_name }}"
|
||||
data-page="{{ page_key }}"
|
||||
data-section="{{ section_key }}"
|
||||
data-action="{{ action }}"
|
||||
onchange="togglePermission('{{ role_name }}', '{{ page_key }}', '{{ section_key }}', '{{ action }}', this)">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<span class="function-text">{{ action_names[action] }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% set current_module = '' %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons-container">
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-secondary" onclick="resetAllToDefaults()">
|
||||
<span>🔄</span>
|
||||
Reset All to Defaults
|
||||
</button>
|
||||
<button class="btn btn-primary" onclick="saveAllPermissions()">
|
||||
<span>💾</span>
|
||||
Save All Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Initialize data from backend
|
||||
let permissions = {{ permissions_json|safe }};
|
||||
let rolePermissions = {{ role_permissions_json|safe }};
|
||||
|
||||
// Toggle permission function
|
||||
function togglePermission(roleName, pageKey, sectionKey, action, checkbox) {
|
||||
const isChecked = checkbox.checked;
|
||||
const permissionKey = `${pageKey}.${sectionKey}.${action}`;
|
||||
|
||||
// Update visual state of the function item
|
||||
const functionItem = checkbox.closest('.function-item');
|
||||
if (isChecked) {
|
||||
functionItem.classList.remove('disabled');
|
||||
} else {
|
||||
functionItem.classList.add('disabled');
|
||||
}
|
||||
|
||||
// Update data structure (flat array format)
|
||||
if (!rolePermissions[roleName]) {
|
||||
rolePermissions[roleName] = [];
|
||||
}
|
||||
|
||||
if (isChecked && !rolePermissions[roleName].includes(permissionKey)) {
|
||||
rolePermissions[roleName].push(permissionKey);
|
||||
} else if (!isChecked) {
|
||||
const index = rolePermissions[roleName].indexOf(permissionKey);
|
||||
if (index > -1) {
|
||||
rolePermissions[roleName].splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save all permissions
|
||||
function saveAllPermissions() {
|
||||
// Convert flat permission arrays to nested structure for backend
|
||||
const structuredPermissions = {};
|
||||
|
||||
for (const [roleName, permissions] of Object.entries(rolePermissions)) {
|
||||
structuredPermissions[roleName] = {};
|
||||
|
||||
permissions.forEach(permissionKey => {
|
||||
const [pageKey, sectionKey, action] = permissionKey.split('.');
|
||||
|
||||
if (!structuredPermissions[roleName][pageKey]) {
|
||||
structuredPermissions[roleName][pageKey] = {};
|
||||
}
|
||||
if (!structuredPermissions[roleName][pageKey][sectionKey]) {
|
||||
structuredPermissions[roleName][pageKey][sectionKey] = [];
|
||||
}
|
||||
|
||||
structuredPermissions[roleName][pageKey][sectionKey].push(action);
|
||||
});
|
||||
}
|
||||
|
||||
fetch('/settings/save_all_role_permissions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
permissions: structuredPermissions
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('All permissions saved successfully!');
|
||||
} else {
|
||||
alert('Error saving permissions: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Error saving permissions: ' + error);
|
||||
});
|
||||
}
|
||||
|
||||
// Reset all permissions to defaults
|
||||
function resetAllToDefaults() {
|
||||
if (confirm('Are you sure you want to reset ALL role permissions to defaults? This will overwrite all current settings.')) {
|
||||
fetch('/settings/reset_all_role_permissions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error resetting permissions: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Error resetting permissions: ' + error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize checkbox states when page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Set initial states based on data
|
||||
document.querySelectorAll('.function-item').forEach(item => {
|
||||
const roleName = item.dataset.role;
|
||||
const permissionKey = item.dataset.permission;
|
||||
const checkbox = item.querySelector('input[type="checkbox"]');
|
||||
|
||||
// Check if this role has this permission
|
||||
const hasPermission = rolePermissions[roleName] && rolePermissions[roleName].includes(permissionKey);
|
||||
|
||||
if (hasPermission) {
|
||||
checkbox.checked = true;
|
||||
item.classList.remove('disabled');
|
||||
} else {
|
||||
checkbox.checked = false;
|
||||
item.classList.add('disabled');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -44,9 +44,7 @@
|
||||
<a href="{{ url_for('main.user_management_simple') }}" class="btn" style="background-color: #2196f3; color: white; margin-right: 10px;">
|
||||
🎯 Manage Users (Simplified)
|
||||
</a>
|
||||
<a href="{{ url_for('main.role_permissions') }}" class="btn" style="background-color: #6c757d; color: white;">
|
||||
⚙️ Advanced Role Permissions
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<small style="display: block; margin-top: 10px; color: #666;">
|
||||
Recommended: Use the simplified user management for easier administration
|
||||
|
||||
890
py_app/app/templates/user_management_simple.html
Normal file
890
py_app/app/templates/user_management_simple.html
Normal file
@@ -0,0 +1,890 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}User Management - Simplified{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
/* Theme-aware card styles */
|
||||
.user-management-page .card {
|
||||
text-align: left !important;
|
||||
flex: none !important;
|
||||
max-width: 100% !important;
|
||||
padding: 0 !important;
|
||||
border-radius: 5px !important;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
/* Light mode card styles */
|
||||
body.light-mode .user-management-page .card {
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
border: 1px solid #ddd !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page .card-body {
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
padding: 1.25rem !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page .card-header {
|
||||
background-color: #f8f9fa !important;
|
||||
color: #333 !important;
|
||||
border-bottom: 1px solid #ddd !important;
|
||||
padding: 0.75rem 1.25rem !important;
|
||||
border-top-left-radius: 4px !important;
|
||||
border-top-right-radius: 4px !important;
|
||||
}
|
||||
|
||||
/* Dark mode card styles */
|
||||
body.dark-mode .user-management-page .card {
|
||||
background: #1e1e1e !important;
|
||||
color: #fff !important;
|
||||
border: 1px solid #444 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .card-body {
|
||||
background: #1e1e1e !important;
|
||||
color: #fff !important;
|
||||
padding: 1.25rem !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .card-header {
|
||||
background-color: #2a2a2a !important;
|
||||
color: #ccc !important;
|
||||
border-bottom: 1px solid #444 !important;
|
||||
padding: 0.75rem 1.25rem !important;
|
||||
border-top-left-radius: 4px !important;
|
||||
border-top-right-radius: 4px !important;
|
||||
}
|
||||
|
||||
/* Theme-aware header text */
|
||||
.user-management-page .card-header h5 {
|
||||
margin: 0 !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page .card-header h5 {
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .card-header h5 {
|
||||
color: #ccc !important;
|
||||
}
|
||||
|
||||
/* Theme-aware role badges */
|
||||
.user-role-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.role-superadmin { background-color: #dc3545; color: white; }
|
||||
.role-admin { background-color: #fd7e14; color: white; }
|
||||
.role-manager { background-color: #007bff; color: white; }
|
||||
.role-worker { background-color: #28a745; color: white; }
|
||||
|
||||
/* Dark mode badge adjustments */
|
||||
body.dark-mode .role-manager { background-color: #0056b3; }
|
||||
body.dark-mode .role-worker { background-color: #1e7e34; }
|
||||
|
||||
/* Theme-aware module badges */
|
||||
.module-badges .badge {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
body.light-mode .module-badges .badge {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
body.dark-mode .module-badges .badge {
|
||||
background-color: #0056b3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.module-checkboxes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.module-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
/* Theme-aware info panels */
|
||||
.access-level-info {
|
||||
border-left: 4px solid #007bff;
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
body.light-mode .access-level-info {
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
body.dark-mode .access-level-info {
|
||||
background-color: #2a2a2a;
|
||||
color: #ccc;
|
||||
border-left-color: #0056b3;
|
||||
}
|
||||
|
||||
.table-warning {
|
||||
background-color: #fff3cd !important;
|
||||
}
|
||||
|
||||
body.dark-mode .table-warning {
|
||||
background-color: #664d03 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* Colored card headers - respect theme but use accent colors */
|
||||
.card-header.bg-primary {
|
||||
background-color: #007bff !important;
|
||||
color: white !important;
|
||||
border-color: #007bff !important;
|
||||
}
|
||||
|
||||
.card-header.bg-primary h5 {
|
||||
color: white !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.card-header.bg-success {
|
||||
background-color: #28a745 !important;
|
||||
color: white !important;
|
||||
border-color: #28a745 !important;
|
||||
}
|
||||
|
||||
.card-header.bg-success h5 {
|
||||
color: white !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* Dark mode adjustments for colored headers */
|
||||
body.dark-mode .card-header.bg-primary {
|
||||
background-color: #0056b3 !important;
|
||||
border-color: #0056b3 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .card-header.bg-success {
|
||||
background-color: #1e7e34 !important;
|
||||
border-color: #1e7e34 !important;
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#quickModuleEdit {
|
||||
border: 2px solid #198754;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
background-color: #f8fff8;
|
||||
}
|
||||
|
||||
/* Theme-aware table styling */
|
||||
.thead-dark {
|
||||
background-color: #343a40 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
body.dark-mode .thead-dark {
|
||||
background-color: #1a1a1a !important;
|
||||
}
|
||||
|
||||
.user-management-page .table {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page .table {
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .table {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.user-management-page .table th,
|
||||
.user-management-page .table td {
|
||||
border-top: 1px solid #dee2e6 !important;
|
||||
padding: 0.75rem !important;
|
||||
vertical-align: top !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .table th,
|
||||
body.dark-mode .user-management-page .table td {
|
||||
border-top: 1px solid #444 !important;
|
||||
}
|
||||
|
||||
.user-management-page .table-striped tbody tr:nth-of-type(odd) {
|
||||
background-color: rgba(0, 0, 0, 0.05) !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .table-striped tbody tr:nth-of-type(odd) {
|
||||
background-color: rgba(255, 255, 255, 0.05) !important;
|
||||
}
|
||||
|
||||
.user-management-page .table-hover tbody tr:hover {
|
||||
background-color: rgba(0, 0, 0, 0.075) !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .table-hover tbody tr:hover {
|
||||
background-color: rgba(255, 255, 255, 0.075) !important;
|
||||
}
|
||||
|
||||
/* Theme-aware form elements */
|
||||
.user-management-page .form-control {
|
||||
border: 1px solid #ced4da !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page .form-control {
|
||||
background-color: #fff !important;
|
||||
color: #000 !important;
|
||||
border-color: #ced4da !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .form-control {
|
||||
background-color: #2a2a2a !important;
|
||||
color: #fff !important;
|
||||
border-color: #444 !important;
|
||||
}
|
||||
|
||||
.user-management-page .form-control:focus {
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25) !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page .form-control:focus {
|
||||
border-color: #80bdff !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .form-control:focus {
|
||||
border-color: #0056b3 !important;
|
||||
}
|
||||
|
||||
.user-management-page label {
|
||||
font-weight: 500 !important;
|
||||
margin-bottom: 0.5rem !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page label {
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page label {
|
||||
color: #ccc !important;
|
||||
}
|
||||
|
||||
/* Theme-aware buttons */
|
||||
.user-management-page .btn-primary {
|
||||
background-color: #007bff !important;
|
||||
border-color: #007bff !important;
|
||||
}
|
||||
|
||||
.user-management-page .btn-primary:hover {
|
||||
background-color: #0056b3 !important;
|
||||
border-color: #0056b3 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .btn-primary {
|
||||
background-color: #0056b3 !important;
|
||||
border-color: #0056b3 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .btn-primary:hover {
|
||||
background-color: #004085 !important;
|
||||
border-color: #004085 !important;
|
||||
}
|
||||
|
||||
/* Additional theme-aware elements */
|
||||
#quickModuleEdit {
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
border: 2px solid #28a745;
|
||||
}
|
||||
|
||||
body.light-mode #quickModuleEdit {
|
||||
background-color: #f8fff8;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
body.dark-mode #quickModuleEdit {
|
||||
background-color: #1a2f1a;
|
||||
color: #ccc;
|
||||
border-color: #1e7e34;
|
||||
}
|
||||
|
||||
/* Theme-aware text colors */
|
||||
body.light-mode .user-management-page h2,
|
||||
body.light-mode .user-management-page p {
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page h2,
|
||||
body.dark-mode .user-management-page p {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page .text-muted {
|
||||
color: #6c757d !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .text-muted {
|
||||
color: #adb5bd !important;
|
||||
}
|
||||
|
||||
/* Theme-aware select dropdown */
|
||||
body.light-mode .user-management-page select.form-control {
|
||||
background-color: #fff !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page select.form-control {
|
||||
background-color: #2a2a2a !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4 user-management-page">
|
||||
<h2>Simplified User Management</h2>
|
||||
<p class="text-muted">Manage users with the new 4-tier permission system: Superadmin → Admin → Manager → Worker</p>
|
||||
|
||||
<div class="row">
|
||||
<!-- Create User Card -->
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0">👤 Create New User</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="addUserForm" method="POST" action="/create_user_simple">
|
||||
<div class="form-group">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="role">Role:</label>
|
||||
<select id="role" name="role" class="form-control" required onchange="updateModuleSelection()">
|
||||
<option value="">Select a role...</option>
|
||||
<option value="superadmin">Superadmin - Full system access</option>
|
||||
<option value="admin">Admin - Full app access (except role permissions)</option>
|
||||
<option value="manager">Manager - Module-based access (can have multiple modules)</option>
|
||||
<option value="worker">Worker - Limited module access (can have multiple modules)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="moduleSelection" style="display: none;">
|
||||
<label>Modules:</label>
|
||||
<div class="module-checkboxes">
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="module_quality" name="modules" value="quality">
|
||||
<label for="module_quality">Quality Control</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="module_warehouse" name="modules" value="warehouse">
|
||||
<label for="module_warehouse">Warehouse Management</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="module_labels" name="modules" value="labels">
|
||||
<label for="module_labels">Label Management</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="accessLevelInfo" class="access-level-info" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-block">Create User</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit User Rights Card -->
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0">⚙️ Edit User Rights</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="userRightsPanel">
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-user-edit fa-3x mb-3"></i>
|
||||
<p>Select a user from the table below to edit their rights and module access.</p>
|
||||
</div>
|
||||
|
||||
<!-- Quick Module Management for Selected User -->
|
||||
<div id="quickModuleEdit" style="display: none;">
|
||||
<h6>Quick Module Management</h6>
|
||||
<div class="mb-3">
|
||||
<strong>User:</strong> <span id="selectedUserName"></span>
|
||||
<br><small class="text-muted">Role: <span id="selectedUserRole"></span></small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Current Modules:</label>
|
||||
<div id="currentModules" class="module-checkboxes">
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="quick_module_quality" name="quick_modules" value="quality">
|
||||
<label for="quick_module_quality">Quality Control</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="quick_module_warehouse" name="quick_modules" value="warehouse">
|
||||
<label for="quick_module_warehouse">Warehouse Management</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="quick_module_labels" name="quick_modules" value="labels">
|
||||
<label for="quick_module_labels">Label Management</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm w-100">
|
||||
<button type="button" class="btn btn-success" onclick="updateUserModules()">
|
||||
💾 Save Module Changes
|
||||
</button>
|
||||
<button type="button" class="btn btn-info" onclick="showFullEditModal()">
|
||||
✏️ Full Edit
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" onclick="deleteSelectedUser()">
|
||||
🗑️ Delete User
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Users Table -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>👥 Current Users</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if users %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Role</th>
|
||||
<th>Modules</th>
|
||||
<th>Access Level</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr class="user-row" data-user-id="{{ user.id }}" data-username="{{ user.username }}" data-role="{{ user.role }}" data-modules="{{ (user.get_modules() or []) | tojson }}">
|
||||
<td>
|
||||
<strong>{{ user.username }}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span class="user-role-badge role-{{ user.role }}">
|
||||
{{ user.role.title() }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="module-badges">
|
||||
{% if user.role in ['superadmin', 'admin'] %}
|
||||
<span class="badge bg-secondary">All Modules</span>
|
||||
{% elif user.modules %}
|
||||
{% for module in user.get_modules() %}
|
||||
<span class="badge bg-info">{{ module.title() }}</span>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<span class="text-muted">No modules assigned</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<small class="text-muted">
|
||||
{% if user.role == 'superadmin' %}
|
||||
Full system access
|
||||
{% elif user.role == 'admin' %}
|
||||
Full app access
|
||||
{% elif user.role == 'manager' %}
|
||||
Full module access + reports
|
||||
{% elif user.role == 'worker' %}
|
||||
Basic operations only (no reports) - Can have multiple modules
|
||||
{% endif %}
|
||||
</small>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-outline-primary select-user-btn"
|
||||
data-user-id="{{ user.id }}"
|
||||
data-username="{{ user.username }}"
|
||||
data-role="{{ user.role }}"
|
||||
data-modules="{{ (user.get_modules() or []) | tojson }}"
|
||||
title="Select for quick edit">
|
||||
📝 Select
|
||||
</button>
|
||||
<button class="btn btn-outline-info edit-user-btn"
|
||||
data-user-id="{{ user.id }}"
|
||||
data-username="{{ user.username }}"
|
||||
data-role="{{ user.role }}"
|
||||
data-modules="{{ (user.get_modules() or []) | tojson }}"
|
||||
title="Full edit">
|
||||
⚙️ Edit
|
||||
</button>
|
||||
{% if user.username != session.get('user') %}
|
||||
<button class="btn btn-outline-danger delete-user-btn"
|
||||
data-user-id="{{ user.id }}"
|
||||
data-username="{{ user.username }}"
|
||||
title="Delete user">
|
||||
🗑️
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-users fa-3x mb-3"></i>
|
||||
<p>No users found. Create your first user above!</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit User Modal -->
|
||||
<div class="modal fade" id="editUserModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Edit User</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form id="editUserForm" method="POST" action="/edit_user_simple">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="edit_user_id" name="user_id">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit_username">Username:</label>
|
||||
<input type="text" id="edit_username" name="username" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit_password">Password (leave blank to keep current):</label>
|
||||
<input type="password" id="edit_password" name="password" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit_role">Role:</label>
|
||||
<select id="edit_role" name="role" class="form-control" required onchange="updateEditModuleSelection()">
|
||||
<option value="">Select a role...</option>
|
||||
<option value="superadmin">Superadmin - Full system access</option>
|
||||
<option value="admin">Admin - Full app access (except role permissions)</option>
|
||||
<option value="manager">Manager - Module-based access (can have multiple modules)</option>
|
||||
<option value="worker">Worker - Limited module access (can have multiple modules)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="editModuleSelection" style="display: none;">
|
||||
<label>Modules:</label>
|
||||
<div class="module-checkboxes">
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="edit_module_quality" name="modules" value="quality">
|
||||
<label for="edit_module_quality">Quality Control</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="edit_module_warehouse" name="modules" value="warehouse">
|
||||
<label for="edit_module_warehouse">Warehouse Management</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="edit_module_labels" name="modules" value="labels">
|
||||
<label for="edit_module_labels">Label Management</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="editAccessLevelInfo" class="access-level-info" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updateModuleSelection() {
|
||||
const role = document.getElementById('role').value;
|
||||
const moduleSelection = document.getElementById('moduleSelection');
|
||||
const accessLevelInfo = document.getElementById('accessLevelInfo');
|
||||
const checkboxes = document.querySelectorAll('#moduleSelection input[type="checkbox"]');
|
||||
|
||||
if (role === 'superadmin' || role === 'admin') {
|
||||
moduleSelection.style.display = 'none';
|
||||
checkboxes.forEach(cb => cb.checked = false);
|
||||
accessLevelInfo.style.display = 'none';
|
||||
} else if (role === 'manager' || role === 'worker') {
|
||||
moduleSelection.style.display = 'block';
|
||||
checkboxes.forEach(cb => cb.checked = false);
|
||||
|
||||
if (role === 'manager') {
|
||||
accessLevelInfo.style.display = 'block';
|
||||
accessLevelInfo.innerHTML = '<strong>Manager Access:</strong> Can have multiple modules. Full access to assigned modules including reports and management functions.';
|
||||
} else if (role === 'worker') {
|
||||
accessLevelInfo.style.display = 'block';
|
||||
accessLevelInfo.innerHTML = '<strong>Worker Access:</strong> Can have multiple modules. Limited to basic operations only (no reports or management functions). <br><small>Examples: Quality worker can scan, Warehouse worker can move orders, etc.</small>';
|
||||
}
|
||||
} else {
|
||||
moduleSelection.style.display = 'none';
|
||||
accessLevelInfo.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function updateEditModuleSelection() {
|
||||
const role = document.getElementById('edit_role').value;
|
||||
const moduleSelection = document.getElementById('editModuleSelection');
|
||||
const accessLevelInfo = document.getElementById('editAccessLevelInfo');
|
||||
const checkboxes = document.querySelectorAll('#editModuleSelection input[type="checkbox"]');
|
||||
|
||||
if (role === 'superadmin' || role === 'admin') {
|
||||
moduleSelection.style.display = 'none';
|
||||
checkboxes.forEach(cb => cb.checked = false);
|
||||
accessLevelInfo.style.display = 'none';
|
||||
} else if (role === 'manager' || role === 'worker') {
|
||||
moduleSelection.style.display = 'block';
|
||||
|
||||
if (role === 'manager') {
|
||||
accessLevelInfo.style.display = 'block';
|
||||
accessLevelInfo.innerHTML = '<strong>Manager Access:</strong> Can have multiple modules. Full access to assigned modules including reports and management functions.';
|
||||
} else if (role === 'worker') {
|
||||
accessLevelInfo.style.display = 'block';
|
||||
accessLevelInfo.innerHTML = '<strong>Worker Access:</strong> Can have multiple modules. Limited to basic operations only (no reports or management functions). <br><small>Examples: Quality worker can scan, Warehouse worker can move orders, etc.</small>';
|
||||
}
|
||||
} else {
|
||||
moduleSelection.style.display = 'none';
|
||||
accessLevelInfo.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Global variable to store selected user
|
||||
let selectedUser = null;
|
||||
|
||||
function selectUserForQuickEdit(userId, username, role, modules) {
|
||||
selectedUser = {id: userId, username: username, role: role, modules: modules};
|
||||
|
||||
// Update the quick edit panel
|
||||
document.getElementById('selectedUserName').textContent = username;
|
||||
document.getElementById('selectedUserRole').textContent = role;
|
||||
|
||||
// Clear all module checkboxes first
|
||||
const checkboxes = document.querySelectorAll('#currentModules input[type="checkbox"]');
|
||||
checkboxes.forEach(cb => cb.checked = false);
|
||||
|
||||
// Check the appropriate modules
|
||||
if (modules && Array.isArray(modules)) {
|
||||
modules.forEach(module => {
|
||||
const checkbox = document.getElementById('quick_module_' + module);
|
||||
if (checkbox) checkbox.checked = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Show the quick edit panel
|
||||
document.getElementById('quickModuleEdit').style.display = 'block';
|
||||
document.querySelector('#userRightsPanel .text-center').style.display = 'none';
|
||||
|
||||
// Highlight selected row
|
||||
document.querySelectorAll('.user-row').forEach(row => {
|
||||
row.classList.remove('table-warning');
|
||||
});
|
||||
document.querySelector(`[data-user-id="${userId}"]`).classList.add('table-warning');
|
||||
}
|
||||
|
||||
function updateUserModules() {
|
||||
if (!selectedUser) {
|
||||
alert('No user selected');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get selected modules
|
||||
const checkboxes = document.querySelectorAll('#currentModules input[type="checkbox"]:checked');
|
||||
const modules = Array.from(checkboxes).map(cb => cb.value);
|
||||
|
||||
// Validate modules for the role
|
||||
if ((selectedUser.role === 'manager' || selectedUser.role === 'worker') && modules.length === 0) {
|
||||
alert(`${selectedUser.role}s must have at least one module assigned.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create and submit form
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/quick_update_modules';
|
||||
|
||||
// Add user ID
|
||||
const userIdInput = document.createElement('input');
|
||||
userIdInput.type = 'hidden';
|
||||
userIdInput.name = 'user_id';
|
||||
userIdInput.value = selectedUser.id;
|
||||
form.appendChild(userIdInput);
|
||||
|
||||
// Add modules
|
||||
modules.forEach(module => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'modules';
|
||||
input.value = module;
|
||||
form.appendChild(input);
|
||||
});
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
function showFullEditModal() {
|
||||
if (!selectedUser) {
|
||||
alert('No user selected');
|
||||
return;
|
||||
}
|
||||
|
||||
editUser(selectedUser.id, selectedUser.username, selectedUser.role, selectedUser.modules);
|
||||
}
|
||||
|
||||
function deleteSelectedUser() {
|
||||
if (!selectedUser) {
|
||||
alert('No user selected');
|
||||
return;
|
||||
}
|
||||
|
||||
deleteUser(selectedUser.id, selectedUser.username);
|
||||
}
|
||||
|
||||
function editUser(userId, username, role, modules) {
|
||||
document.getElementById('edit_user_id').value = userId;
|
||||
document.getElementById('edit_username').value = username;
|
||||
document.getElementById('edit_role').value = role;
|
||||
document.getElementById('edit_password').value = '';
|
||||
|
||||
// Clear all module checkboxes first
|
||||
const checkboxes = document.querySelectorAll('#editModuleSelection input[type="checkbox"]');
|
||||
checkboxes.forEach(cb => cb.checked = false);
|
||||
|
||||
// Check the appropriate modules
|
||||
if (modules && Array.isArray(modules)) {
|
||||
modules.forEach(module => {
|
||||
const checkbox = document.getElementById('edit_module_' + module);
|
||||
if (checkbox) checkbox.checked = true;
|
||||
});
|
||||
}
|
||||
|
||||
updateEditModuleSelection();
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById('editUserModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
function deleteUser(userId, username) {
|
||||
if (confirm(`Are you sure you want to delete user "${username}"?`)) {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/delete_user_simple';
|
||||
|
||||
const userIdInput = document.createElement('input');
|
||||
userIdInput.type = 'hidden';
|
||||
userIdInput.name = 'user_id';
|
||||
userIdInput.value = userId;
|
||||
|
||||
form.appendChild(userIdInput);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
function deleteUser(userId, username) {
|
||||
if (confirm(`Are you sure you want to delete user "${username}"?`)) {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/delete_user_simple';
|
||||
|
||||
const userIdInput = document.createElement('input');
|
||||
userIdInput.type = 'hidden';
|
||||
userIdInput.name = 'user_id';
|
||||
userIdInput.value = userId;
|
||||
|
||||
form.appendChild(userIdInput);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
updateModuleSelection();
|
||||
|
||||
// Add event listeners for select user buttons
|
||||
document.querySelectorAll('.select-user-btn').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const userId = this.dataset.userId;
|
||||
const username = this.dataset.username;
|
||||
const role = this.dataset.role;
|
||||
let modules = [];
|
||||
try {
|
||||
modules = JSON.parse(this.dataset.modules) || [];
|
||||
} catch (e) {
|
||||
console.log('Error parsing modules for user:', username, e);
|
||||
modules = [];
|
||||
}
|
||||
selectUserForQuickEdit(userId, username, role, modules);
|
||||
});
|
||||
});
|
||||
|
||||
// Add event listeners for edit buttons
|
||||
document.querySelectorAll('.edit-user-btn').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const userId = this.dataset.userId;
|
||||
const username = this.dataset.username;
|
||||
const role = this.dataset.role;
|
||||
let modules = [];
|
||||
try {
|
||||
modules = JSON.parse(this.dataset.modules) || [];
|
||||
} catch (e) {
|
||||
console.log('Error parsing modules for user:', username, e);
|
||||
modules = [];
|
||||
}
|
||||
editUser(userId, username, role, modules);
|
||||
});
|
||||
});
|
||||
|
||||
// Add event listeners for delete buttons
|
||||
document.querySelectorAll('.delete-user-btn').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const userId = this.dataset.userId;
|
||||
const username = this.dataset.username;
|
||||
deleteUser(userId, username);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user