Initial commit: Quality App v2 - FG Scan Module with Reports
This commit is contained in:
137
app/static/js/base.js
Normal file
137
app/static/js/base.js
Normal file
@@ -0,0 +1,137 @@
|
||||
/* Base JavaScript Utilities */
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize tooltips and popovers
|
||||
initializeBootstrap();
|
||||
|
||||
// Setup flash message auto-close
|
||||
setupFlashMessages();
|
||||
|
||||
// Setup theme toggle if available
|
||||
setupThemeToggle();
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialize Bootstrap tooltips and popovers
|
||||
*/
|
||||
function initializeBootstrap() {
|
||||
// Initialize all tooltips
|
||||
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||
});
|
||||
|
||||
// Initialize all popovers
|
||||
const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
|
||||
popoverTriggerList.map(function (popoverTriggerEl) {
|
||||
return new bootstrap.Popover(popoverTriggerEl);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-close flash messages after 5 seconds
|
||||
*/
|
||||
function setupFlashMessages() {
|
||||
const alerts = document.querySelectorAll('.alert');
|
||||
alerts.forEach(function(alert) {
|
||||
setTimeout(function() {
|
||||
const bsAlert = new bootstrap.Alert(alert);
|
||||
bsAlert.close();
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup theme toggle functionality
|
||||
*/
|
||||
function setupThemeToggle() {
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
if (!themeToggle) return;
|
||||
|
||||
const currentTheme = localStorage.getItem('theme') || 'light';
|
||||
applyTheme(currentTheme);
|
||||
|
||||
themeToggle.addEventListener('click', function() {
|
||||
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
||||
applyTheme(newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply theme to document
|
||||
*/
|
||||
function applyTheme(theme) {
|
||||
const html = document.documentElement;
|
||||
if (theme === 'dark') {
|
||||
html.setAttribute('data-bs-theme', 'dark');
|
||||
} else {
|
||||
html.removeAttribute('data-bs-theme');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a toast notification
|
||||
*/
|
||||
function showToast(message, type = 'info') {
|
||||
const toastHtml = `
|
||||
<div class="toast align-items-center text-white bg-${type} border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">
|
||||
${message}
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const toastContainer = document.querySelector('.toast-container') || createToastContainer();
|
||||
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
|
||||
|
||||
const toastElement = toastContainer.lastElementChild;
|
||||
const toast = new bootstrap.Toast(toastElement);
|
||||
toast.show();
|
||||
|
||||
toastElement.addEventListener('hidden.bs.toast', function() {
|
||||
toastElement.remove();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create toast container if it doesn't exist
|
||||
*/
|
||||
function createToastContainer() {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'toast-container position-fixed bottom-0 end-0 p-3';
|
||||
document.body.appendChild(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounce function for input handlers
|
||||
*/
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Throttle function for scroll/resize handlers
|
||||
*/
|
||||
function throttle(func, limit) {
|
||||
let inThrottle;
|
||||
return function(...args) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
94
app/static/js/theme.js
Normal file
94
app/static/js/theme.js
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Theme Toggle - Light/Dark Mode
|
||||
* Persists user preference in localStorage
|
||||
*/
|
||||
|
||||
class ThemeToggle {
|
||||
constructor() {
|
||||
this.STORAGE_KEY = 'quality-app-theme';
|
||||
this.DARK_THEME = 'dark';
|
||||
this.LIGHT_THEME = 'light';
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Load saved theme or default to light
|
||||
const savedTheme = localStorage.getItem(this.STORAGE_KEY) || this.LIGHT_THEME;
|
||||
this.setTheme(savedTheme);
|
||||
|
||||
// Setup toggle button listener
|
||||
this.setupToggleButton();
|
||||
|
||||
console.log('Theme initialized:', savedTheme);
|
||||
}
|
||||
|
||||
setTheme(theme) {
|
||||
const isDark = theme === this.DARK_THEME;
|
||||
|
||||
if (isDark) {
|
||||
document.body.setAttribute('data-theme', 'dark');
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
localStorage.setItem(this.STORAGE_KEY, this.DARK_THEME);
|
||||
console.log('Dark theme set');
|
||||
} else {
|
||||
document.body.setAttribute('data-theme', 'light');
|
||||
document.documentElement.setAttribute('data-theme', 'light');
|
||||
localStorage.setItem(this.STORAGE_KEY, this.LIGHT_THEME);
|
||||
console.log('Light theme set');
|
||||
}
|
||||
|
||||
// Update toggle button icon
|
||||
this.updateToggleIcon(isDark);
|
||||
}
|
||||
|
||||
getCurrentTheme() {
|
||||
return document.body.getAttribute('data-theme') || this.LIGHT_THEME;
|
||||
}
|
||||
|
||||
toggleTheme() {
|
||||
const currentTheme = this.getCurrentTheme();
|
||||
const newTheme = currentTheme === this.DARK_THEME ? this.LIGHT_THEME : this.DARK_THEME;
|
||||
console.log('Toggling from', currentTheme, 'to', newTheme);
|
||||
this.setTheme(newTheme);
|
||||
}
|
||||
|
||||
setupToggleButton() {
|
||||
const toggleBtn = document.getElementById('themeToggleBtn');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.toggleTheme();
|
||||
});
|
||||
console.log('Theme toggle button setup complete');
|
||||
} else {
|
||||
console.error('Theme toggle button not found');
|
||||
}
|
||||
}
|
||||
|
||||
updateToggleIcon(isDark) {
|
||||
const toggleBtn = document.getElementById('themeToggleBtn');
|
||||
if (toggleBtn) {
|
||||
const icon = toggleBtn.querySelector('i');
|
||||
if (icon) {
|
||||
if (isDark) {
|
||||
icon.classList.remove('fa-moon');
|
||||
icon.classList.add('fa-sun');
|
||||
toggleBtn.setAttribute('title', 'Switch to Light Mode');
|
||||
} else {
|
||||
icon.classList.remove('fa-sun');
|
||||
icon.classList.add('fa-moon');
|
||||
toggleBtn.setAttribute('title', 'Switch to Dark Mode');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize theme toggle when DOM is loaded
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ThemeToggle();
|
||||
});
|
||||
} else {
|
||||
new ThemeToggle();
|
||||
}
|
||||
Reference in New Issue
Block a user