// Snauw Counter - Frontend JavaScript // Global variables let selectedSeverity = null; // Fast datetime initialization - runs as soon as script loads function fastDateTimeInit() { const dateTimeInput = document.getElementById('incident_time'); if (dateTimeInput) { const now = new Date(); const formattedDateTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}T${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; dateTimeInput.value = formattedDateTime; } } // Try to initialize datetime immediately if elements exist if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', fastDateTimeInit); } else { fastDateTimeInit(); } // Initialize app when DOM is loaded document.addEventListener('DOMContentLoaded', function() { // Initialize date/time FIRST - before any user interaction initializeDateTime(); trackDateTimeModification(); // Then initialize other components initializeSeveritySelector(); initializeAnonymousUserSupport(); loadChart(); updateHeaderCounter(); // Hide privacy notice als gebruiker het al eerder heeft weggedaan if (localStorage.getItem('privacy-notice-hidden')) { const notice = document.getElementById('privacy-notice'); if (notice) notice.style.display = 'none'; } }); // Initialize date/time fields with current date and time function initializeDateTime() { const dateTimeInput = document.getElementById('incident_time'); if (dateTimeInput) { // Get current date and time const now = new Date(); // Format to datetime-local format (YYYY-MM-DDTHH:mm) const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const formattedDateTime = `${year}-${month}-${day}T${hours}:${minutes}`; dateTimeInput.value = formattedDateTime; // Add helper text const helpText = dateTimeInput.nextElementSibling; if (helpText && helpText.tagName === 'SMALL') { helpText.innerHTML = `✓ Automatisch ingevuld: ${formatDisplayTime(now)}`; } } } // Format time for display function formatDisplayTime(date) { return date.toLocaleString('nl-NL', { weekday: 'short', day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' }); } // Update date/time when page becomes visible (for accuracy) document.addEventListener('visibilitychange', function() { if (!document.hidden) { // Only update if the field hasn't been manually changed const dateTimeInput = document.getElementById('incident_time'); if (dateTimeInput && !dateTimeInput.dataset.userModified) { initializeDateTime(); } } }); // Track if user manually modifies the datetime field function trackDateTimeModification() { const dateTimeInput = document.getElementById('incident_time'); if (dateTimeInput) { dateTimeInput.addEventListener('input', function() { this.dataset.userModified = 'true'; const helpText = this.nextElementSibling; if (helpText && helpText.tagName === 'SMALL') { helpText.innerHTML = 'Handmatig aangepast'; } }); // Add reset button const resetBtn = document.createElement('button'); resetBtn.type = 'button'; resetBtn.className = 'btn btn-secondary'; resetBtn.style.marginLeft = '0.5rem'; resetBtn.style.padding = '0.25rem 0.5rem'; resetBtn.style.fontSize = '0.8rem'; resetBtn.innerHTML = '🔄 Nu'; resetBtn.title = 'Reset naar huidige tijd'; resetBtn.onclick = function() { delete dateTimeInput.dataset.userModified; initializeDateTime(); }; // Only add reset button if it doesn't exist yet if (!dateTimeInput.parentNode.querySelector('.btn.btn-secondary')) { dateTimeInput.parentNode.insertBefore(resetBtn, dateTimeInput.nextSibling); } } } // Update date/time when page becomes visible (for accuracy) document.addEventListener('visibilitychange', function() { if (!document.hidden) { // Only update if the field hasn't been manually changed const dateTimeInput = document.getElementById('incident_time'); if (dateTimeInput && !dateTimeInput.dataset.userModified) { initializeDateTime(); } } }); // Quick add incident functionality function quickAddIncident() { // Check if we're already on add incident page if (window.location.pathname.includes('/add')) { return; } // For anonymous users, show quick add modal if (!document.body.dataset.isAuthenticated) { showQuickAddModal(); } else { // Redirect to add incident page window.location.href = '/add-incident'; } } // Show quick add modal for anonymous users function showQuickAddModal() { const modal = document.createElement('div'); modal.className = 'modal'; modal.innerHTML = ` `; document.body.appendChild(modal); // Initialize severity selector for modal initializeSeveritySelector(); // Add modal styles const style = document.createElement('style'); style.textContent = ` .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 2000; padding: 1rem; } .modal-content { background: white; border-radius: 12px; max-width: 500px; width: 100%; max-height: 80vh; overflow-y: auto; } .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.5rem; border-bottom: 1px solid #e9ecef; } .modal-body { padding: 1.5rem; } .close-btn { background: none; border: none; font-size: 1.5rem; cursor: pointer; color: #6c757d; } `; document.head.appendChild(style); } // Initialize severity selector buttons function initializeSeveritySelector() { const severityButtons = document.querySelectorAll('.severity-btn'); const severityInput = document.getElementById('severity-input'); severityButtons.forEach(button => { button.addEventListener('click', function() { // Remove active class from all buttons severityButtons.forEach(btn => btn.classList.remove('active')); // Add active class to clicked button this.classList.add('active'); // Update selected severity selectedSeverity = parseInt(this.dataset.severity); // Update hidden input value if it exists if (severityInput) { severityInput.value = selectedSeverity; } console.log(`Selected severity: ${selectedSeverity}`); }); }); } // Update header counter with total incidents function updateHeaderCounter() { const counterDigit1 = document.getElementById('counter-digit-1'); const counterDigit2 = document.getElementById('counter-digit-2'); const counterDigit3 = document.getElementById('counter-digit-3'); // Also update the old header-counter for backwards compatibility const counterElement = document.getElementById('header-counter'); if (!counterDigit1 && !counterElement) return; let totalIncidents = 0; if (document.body.dataset.isAuthenticated === 'true') { // For authenticated users - would normally fetch from API // For now, we'll use a placeholder totalIncidents = 0; } else { // For anonymous users - use localStorage const incidents = JSON.parse(localStorage.getItem('snauw_incidents') || '[]'); totalIncidents = incidents.length; } // Format number with leading zeros const formattedCount = totalIncidents.toString().padStart(3, '0'); // Update individual digits if they exist (new banner) if (counterDigit1 && counterDigit2 && counterDigit3) { counterDigit1.textContent = formattedCount[0]; counterDigit2.textContent = formattedCount[1]; counterDigit3.textContent = formattedCount[2]; // Add pulsing animation for new incidents if (totalIncidents > 0) { [counterDigit1, counterDigit2, counterDigit3].forEach(digit => { digit.style.animation = 'pulse 2s ease-in-out infinite'; }); } } // Update old counter element if it exists (backwards compatibility) if (counterElement) { counterElement.textContent = formattedCount; // Add pulsing animation for new incidents if (totalIncidents > 0) { counterElement.style.animation = 'pulse 2s ease-in-out infinite'; } } } // Generate Snauw-index buttons with descriptions function getSnauwIndexButtons() { const descriptions = [ 'Lichte correctie', 'Geprikkelde waarschuwing', 'Openlijke snauw', 'Bijtende aanval', 'Explosieve uitval' ]; return [...Array(5)].map((_, i) => ` `).join(''); } // Close modal function closeModal() { const modal = document.querySelector('.modal'); if (modal) { modal.remove(); } } // Submit quick incident for anonymous users async function submitQuickIncident(event) { event.preventDefault(); if (!selectedSeverity) { alert('Selecteer een Snauw-index'); return; } const notes = document.getElementById('quick-notes').value; const now = new Date(); const incident = { severity: selectedSeverity, notes: notes, timestamp: now.toISOString() }; // Store in localStorage for anonymous users let incidents = JSON.parse(localStorage.getItem('snauw_incidents') || '[]'); incident.id = Date.now(); // Simple ID for local storage incidents.push(incident); localStorage.setItem('snauw_incidents', JSON.stringify(incidents)); // Update header counter updateHeaderCounter(); // Add animation to counter const counterElement = document.getElementById('header-counter'); if (counterElement) { counterElement.classList.add('updated'); setTimeout(() => counterElement.classList.remove('updated'), 3000); } // Show success message showNotification('✅ Incident opgeslagen!', 'success'); // Close modal closeModal(); // Refresh statistics if on stats page if (window.location.pathname.includes('statistics')) { location.reload(); } } // Show notification function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `alert alert-${type}`; notification.textContent = message; notification.style.position = 'fixed'; notification.style.top = '2rem'; notification.style.right = '2rem'; notification.style.zIndex = '3000'; notification.style.minWidth = '250px'; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); } // Anonymous user support function initializeAnonymousUserSupport() { // Check if user has incidents in localStorage const localIncidents = localStorage.getItem('snauw_incidents'); if (localIncidents && !document.body.dataset.isAuthenticated) { const incidents = JSON.parse(localIncidents); if (incidents.length > 0) { showMigrationOffer(); } } } // Show migration offer to anonymous users with data function showMigrationOffer() { const incidents = JSON.parse(localStorage.getItem('snauw_incidents') || '[]'); if (incidents.length < 3) return; // Only show after a few incidents const lastOffer = localStorage.getItem('migration-offer-shown'); const oneWeekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000); if (lastOffer && parseInt(lastOffer) > oneWeekAgo) return; // Don't show too often const banner = document.createElement('div'); banner.className = 'migration-banner'; banner.innerHTML = `
💾 Je hebt ${incidents.length} incidenten opgeslagen!
Maak een account om je gegevens veilig te bewaren en op meerdere apparaten te gebruiken.
Account maken
`; const main = document.querySelector('main.container'); main.insertBefore(banner, main.firstChild); localStorage.setItem('migration-offer-shown', Date.now().toString()); } // Hide migration banner function hideMigrationBanner() { const banner = document.querySelector('.migration-banner'); if (banner) banner.remove(); } // Hide privacy notice function hidePrivacyNotice() { const notice = document.getElementById('privacy-notice'); if (notice) { notice.style.display = 'none'; localStorage.setItem('privacy-notice-hidden', 'true'); } } // Chart functionality function loadChart() { const chartCanvas = document.getElementById('incidentsChart'); if (!chartCanvas) return; // For anonymous users, use localStorage data if (!document.body.dataset.isAuthenticated) { loadAnonymousChart(); } else { loadUserChart(); } } // Load chart for anonymous users function loadAnonymousChart() { const incidents = JSON.parse(localStorage.getItem('snauw_incidents') || '[]'); // Group by date const dateGroups = {}; incidents.forEach(incident => { const date = new Date(incident.timestamp).toDateString(); if (!dateGroups[date]) { dateGroups[date] = []; } dateGroups[date].push(incident); }); // Prepare chart data const dates = Object.keys(dateGroups).slice(-7); // Last 7 days const counts = dates.map(date => dateGroups[date].length); const averages = dates.map(date => { const dayIncidents = dateGroups[date]; const sum = dayIncidents.reduce((acc, inc) => acc + inc.severity, 0); return sum / dayIncidents.length || 0; }); renderChart(dates, counts, averages); } // Load chart for authenticated users (would fetch from server) function loadUserChart() { // This would make an API call to get user's incident data // For now, placeholder renderChart([], [], []); } // Render chart using Chart.js (simplified version without external dependency) function renderChart(dates, counts, averages) { const canvas = document.getElementById('incidentsChart'); if (!canvas) return; const ctx = canvas.getContext('2d'); // Simple bar chart implementation // In a real implementation, you'd use Chart.js library ctx.fillStyle = '#667eea'; ctx.fillRect(10, 10, 50, 100); ctx.fillText('Chart placeholder - Use Chart.js in production', 10, 130); } // Form validation function validateIncidentForm() { const severity = document.getElementById('severity-input'); if (!severity || !severity.value) { alert('Selecteer een Snauw-index'); return false; } const severityValue = parseInt(severity.value); if (severityValue < 1 || severityValue > 5) { alert('Snauw-index moet tussen 1 en 5 zijn'); return false; } return true; } // Export data (for GDPR compliance) function exportData() { let data; if (document.body.dataset.isAuthenticated) { // Would fetch from server for authenticated users alert('Export functionaliteit wordt nog geïmplementeerd voor geregistreerde gebruikers'); return; } else { // Export localStorage data for anonymous users data = { incidents: JSON.parse(localStorage.getItem('snauw_incidents') || '[]'), exportDate: new Date().toISOString(), type: 'anonymous_user_data' }; } const dataStr = JSON.stringify(data, null, 2); const dataBlob = new Blob([dataStr], {type: 'application/json'}); const link = document.createElement('a'); link.href = URL.createObjectURL(dataBlob); link.download = `snauw_counter_export_${new Date().toISOString().split('T')[0]}.json`; link.click(); } // Delete all data (for GDPR compliance) function deleteAllData() { if (!confirm('Weet je zeker dat je alle gegevens wilt verwijderen? Dit kan niet ongedaan worden gemaakt.')) { return; } if (document.body.dataset.isAuthenticated) { // Would make API call for authenticated users alert('Data verwijdering voor geregistreerde gebruikers wordt nog geïmplementeerd'); return; } else { // Clear localStorage for anonymous users localStorage.removeItem('snauw_incidents'); localStorage.removeItem('privacy-notice-hidden'); localStorage.removeItem('migration-offer-shown'); showNotification('✅ Alle lokale gegevens zijn verwijderd', 'success'); setTimeout(() => { location.reload(); }, 2000); } }