314 lines
12 KiB
JavaScript
314 lines
12 KiB
JavaScript
/**
|
|
* @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');
|
|
|
|
// 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 = '<tr><td colspan="5" class="text-center">Loading events...</td></tr>';
|
|
}
|
|
|
|
// Fetch events data
|
|
fetch(`/settings/events?event_type=${eventType}&date_range=${dateRange}&user_id=${userId}&page=${page}`, {
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
})
|
|
.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 = '<tr><td colspan="5" class="text-center">Error: Could not load events</td></tr>';
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching events:', error);
|
|
if (eventsTableBody) {
|
|
eventsTableBody.innerHTML = '<tr><td colspan="5" class="text-center">Error loading events</td></tr>';
|
|
}
|
|
});
|
|
}
|
|
|
|
// 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) {
|
|
console.log('[Settings] Form submitted with values:', {
|
|
primary: primaryColorInput.value,
|
|
secondary: secondaryColorInput.value,
|
|
csrf: document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
});
|
|
}
|
|
});
|