Files
docupulse/static/js/settings.js
2025-06-02 11:46:42 +02:00

381 lines
15 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');
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 = '<tr><td colspan="5" class="text-center">Loading events...</td></tr>';
}
// 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 = '<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) {
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;
});
}
});