/** * @fileoverview Manages the application settings functionality. * This file handles color settings, tab persistence, and UI customization. * It provides functionality to: * - Manage primary and secondary color schemes * - Handle settings tab navigation and persistence * - Convert and manipulate colors (hex, RGB) * - Update UI elements with new color schemes */ document.addEventListener('DOMContentLoaded', function() { const primaryColorInput = document.getElementById('primaryColor'); const secondaryColorInput = document.getElementById('secondaryColor'); const colorSettingsForm = document.getElementById('colorSettingsForm'); const companyInfoForm = document.getElementById('companyInfoForm'); // Get all tab buttons const settingsTabs = document.querySelectorAll('[data-bs-toggle="tab"]'); /** * Activates a specific settings tab and updates the UI accordingly. * Also persists the selected tab in localStorage. * @function * @param {string} tabId - The ID of the tab to activate */ function activateTab(tabId) { // Update URL without reloading the page const url = new URL(window.location.href); url.searchParams.set('tab', tabId); window.history.pushState({}, '', url); // Save active tab to localStorage localStorage.setItem('settingsActiveTab', tabId); // Update active state of tabs settingsTabs.forEach(tab => { const targetId = tab.getAttribute('data-bs-target').substring(1); if (targetId === tabId) { tab.classList.add('active'); } else { tab.classList.remove('active'); } }); // Update active state of tab panes document.querySelectorAll('.tab-pane').forEach(pane => { if (pane.id === tabId) { pane.classList.add('show', 'active'); } else { pane.classList.remove('show', 'active'); } }); // If switching to events tab, fetch events data if (tabId === 'events') { fetchEvents(); } } /** * Fetches events data from the server and updates the events table * @function */ function fetchEvents() { const url = new URL(window.location.href); const eventType = url.searchParams.get('event_type') || ''; const dateRange = url.searchParams.get('date_range') || '7d'; const userId = url.searchParams.get('user_id') || ''; const page = url.searchParams.get('page') || 1; // Show loading state const eventsTableBody = document.getElementById('eventsTableBody'); if (eventsTableBody) { eventsTableBody.innerHTML = 'Loading events...'; } // Get CSRF token const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); // Fetch events data fetch(`/settings/events?event_type=${eventType}&date_range=${dateRange}&user_id=${userId}&page=${page}`, { headers: { 'X-Requested-With': 'XMLHttpRequest', 'X-CSRF-Token': csrfToken } }) .then(response => response.text()) .then(html => { // Extract the events table content from the response const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const newEventsTable = doc.getElementById('eventsTableBody'); console.log('Found table body:', newEventsTable); // Debug log if (newEventsTable && eventsTableBody) { eventsTableBody.innerHTML = newEventsTable.innerHTML; console.log('Updated table content'); // Debug log } else { console.error('Could not find events table in response'); if (eventsTableBody) { eventsTableBody.innerHTML = 'Error: Could not load events'; } } }) .catch(error => { console.error('Error fetching events:', error); if (eventsTableBody) { eventsTableBody.innerHTML = 'Error loading events'; } }); } // Add click event listeners to tabs settingsTabs.forEach(tab => { tab.addEventListener('click', (e) => { const tabId = e.target.getAttribute('data-bs-target').substring(1); activateTab(tabId); }); }); // Restore active tab from localStorage or URL hash const savedTab = localStorage.getItem('settingsActiveTab') || window.location.hash.substring(1) || 'colors'; activateTab(savedTab); /** * Converts a hexadecimal color code to RGB values. * @function * @param {string} hex - The hexadecimal color code (e.g., '#FF0000') * @returns {Object|null} Object containing r, g, b values or null if invalid hex */ function hexToRgb(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } /** * Converts RGB values to a hexadecimal color code. * @function * @param {number} r - Red value (0-255) * @param {number} g - Green value (0-255) * @param {number} b - Blue value (0-255) * @returns {string} Hexadecimal color code */ function rgbToHex(r, g, b) { return '#' + [r, g, b].map(x => { const hex = x.toString(16); return hex.length === 1 ? '0' + hex : hex; }).join(''); } /** * Lightens a color by a specified amount. * @function * @param {string} color - The hexadecimal color to lighten * @param {number} amount - The amount to lighten (0-1) * @returns {string} The lightened hexadecimal color */ function lightenColor(color, amount) { const rgb = hexToRgb(color); if (!rgb) return color; return rgbToHex( Math.min(255, Math.round(rgb.r + (255 - rgb.r) * amount)), Math.min(255, Math.round(rgb.g + (255 - rgb.g) * amount)), Math.min(255, Math.round(rgb.b + (255 - rgb.b) * amount)) ); } /** * Darkens a color by a specified amount. * @function * @param {string} color - The hexadecimal color to darken * @param {number} amount - The amount to darken (0-1) * @returns {string} The darkened hexadecimal color */ function darkenColor(color, amount) { const rgb = hexToRgb(color); if (!rgb) return color; return rgbToHex( Math.max(0, Math.round(rgb.r * (1 - amount))), Math.max(0, Math.round(rgb.g * (1 - amount))), Math.max(0, Math.round(rgb.b * (1 - amount))) ); } /** * Updates all color-related UI elements based on a new color value. * Updates CSS variables, chart colors, and color previews. * @function * @param {string} color - The new color value in hexadecimal * @param {boolean} isPrimary - Whether this is the primary color (true) or secondary color (false) */ function updateAllColors(color, isPrimary) { const prefix = isPrimary ? 'primary' : 'secondary'; // Calculate derived colors const lightColor = lightenColor(color, 0.15); const bgLightColor = lightenColor(color, 0.9); const opacity15 = color + '26'; // 15% opacity in hex // Calculate chart colors const chartColors = { base: color, light: lightenColor(color, 0.2), lighter: lightenColor(color, 0.4), lightest: lightenColor(color, 0.6), pale: lightenColor(color, 0.8) }; // Update CSS variables for the entire website document.documentElement.style.setProperty(`--${prefix}-color`, color); document.documentElement.style.setProperty(`--${prefix}-light`, lightColor); document.documentElement.style.setProperty(`--${prefix}-bg-light`, bgLightColor); document.documentElement.style.setProperty(`--${prefix}-opacity-15`, opacity15); // Update chart color variables document.documentElement.style.setProperty(`--chart-${prefix}`, chartColors.base); document.documentElement.style.setProperty(`--chart-${prefix}-light`, chartColors.light); document.documentElement.style.setProperty(`--chart-${prefix}-lighter`, chartColors.lighter); document.documentElement.style.setProperty(`--chart-${prefix}-lightest`, chartColors.lightest); document.documentElement.style.setProperty(`--chart-${prefix}-pale`, chartColors.pale); // Update derived colors in the preview section const derivedColorsSection = document.getElementById(`${prefix}DerivedColors`); if (derivedColorsSection) { const previews = derivedColorsSection.querySelectorAll('.color-preview'); previews.forEach(preview => { const colorType = preview.getAttribute('data-color-type'); let previewColor; switch (colorType) { case 'base': previewColor = color; break; case 'light': previewColor = lightColor; break; case 'bg-light': previewColor = bgLightColor; break; case 'opacity': previewColor = opacity15; break; } preview.style.backgroundColor = previewColor; }); } // Update the color input value if (isPrimary) { primaryColorInput.value = color; } else { secondaryColorInput.value = color; } } // Event listeners for color inputs primaryColorInput.addEventListener('change', () => { updateAllColors(primaryColorInput.value, true); }); secondaryColorInput.addEventListener('change', () => { updateAllColors(secondaryColorInput.value, false); }); // Also listen for input events for real-time updates primaryColorInput.addEventListener('input', () => { updateAllColors(primaryColorInput.value, true); }); secondaryColorInput.addEventListener('input', () => { updateAllColors(secondaryColorInput.value, false); }); /** * Initializes the color settings from the current CSS variables. * Updates input values and color previews. * @function */ function initializeColors() { // Get the current computed CSS variable values const computedPrimaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim(); const computedSecondaryColor = getComputedStyle(document.documentElement).getPropertyValue('--secondary-color').trim(); console.log('[Settings] Initializing colors:', { primary: computedPrimaryColor, secondary: computedSecondaryColor, timestamp: new Date().toISOString() }); // Update input values primaryColorInput.value = computedPrimaryColor; secondaryColorInput.value = computedSecondaryColor; // Update all color previews updateAllColors(computedPrimaryColor, true); updateAllColors(computedSecondaryColor, false); } // Initialize colors when the page loads initializeColors(); // Handle form submission if (colorSettingsForm) { colorSettingsForm.addEventListener('submit', function(e) { e.preventDefault(); // Prevent default form submission const formData = new FormData(colorSettingsForm); const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); fetch(colorSettingsForm.action, { method: 'POST', headers: { 'X-CSRF-Token': csrfToken }, body: formData }) .then(response => { if (response.ok) { window.location.reload(); // Reload to show updated colors } else { throw new Error('Failed to update colors'); } }) .catch(error => { console.error('Error updating colors:', error); alert('Failed to update colors. Please try again.'); }); }); } // Handle company info form submission if (companyInfoForm) { companyInfoForm.addEventListener('submit', function(e) { e.preventDefault(); const formData = new FormData(companyInfoForm); const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); fetch(companyInfoForm.action, { method: 'POST', headers: { 'X-CSRF-Token': csrfToken }, body: formData }) .then(response => { if (response.ok) { window.location.reload(); // Reload to show updated info } else { throw new Error('Failed to update company info'); } }) .catch(error => { console.error('Error updating company info:', error); alert('Failed to update company info. Please try again.'); }); }); } // Add event listener for download button const downloadEventsBtn = document.getElementById('downloadEvents'); if (downloadEventsBtn) { downloadEventsBtn.addEventListener('click', function() { const eventType = document.getElementById('eventTypeFilter').value; const dateRange = document.getElementById('dateRangeFilter').value; const userId = document.getElementById('userFilter').value; // Construct download URL with current filters const downloadUrl = `/settings/events/download?event_type=${eventType}&date_range=${dateRange}&user_id=${userId}`; // Trigger download window.location.href = downloadUrl; }); } });