// Dashboard functionality // Wait for the DOM to be fully loaded before running any code document.addEventListener("DOMContentLoaded", function() { // More reliable login detection - check for dashboard-specific elements const isLoggedIn = ( // Check for common dashboard elements !!document.querySelector('.dashboard-content') || !!document.querySelector('.side-navbar') || !!document.querySelector('#user-dropdown') || !!document.querySelector('.user-menu') || !!document.querySelector('.nav-link[href*="logout"]') || // Check URL patterns that would only be accessible when logged in window.location.pathname.includes('/dashboard') || window.location.pathname.includes('/profile') || window.location.pathname.includes('/app/') || // Explicit authentication flag window.IS_AUTHENTICATED === true ); if (isLoggedIn) { // Call with a slight delay to ensure page is fully loaded setTimeout(function() { checkWeakPassword(); }, 100); } // Initialize all components initializeComponents(); // Set up event handlers setupEventHandlers(); // Get device token for notifications if (typeof window.getDeviceToken === 'function' && isLoggedIn) { window.getDeviceToken(); } // Add global keyboard event handler for Escape key document.addEventListener('keydown', function(event) { if (event.key === 'Escape' || event.keyCode === 27) { closeAllModals(); } }); // Initialize responsive modals initResponsiveModals(); // Make all tables responsive makeAllTablesResponsive(); // Handle long content handleLongContent(); // Process any dynamically added tables const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'childList') { makeAllTablesResponsive(); handleLongContent(); } }); }); // Start observing the document for added nodes observer.observe(document.body, { childList: true, subtree: true }); }); // Function to initialize all dashboard components function initializeComponents() { // Initialize any Bootstrap components if (typeof bootstrap !== 'undefined') { // Initialize tooltips var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); tooltipTriggerList.map(function(tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); // Initialize popovers var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')); popoverTriggerList.map(function(popoverTriggerEl) { return new bootstrap.Popover(popoverTriggerEl); }); } // Handle image loading errors document.querySelectorAll('img').forEach(function(img) { img.addEventListener("error", function() { this.onerror = null; this.src = "/static/img/user.png"; }); }); // Setup star rating if elements exist if (typeof window.setupStarRating === 'function') { window.setupStarRating(); } // Setup tabs if elements exist if (typeof window.setupCustomTabs === 'function') { window.setupCustomTabs(); } // Setup feedback tabs if elements exist if (typeof window.setupFeedbackTabs === 'function') { window.setupFeedbackTabs(); } // Initialize Bootstrap components if (typeof window.initializeBootstrapComponents === 'function') { window.initializeBootstrapComponents(); } // Set up form validation if (typeof window.setupRatingFormValidation === 'function') { window.setupRatingFormValidation(); } // Ensure notifications.js is loaded if (typeof window.showNotification !== 'function') { // Dynamically load the notifications script if not already available const script = document.createElement('script'); script.src = '/static/js/notifications.js'; script.async = true; document.head.appendChild(script); } // Ensure notification toast container exists if (!document.getElementById('notificationToast')) { const toastContainer = document.createElement('div'); toastContainer.innerHTML = `
`; document.body.appendChild(toastContainer.firstElementChild); } } // Star rating functionality function setupStarRating() { document.querySelectorAll('.rating').forEach(function(container) { var stars = container.querySelectorAll('.star'); var ratingInput = container.querySelector('input[type="hidden"]'); stars.forEach(function(star, index) { // Set initial state if (ratingInput && parseInt(ratingInput.value) > index) { star.classList.add('active'); } // Add click handlers star.addEventListener('click', function() { // Update hidden input if (ratingInput) { ratingInput.value = index + 1; } // Update star display stars.forEach(function(s, i) { if (i <= index) { s.classList.add('active'); } else { s.classList.remove('active'); } }); }); // Add hover effects star.addEventListener('mouseenter', function() { stars.forEach(function(s, i) { if (i <= index) { s.classList.add('hover'); } else { s.classList.remove('hover'); } }); }); container.addEventListener('mouseleave', function() { stars.forEach(function(s) { s.classList.remove('hover'); }); }); }); }); } // Function to set up custom tabs function setupCustomTabs() { var tabLinks = document.querySelectorAll('.custom-tabs .tab-link'); tabLinks.forEach(function(link) { link.addEventListener('click', function(e) { e.preventDefault(); var target = this.getAttribute('data-target'); // Hide all tab content document.querySelectorAll('.custom-tabs .tab-content').forEach(function(tab) { tab.classList.remove('active'); }); // Deactivate all tab links tabLinks.forEach(function(tabLink) { tabLink.classList.remove('active'); }); // Activate clicked tab and content this.classList.add('active'); document.getElementById(target).classList.add('active'); }); }); // Activate first tab if none active if (document.querySelector('.custom-tabs .tab-link.active') === null) { var firstTab = document.querySelector('.custom-tabs .tab-link'); if (firstTab) { firstTab.click(); } } } // Function to set up feedback tabs function setupFeedbackTabs() { var tabLinks = document.querySelectorAll('.feedback-tabs .tab-link'); tabLinks.forEach(function(link) { link.addEventListener('click', function(e) { e.preventDefault(); var target = this.getAttribute('data-target'); // Hide all tab content document.querySelectorAll('.feedback-tabs .tab-content').forEach(function(tab) { tab.classList.remove('active'); }); // Deactivate all tab links tabLinks.forEach(function(tabLink) { tabLink.classList.remove('active'); }); // Activate clicked tab and content this.classList.add('active'); document.getElementById(target).classList.add('active'); }); }); } // Function to check if the current user's password is weak and show a popup if needed function checkWeakPassword() { // Only skip on login/register pages if (window.location.pathname.indexOf('/login') !== -1 || window.location.pathname.indexOf('/register') !== -1 || window.location.pathname === '/') { return; } // On dashboard page, we should definitely be logged in const onDashboard = window.location.pathname.includes('/dashboard') || window.location.pathname.includes('/app/'); try { // Basic fetch with no extra options to keep it simple fetch('/app/check-weak-password/') .then(function(response) { // Handle unauthorized responses (not logged in) if (response.status === 401 || response.status === 403) { return null; } // Check if response is valid JSON const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { return response.json().catch(error => { return response.text().then(text => { // Only show if there's an indication of a weak password return text.includes('weak') ? { is_weak: true } : { is_weak: false }; }); }); } else { // Not JSON, try to get text instead return response.text().then(text => { // Only show if there's an indication of a weak password return text.includes('weak') ? { is_weak: true } : { is_weak: false }; }); } }) .then(function(data) { // Skip if null (user not authenticated) if (data === null) return; if (data && data.is_weak === true) { // Show modal immediately showWeakPasswordModal(); } }) .catch(function(error) { console.error('Error checking password strength:', error); }); } catch (e) { console.error('Error in checkWeakPassword:', e); } } // Function to create and show the weak password modal function showWeakPasswordModal() { // Check if modal already exists (prevent duplicates) if (document.getElementById('weakPasswordModal')) { document.getElementById('weakPasswordModal').style.display = 'block'; return; } // Get CSRF token var csrfToken = ''; var csrfInput = document.querySelector('input[name="csrfmiddlewaretoken"]'); if (csrfInput) { csrfToken = csrfInput.value; } else { // Try to get from cookie if not found in form var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = cookies[i].trim(); if (cookie.startsWith('csrftoken=')) { csrfToken = cookie.substring('csrftoken='.length, cookie.length); break; } } } // Create modal HTML with password inputs var modalHtml = ` `; // Append modal to body document.body.insertAdjacentHTML('beforeend', modalHtml); // Add media query styles for better responsiveness const styleEl = document.createElement('style'); styleEl.textContent = ` @media (max-width: 576px) { #weakPasswordModal .modal-content { width: 95% !important; padding: 15px !important; } #weakPasswordModal .modal-title { font-size: 1.1rem !important; } #weakPasswordModal .form-label { font-size: 0.9rem !important; } } @media (max-height: 640px) { #weakPasswordModal { align-items: flex-start !important; padding-top: 20px !important; } } `; document.head.appendChild(styleEl); // Set up password handling directly var passwordArea = document.getElementById('new_password_area'); var confirmArea = document.getElementById('confirm_password_area'); var lengthDisplay = document.getElementById('password-length-display'); var changeBtn = document.getElementById('password-change-btn'); var showPasswordCheckbox = document.getElementById('show_password'); var passwordForm = document.getElementById('password-form-div'); if (passwordArea && lengthDisplay) { // Focus on password field setTimeout(function() { passwordArea.focus(); }, 100); // Update function that directly reads the password field function updateLengthDisplay() { var password = passwordArea.value || ''; // Update display with current length lengthDisplay.textContent = 'Current length: ' + password.length + ' characters'; } // Add multiple event listeners to catch all input cases ['input', 'keyup', 'keydown', 'change', 'paste'].forEach(function(eventType) { passwordArea.addEventListener(eventType, updateLengthDisplay); }); // Add keydown event for Enter key passwordArea.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); // Focus on confirm field if Enter is pressed in password field confirmArea.focus(); } }); // Add keydown event for Enter key on confirm field confirmArea.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); // Trigger submit button click if Enter is pressed in confirm field if (changeBtn) { changeBtn.click(); } } }); // Initial update updateLengthDisplay(); } // Toggle password visibility if (showPasswordCheckbox) { showPasswordCheckbox.addEventListener('change', function() { var type = this.checked ? 'text' : 'password'; passwordArea.type = type; confirmArea.type = type; }); } if (changeBtn) { changeBtn.addEventListener('click', function() { // Get the current password from the hidden field (more reliable) var currentPassword = document.getElementById('current_password_hidden').value || '123'; var newPassword = passwordArea.value || ''; var confirmPassword = confirmArea.value || ''; // Check for empty fields first if (!newPassword) { alert('New password is required.'); passwordArea.focus(); return; } if (!confirmPassword) { alert('Please confirm your password.'); confirmArea.focus(); return; } // Validate passwords if (newPassword.length < 8) { alert('Password must be at least 8 characters long! Your password is ' + newPassword.length + ' characters.'); passwordArea.focus(); return; } if (newPassword !== confirmPassword) { alert('Passwords do not match!'); confirmArea.focus(); return; } // Get CSRF token var csrfToken = document.querySelector('input[name="csrfmiddlewaretoken"]')?.value || ''; // Create form data var formData = new FormData(); formData.append('csrfmiddlewaretoken', csrfToken); formData.append('current_password', currentPassword); formData.append('new_password', newPassword); formData.append('confirm_password', confirmPassword); // Submit form fetch('/app/change-password/', { method: 'POST', body: formData, headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(response => response.json()) .then(function(data) { if (data.success) { // Show success message if (typeof window.showNotification === 'function') { window.showNotification('Password Updated', data.message || 'Your password has been changed successfully. Please login again.', 'success'); } else { alert(data.message || 'Password changed successfully. Please login again.'); } // Close the modal document.getElementById('weakPasswordModal')?.remove(); // Wait a moment for the notification to be visible setTimeout(function() { // Redirect to login page window.location.href = data.redirect || '/login/'; }, 1500); } else if (data.error) { // Show error notification if (typeof window.showNotification === 'function') { window.showNotification('Error', data.error, 'error'); } else { alert(data.error); } } }) .catch(error => { console.error('Error:', error); window.location.reload(); }); }); } // Handle form submission (including Enter key) if (passwordForm) { passwordForm.addEventListener('submit', function(e) { e.preventDefault(); // Trigger the password change button click if (changeBtn) { changeBtn.click(); } }); } } // Function to set up event handlers function setupEventHandlers() { // Set up any tab switching behavior var tabLinks = document.querySelectorAll('[data-bs-toggle="tab"]'); tabLinks.forEach(function(tabLink) { tabLink.addEventListener('click', function(event) { event.preventDefault(); var targetTab = document.querySelector(this.getAttribute('href')); if (targetTab) { // Hide all tabs document.querySelectorAll('.tab-pane').forEach(function(tab) { tab.classList.remove('active', 'show'); }); // Show target tab targetTab.classList.add('active', 'show'); // Update active state on tab links tabLinks.forEach(function(link) { link.classList.remove('active'); }); this.classList.add('active'); } }); }); // Close any alerts after a delay var alerts = document.querySelectorAll('.alert:not(.alert-warning)'); alerts.forEach(function(alert) { setTimeout(function() { alert.style.display = 'none'; }, 5000); }); } // Function to close all custom modals function closeAllModals() { // Close standard modals with display:none style document.querySelectorAll('[id$="Modal"]').forEach(modal => { if (modal.style && modal.style.display === 'block') { modal.style.display = 'none'; // If there's a specific close function, try to call it const modalId = modal.id; const closeFunction = window['close' + modalId.charAt(0).toUpperCase() + modalId.slice(1)]; if (typeof closeFunction === 'function') { closeFunction(); } else { // Reset body overflow as a fallback document.body.style.overflow = ''; } } }); // Explicitly handle the weak password modal - but only allow force close const weakPasswordModal = document.getElementById('weakPasswordModal'); if (weakPasswordModal && weakPasswordModal.style.display === 'block') { // Do not auto-close the weak password modal // This will be handled by the specific close function } // Close Bootstrap modals const bootstrapModals = document.querySelectorAll('.modal.show'); bootstrapModals.forEach(modal => { if (window.bootstrap && window.bootstrap.Modal) { const bsModal = bootstrap.Modal.getInstance(modal); if (bsModal) { bsModal.hide(); } } }); } // Set up direct event handlers without jQuery function setupDirectPasswordHandlers() { // Get references to the new password elements var passwordArea = document.getElementById('new_password_area'); var confirmArea = document.getElementById('confirm_password_area'); var lengthDisplay = document.getElementById('password-length-display'); var changeBtn = document.getElementById('password-change-btn'); var passwordForm = document.getElementById('password-form-div'); if (passwordArea && lengthDisplay) { // Focus on password field setTimeout(function() { passwordArea.focus(); }, 100); // Update function that directly reads the password field function updateLengthDisplay() { var password = passwordArea.value || ''; // Update display with current length lengthDisplay.textContent = 'Current length: ' + password.length + ' characters'; } // Add multiple event listeners to catch all input cases ['input', 'keyup', 'keydown', 'change', 'paste'].forEach(function(eventType) { passwordArea.addEventListener(eventType, updateLengthDisplay); }); // Add keydown event for Enter key passwordArea.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); // Focus on confirm field if Enter is pressed in password field confirmArea.focus(); } }); // Add keydown event for Enter key on confirm field confirmArea.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); // Trigger submit button click if Enter is pressed in confirm field if (changeBtn) { changeBtn.click(); } } }); // Initial update updateLengthDisplay(); } // Handle form submission (including Enter key) if (passwordForm) { passwordForm.addEventListener('submit', function(e) { e.preventDefault(); // Trigger the password change button click if (changeBtn) { changeBtn.click(); } }); } if (changeBtn) { changeBtn.addEventListener('click', function() { // Get the current password from the hidden field (more reliable) var currentPassword = document.getElementById('current_password_hidden').value || '123'; var newPassword = passwordArea.value || ''; var confirmPassword = confirmArea.value || ''; // Check for empty fields first if (!newPassword) { alert('New password is required.'); passwordArea.focus(); return; } if (!confirmPassword) { alert('Please confirm your password.'); confirmArea.focus(); return; } // Validate passwords if (newPassword.length < 8) { alert('Password must be at least 8 characters long! Your password is ' + newPassword.length + ' characters.'); passwordArea.focus(); return; } if (newPassword !== confirmPassword) { alert('Passwords do not match!'); confirmArea.focus(); return; } // Get CSRF token var csrfToken = document.querySelector('input[name="csrfmiddlewaretoken"]')?.value || ''; // Create form data var formData = new FormData(); formData.append('csrfmiddlewaretoken', csrfToken); formData.append('current_password', currentPassword); formData.append('new_password', newPassword); formData.append('confirm_password', confirmPassword); // Submit form fetch('/app/change-password/', { method: 'POST', body: formData, headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(response => response.json()) .then(function(data) { if (data.success) { // Show success message if (typeof window.showNotification === 'function') { window.showNotification('Password Updated', data.message || 'Your password has been changed successfully. Please login again.', 'success'); } else { alert(data.message || 'Password changed successfully. Please login again.'); } // Close the modal document.getElementById('weakPasswordModal')?.remove(); // Wait a moment for the notification to be visible setTimeout(function() { // Redirect to login page window.location.href = data.redirect || '/login/'; }, 1500); } else if (data.error) { // Show error notification if (typeof window.showNotification === 'function') { window.showNotification('Error', data.error, 'error'); } else { alert(data.error); } } }) .catch(error => { console.error('Error:', error); window.location.reload(); }); }); } } // Function to initialize responsive modals function initResponsiveModals() { // Function to update modal styles based on screen size function updateModalStyles() { const isMobile = window.innerWidth < 768; const isSmallMobile = window.innerWidth < 576; document.querySelectorAll('.modal-dialog').forEach(function(dialog) { if (isSmallMobile) { dialog.classList.add('modal-fullscreen-sm-down'); dialog.classList.remove('modal-dialog-centered'); } else if (isMobile) { dialog.classList.remove('modal-fullscreen-sm-down'); dialog.classList.add('modal-dialog-centered'); } else { dialog.classList.remove('modal-fullscreen-sm-down', 'modal-dialog-centered'); } }); } // Initial update updateModalStyles(); // Update on resize window.addEventListener('resize', debounce(updateModalStyles, 250)); // Update when modal is shown document.querySelectorAll('.modal').forEach(function(modal) { modal.addEventListener('show.bs.modal', updateModalStyles); }); } // Function to make all tables responsive function makeAllTablesResponsive() { document.querySelectorAll('table:not(.table-responsive)').forEach(function(table) { if (!table.parentElement.classList.contains('table-responsive')) { const wrapper = document.createElement('div'); wrapper.classList.add('table-responsive'); table.parentNode.insertBefore(wrapper, table); wrapper.appendChild(table); } }); } // Function to handle long content function handleLongContent() { const isMobile = window.innerWidth < 768; // Handle text truncation document.querySelectorAll('[data-truncate-mobile]').forEach(function(element) { if (isMobile) { const fullText = element.getAttribute('data-full-text') || element.textContent; const maxLength = parseInt(element.getAttribute('data-truncate-mobile'), 10) || 50; if (!element.getAttribute('data-full-text')) { element.setAttribute('data-full-text', fullText); } if (fullText.length > maxLength) { element.textContent = fullText.substring(0, maxLength) + '...'; } } else { // Restore full text on desktop if (element.getAttribute('data-full-text')) { element.textContent = element.getAttribute('data-full-text'); } } }); // Handle long tables document.querySelectorAll('.table-responsive').forEach(function(table) { if (isMobile) { table.style.maxHeight = '400px'; table.style.overflowY = 'auto'; } else { table.style.maxHeight = ''; table.style.overflowY = ''; } }); } // Debounce function to limit the rate at which a function can fire function debounce(func, wait) { let timeout; return function() { const context = this; const args = arguments; clearTimeout(timeout); timeout = setTimeout(function() { func.apply(context, args); }, wait); }; }