document.addEventListener('DOMContentLoaded', () => { const navToggle = document.querySelector('.nav-toggle'); const navMenu = document.querySelector('.nav-menu'); if (navToggle && navMenu) { navToggle.addEventListener('click', () => { navMenu.classList.toggle('open'); navToggle.classList.toggle('is-open'); }); } const flashMessages = document.querySelectorAll('.flash-message'); flashMessages.forEach(message => { const closeButton = message.querySelector('.flash-close'); const hideMessage = () => { message.classList.add('is-hidden'); window.setTimeout(() => message.remove(), 250); }; if (closeButton) { closeButton.addEventListener('click', hideMessage); } window.setTimeout(hideMessage, 5000); }); const filterForm = document.querySelector('.filter-card form'); if (filterForm) { const inputs = filterForm.querySelectorAll('input, select'); inputs.forEach(input => { input.addEventListener('change', () => { input.classList.add('has-value'); }); if (input.value) { input.classList.add('has-value'); } }); } const chartElements = document.querySelectorAll('[data-chart]'); if (chartElements.length > 0 && typeof window.Chart !== 'undefined') { chartElements.forEach(canvas => { const rawConfig = canvas.dataset.chart; if (!rawConfig) { return; } let config; try { config = JSON.parse(rawConfig); } catch (error) { console.error('Diagrammkonfiguration ist ungültig.', error); return; } if (!config || !Array.isArray(config.labels) || !Array.isArray(config.values)) { return; } const chartType = config.type ?? 'doughnut'; const isCircular = chartType === 'doughnut' || chartType === 'pie'; const parsedValues = config.values.map(value => (typeof value === 'number' ? value : Number(value))) .map(value => (Number.isFinite(value) ? value : 0)); const providedColors = Array.isArray(config.colors) ? config.colors : (typeof config.colors === 'string' ? [config.colors] : []); const dataset = { label: config.datasetLabel ?? '', data: parsedValues, borderWidth: config.borderWidth ?? (isCircular ? 1 : 2), fill: config.fill ?? !isCircular, tension: config.tension ?? (isCircular ? 0 : 0.35) }; if (isCircular) { dataset.backgroundColor = providedColors.length > 0 ? providedColors : ['#4460f7', '#3ac0a0', '#ff9f43', '#ef476f']; dataset.borderColor = config.borderColor ?? '#ffffff'; dataset.hoverOffset = 8; } else { const borderColor = config.borderColor ?? (providedColors.length > 0 ? providedColors[0] : '#4460f7'); const backgroundColor = providedColors.length > 0 ? providedColors[0] : 'rgba(68, 96, 247, 0.18)'; dataset.borderColor = borderColor; dataset.backgroundColor = config.fill === false ? 'transparent' : backgroundColor; dataset.pointBackgroundColor = borderColor; dataset.pointBorderColor = '#ffffff'; dataset.pointRadius = config.pointRadius ?? 4; } const options = { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: config.legend ?? isCircular, position: config.legendPosition ?? 'bottom' }, tooltip: { callbacks: { label: context => { const value = context.parsed; if (isCircular) { const datasetValues = context.dataset.data ?? []; const total = datasetValues.reduce((sum, entry) => (typeof entry === 'number' ? sum + entry : sum), 0); const percentage = total > 0 ? Math.round((value / total) * 100) : 0; return `${context.label}: ${value} (${percentage}%)`; } const label = context.label ?? ''; return label ? `${label}: ${value}` : `${value}`; } } }, title: config.title ? { display: true, text: config.title } : undefined }, scales: isCircular ? undefined : { y: { beginAtZero: true, ticks: { precision: 0, stepSize: 1 }, grid: { borderDash: [4, 4], color: 'rgba(107, 114, 133, 0.2)' } }, x: { grid: { display: false } } } }; new Chart(canvas, { type: chartType, data: { labels: config.labels, datasets: [dataset] }, options }); }); } });