(function() { // DOM Elements const loadingIndicator = document.getElementById('loadingIndicator'); const errorContainer = document.getElementById('errorContainer'); const errorMessage = document.getElementById('errorMessage'); const errorDetails = document.getElementById('errorDetails'); const dashboardContent = document.getElementById('dashboardContent'); const refreshDashboardBtn = document.getElementById('refreshDashboardBtn'); // Elements specific to status page's own filtering/sorting UI - Activated const searchWarranties = document.getElementById('searchWarranties'); const statusFilter = document.getElementById('statusFilter'); const exportBtn = document.getElementById('exportBtn'); // sortableHeaders will be queried inside attachSortListeners // Configuration const STATUS_PAGE_API_BASE_URL = '/api/statistics'; const GLOBAL_STATUS_PAGE_API_BASE_URL = '/api/statistics/global'; const STATISTICS_API_URL = window.location.origin + STATUS_PAGE_API_BASE_URL; const GLOBAL_STATISTICS_API_URL = window.location.origin + GLOBAL_STATUS_PAGE_API_BASE_URL; let EXPIRING_SOON_DAYS = 30; // IIFE-local variables let currentSort = { column: 'expiration_date', direction: 'asc' }; let allWarranties = []; let statusChart = null; let timelineChart = null; let currentStatusData = null; let currentTimelineData = null; let userCurrencySymbol = '$'; // Default currency symbol let isGlobalView = false; // Track current view mode let isViewControlsInitialized = false; let isDashboardInitialized = false; // Prevent multiple initializations let isDOMHandlerAttached = false; // Prevent multiple DOM handlers let initDashboardPromise = null; // Track ongoing initialization function setTheme(isDark) { const theme = isDark ? 'dark' : 'light'; console.log('Setting theme from status.js IIFE to:', theme); document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('darkMode', isDark); // The visual state of any shared header toggle is handled by other scripts (e.g., script.js or auth.js) } function formatDateYYYYMMDD(date) { if (!date || !(date instanceof Date) || isNaN(date.getTime())) { return 'N/A'; } const year = date.getUTCFullYear(); const month = String(date.getUTCMonth() + 1).padStart(2, '0'); const day = String(date.getUTCDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } function redrawChartsWithNewTheme() { console.log("Theme changed, redrawing charts from status.js IIFE..."); try { if (statusChart && typeof statusChart.destroy === 'function') { statusChart.destroy(); statusChart = null; console.log('Destroyed status chart for theme change'); } if (timelineChart && typeof timelineChart.destroy === 'function') { timelineChart.destroy(); timelineChart = null; console.log('Destroyed timeline chart for theme change'); } if (currentStatusData) { createStatusChart(currentStatusData); } if (currentTimelineData) { createTimelineChart(currentTimelineData); } } catch (e) { console.error('Error redrawing charts with new theme:', e); } } function showLoading() { if (loadingIndicator) loadingIndicator.classList.add('active'); if (dashboardContent) dashboardContent.style.opacity = '0.5'; } function hideLoading() { if (loadingIndicator) loadingIndicator.classList.remove('active'); if (dashboardContent) dashboardContent.style.opacity = '1'; } function showError(message, details = '') { if (errorMessage) errorMessage.textContent = message; if (errorDetails) errorDetails.textContent = details; if (errorContainer) errorContainer.style.display = 'block'; if (dashboardContent) dashboardContent.style.display = 'none'; } function hideError() { if (errorContainer) errorContainer.style.display = 'none'; if (dashboardContent) dashboardContent.style.display = 'block'; } async function fetchStatistics() { try { console.log('Checking authentication status... (status.js IIFE)'); if (!window.auth || !window.auth.isAuthenticated()) { throw new Error('Authentication required.'); } const token = window.auth.getToken(); if (!token) { throw new Error('Authentication token not available.'); } const options = { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json'} }; // Check saved view scope preference to determine which API endpoint to use const savedScope = loadViewScopePreference(); const shouldUseGlobalView = savedScope === 'global'; // Choose API endpoint based on saved preference const apiUrl = shouldUseGlobalView ? GLOBAL_STATISTICS_API_URL : STATISTICS_API_URL; console.log('Fetching statistics from:', apiUrl, '(Global view preference:', savedScope, ')'); const response = await fetch(apiUrl, options); if (!response.ok) { const errorText = await response.text(); throw new Error(`Failed to fetch statistics: ${response.status} ${errorText}`); } // Update isGlobalView to match the loaded data isGlobalView = shouldUseGlobalView; console.log(`[DEBUG] Set isGlobalView to: ${isGlobalView}`); return await response.json(); } catch (error) { console.error('Error fetching statistics (status.js IIFE):', error); throw error; } } function showToast(message, type = 'info') { const toastContainer = document.getElementById('toastContainer'); if (!toastContainer) return; const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.innerHTML = `${message} `; const closeButton = toast.querySelector('.toast-close'); if (closeButton) { closeButton.addEventListener('click', () => toast.remove()); } toastContainer.appendChild(toast); setTimeout(() => { if (toast.parentElement) toast.remove(); }, 3000); } // Global view functions async function initViewControls() { if (isViewControlsInitialized) return; try { // Check if global view is enabled for this user const token = window.auth.getToken(); if (!token) return; const response = await fetch('/api/settings/global-view-status', { method: 'GET', headers: { 'Authorization': `Bearer ${token}` } }); if (response.ok) { const result = await response.json(); if (result.enabled) { showViewSwitcher(); setupViewSwitcherListeners(); // Load and apply saved view scope preference const savedScope = loadViewScopePreference(); if (savedScope === 'global') { // Apply global view silently without saving preference again isGlobalView = true; updateViewButtons(); updateDashboardTitle(); updateTableColumns(); } else { // Apply personal view (default) isGlobalView = false; updateViewButtons(); updateDashboardTitle(); updateTableColumns(); } isViewControlsInitialized = true; } } } catch (error) { console.error('Error checking global view status:', error); // Default to showing view switcher if error occurs (for admins) if (getUserType() === 'admin') { showViewSwitcher(); setupViewSwitcherListeners(); // Load and apply saved view scope preference const savedScope = loadViewScopePreference(); if (savedScope === 'global') { // Apply global view silently without saving preference again isGlobalView = true; updateViewButtons(); updateDashboardTitle(); updateTableColumns(); } else { // Apply personal view (default) isGlobalView = false; updateViewButtons(); updateDashboardTitle(); updateTableColumns(); } isViewControlsInitialized = true; } } } function getUserType() { try { const userInfo = JSON.parse(localStorage.getItem('user_info') || '{}'); return userInfo.is_admin ? 'admin' : 'user'; } catch { return 'user'; } } /** * Get the appropriate localStorage key prefix based on user type * @returns {string} The prefix to use for localStorage keys */ function getPreferenceKeyPrefix() { return getUserType() === 'admin' ? 'admin_' : 'user_'; } /** * Save view scope preference to localStorage * @param {string} scope - 'personal' or 'global' */ function saveViewScopePreference(scope) { try { const prefix = getPreferenceKeyPrefix(); localStorage.setItem(`${prefix}viewScope`, scope); console.log(`Saved view scope preference: ${scope} with prefix: ${prefix}`); } catch (error) { console.error('Error saving view scope preference:', error); } } /** * Load view scope preference from localStorage * @returns {string} The saved preference ('personal', 'global', or 'personal' as default) */ function loadViewScopePreference() { try { const prefix = getPreferenceKeyPrefix(); const savedScope = localStorage.getItem(`${prefix}viewScope`); console.log(`Loaded view scope preference: ${savedScope} with prefix: ${prefix}`); return savedScope || 'personal'; // Default to personal view } catch (error) { console.error('Error loading view scope preference:', error); return 'personal'; // Default to personal view on error } } // --- Status filter persistence for Status Page --- function saveStatusFilterPreference(value) { try { const prefix = getPreferenceKeyPrefix(); const key = `${prefix}statusPageStatusFilter`; if (!value || value === 'all') { // Treat 'all' as reset; clear saved preference localStorage.removeItem(key); console.log(`Cleared saved status filter (key=${key})`); } else { localStorage.setItem(key, value); console.log(`Saved status filter '${value}' (key=${key})`); } } catch (error) { console.error('Error saving status filter preference:', error); } } function loadStatusFilterPreference() { try { const prefix = getPreferenceKeyPrefix(); const key = `${prefix}statusPageStatusFilter`; const saved = localStorage.getItem(key); console.log(`Loaded status filter preference: ${saved} (key=${key})`); return saved || 'all'; } catch (error) { console.error('Error loading status filter preference:', error); return 'all'; } } function showViewSwitcher() { const viewSwitcher = document.getElementById('viewSwitcher'); if (viewSwitcher) { viewSwitcher.style.display = 'flex'; } } function hideViewSwitcher() { const viewSwitcher = document.getElementById('viewSwitcher'); if (viewSwitcher) { viewSwitcher.style.display = 'none'; } } function setupViewSwitcherListeners() { const personalViewBtn = document.getElementById('personalViewBtn'); const globalViewBtn = document.getElementById('globalViewBtn'); if (personalViewBtn) { personalViewBtn.addEventListener('click', () => switchToPersonalView()); } if (globalViewBtn) { globalViewBtn.addEventListener('click', () => switchToGlobalView()); } } async function switchToPersonalView() { if (!isGlobalView) return; // Already in personal view isGlobalView = false; updateViewButtons(); updateDashboardTitle(); updateTableColumns(); // Save view preference saveViewScopePreference('personal'); isDashboardInitialized = false; // Reset to allow refresh with new view await initDashboard(); } async function switchToGlobalView() { if (isGlobalView) return; // Already in global view try { // Check if global view is still available const token = window.auth.getToken(); const response = await fetch('/api/settings/global-view-status', { method: 'GET', headers: { 'Authorization': `Bearer ${token}` } }); if (response.ok) { const result = await response.json(); if (result.enabled) { isGlobalView = true; updateViewButtons(); updateDashboardTitle(); updateTableColumns(); // Save view preference saveViewScopePreference('global'); isDashboardInitialized = false; // Reset to allow refresh with new view await initDashboard(); } else { showToast('Global view is not available', 'error'); // Switch back to personal view if global is disabled await switchToPersonalView(); } } else { throw new Error('Failed to check global view status'); } } catch (error) { console.error('Error switching to global view:', error); showToast('Unable to switch to global view', 'error'); // Switch back to personal view on error await switchToPersonalView(); } } function updateViewButtons() { const personalViewBtn = document.getElementById('personalViewBtn'); const globalViewBtn = document.getElementById('globalViewBtn'); if (personalViewBtn && globalViewBtn) { if (isGlobalView) { personalViewBtn.classList.remove('active'); globalViewBtn.classList.add('active'); } else { personalViewBtn.classList.add('active'); globalViewBtn.classList.remove('active'); } } } function updateDashboardTitle() { const dashboardTitle = document.getElementById('dashboardTitle'); if (dashboardTitle) { if (window.i18next && window.i18next.t) { dashboardTitle.textContent = isGlobalView ? window.i18next.t('status.global_dashboard_title') : window.i18next.t('status.dashboard_title'); } else { dashboardTitle.textContent = isGlobalView ? 'Global Warranty Status Dashboard' : 'Warranty Status Dashboard'; } } } function updateTableColumns() { const ownerHeader = document.getElementById('ownerHeader'); if (ownerHeader) { ownerHeader.style.display = isGlobalView ? 'table-cell' : 'none'; } } function updateSummaryCounts(statusData) { const totalEl = document.getElementById('totalCount'); const activeEl = document.getElementById('activeCount'); const expiringEl = document.getElementById('expiringCount'); const expiredEl = document.getElementById('expiredCount'); if (totalEl) totalEl.textContent = statusData.total || 0; if (activeEl) activeEl.textContent = statusData.active || 0; if (expiringEl) expiringEl.textContent = statusData.expiring_soon || 0; if (expiredEl) expiredEl.textContent = statusData.expired || 0; } function createStatusChart(stats) { const ctxEl = document.getElementById('statusChart'); if (!ctxEl) { console.warn("statusChart canvas not found"); return; } // Properly destroy existing chart if (statusChart && typeof statusChart.destroy === 'function') { try { statusChart.destroy(); statusChart = null; console.log('Destroyed existing status chart'); } catch (e) { console.warn('Error destroying status chart:', e); statusChart = null; } } // Additional check: Clear any Chart.js instances on this canvas const chartInstance = Chart.getChart(ctxEl); if (chartInstance) { console.log('Found existing Chart.js instance on statusChart canvas, destroying it'); chartInstance.destroy(); } const ctx = ctxEl.getContext('2d'); if (!stats || typeof stats !== 'object') stats = { active: 0, expiring_soon: 0, expired: 0, total: 0 }; // Added total for safety const active = stats.active || 0; const expiringSoon = stats.expiring_soon || 0; const expired = stats.expired || 0; const trulyActive = Math.max(0, active - expiringSoon); try { // Get translated labels for chart const activeLabel = i18next.t('warranties.active'); const expiringSoonLabel = i18next.t('warranties.expiring_soon'); const expiredLabel = i18next.t('warranties.expired'); statusChart = new Chart(ctx, { type: 'doughnut', data: { labels: [activeLabel, expiringSoonLabel, expiredLabel], datasets: [{ data: [trulyActive, expiringSoon, expired], backgroundColor: ['#4CAF50', '#FF9800', '#F44336'], borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } } }); console.log('Created new status chart'); } catch (e) { console.error('Error creating status chart:', e); } } function createTimelineChart(timelineData) { const ctxEl = document.getElementById('timelineChart'); if (!ctxEl) { console.warn("timelineChart canvas not found"); return; } // Properly destroy existing chart if (timelineChart && typeof timelineChart.destroy === 'function') { try { timelineChart.destroy(); timelineChart = null; console.log('Destroyed existing timeline chart'); } catch (e) { console.warn('Error destroying timeline chart:', e); timelineChart = null; } } // Additional check: Clear any Chart.js instances on this canvas const chartInstance = Chart.getChart(ctxEl); if (chartInstance) { console.log('Found existing Chart.js instance on timelineChart canvas, destroying it'); chartInstance.destroy(); } const ctx = ctxEl.getContext('2d'); let labels = []; let counts = []; if (!Array.isArray(timelineData) || timelineData.length === 0) { console.warn('Timeline data is empty or not an array. Displaying default empty chart.'); const currentDate = new Date(); for (let i = 2; i >= 0; i--) { // Last 3 months const d = new Date(currentDate.getFullYear(), currentDate.getMonth() - i, 1); labels.push(d.toLocaleDateString(undefined, { month: 'short', year: 'numeric' })); counts.push(0); } } else { labels = timelineData.map(item => { try { let year, monthVal; if (item.year !== undefined && item.month !== undefined) { year = item.year; monthVal = item.month - 1; // JS month is 0-indexed } else if (item.date) { // Assuming item.date is a parsable date string const d = new Date(item.date); year = d.getFullYear(); monthVal = d.getMonth(); } else { // Fallback if structure is unknown const d = new Date(); year = d.getFullYear(); monthVal = d.getMonth(); } return new Date(year, monthVal, 1).toLocaleDateString(undefined, { month: 'short', year: 'numeric' }); } catch (e) { console.error("Error formatting timeline label item:", item, e); return 'Unknown'; } }); counts = timelineData.map(item => item.count || 0); } try { // Get translated label for timeline chart const timelineLabel = i18next.t('status.expiration_timeline'); timelineChart = new Chart(ctx, { type: 'bar', data: { labels: labels, datasets: [{ label: timelineLabel, data: counts, backgroundColor: '#3498db', borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { precision: 0 } } } } }); console.log('Created new timeline chart'); } catch (e) { console.error('Error creating timeline chart:', e); } } function updateRecentExpirations(recentWarrantiesData) { if (!Array.isArray(recentWarrantiesData)) { console.error('Recent warranties data is not an array (status.js IIFE):', recentWarrantiesData); allWarranties = []; } else { allWarranties = recentWarrantiesData.map(warranty => ({ id: warranty.id || String(Math.random()).slice(2, 11), // Ensure ID is a string for consistency product_name: warranty.product_name || warranty.name || 'Unknown Product', purchase_date: warranty.purchase_date, expiration_date: warranty.expiration_date, is_lifetime: warranty.is_lifetime || false, invoice_path: warranty.invoice_path || null, product_url: warranty.product_url, purchase_price: warranty.purchase_price, vendor: warranty.vendor, model_number: warranty.model_number, // include model number for searching serial_numbers: warranty.serial_numbers || [], notes: warranty.notes, manual_path: warranty.manual_path || null, other_document_path: warranty.other_document_path || null, is_archived: !!warranty.is_archived, // Add user fields for global view user_display_name: warranty.user_display_name, username: warranty.username, first_name: warranty.first_name, last_name: warranty.last_name })); } filterAndSortWarranties(); // This will render the table } function filterAndSortWarranties() { const tableBody = document.getElementById('recentExpirationsBody'); if (!tableBody) { console.warn("recentExpirationsBody not found for rendering warranties."); return; } const currentSearchTerm = searchWarranties && searchWarranties.value ? searchWarranties.value.toLowerCase() : ''; const currentStatusValue = statusFilter && statusFilter.value ? statusFilter.value : 'all'; tableBody.innerHTML = ''; if (!allWarranties || allWarranties.length === 0) { const colspan = isGlobalView ? 5 : 4; const noWarrantiesMessage = i18next.t('status.recent_expirations_empty', 'No recently expired or expiring warranties.'); tableBody.innerHTML = `${noWarrantiesMessage}`; return; } const today = new Date(); today.setHours(0,0,0,0); // Normalize today to start of day for consistent comparisons let displayWarranties = allWarranties.filter(w => { const productName = (w.product_name || '').toLowerCase(); const modelNumber = (w.model_number || '').toLowerCase(); if (currentSearchTerm && !(productName.includes(currentSearchTerm) || modelNumber.includes(currentSearchTerm))) return false; // Explicit archived filter if (currentStatusValue === 'archived') { return !!w.is_archived; } // Exclude archived from other filters if (w.is_archived) { return currentStatusValue === 'all'; } if (w.is_lifetime) { if (currentStatusValue === 'expired' || currentStatusValue === 'expiring') return false; return true; // Included in 'all' and 'active' } if (!w.expiration_date) return false; // Non-lifetime must have an expiration date const expirationDate = new Date(w.expiration_date); expirationDate.setHours(0,0,0,0); // Normalize for comparison if (currentStatusValue === 'all') return true; if (currentStatusValue === 'expired') return expirationDate <= today; const timeDiff = expirationDate - today; const daysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); if (currentStatusValue === 'expiring') return expirationDate > today && daysDiff <= EXPIRING_SOON_DAYS; if (currentStatusValue === 'active') return expirationDate > today && daysDiff > EXPIRING_SOON_DAYS; return true; // Should ideally not be reached if statusValue is one of the handled ones }); displayWarranties.sort((a, b) => { let valA, valB; const aIsLifetime = a.is_lifetime; const bIsLifetime = b.is_lifetime; // Prioritize lifetime sort or handle them based on specific column if (currentSort.column === 'status') { valA = getStatusPriority(a.expiration_date, today, aIsLifetime, !!a.is_archived); valB = getStatusPriority(b.expiration_date, today, bIsLifetime, !!b.is_archived); } else { if (aIsLifetime && !bIsLifetime) return -1; if (!aIsLifetime && bIsLifetime) return 1; if (aIsLifetime && bIsLifetime) { valA = (a.product_name || '').toLowerCase(); valB = (b.product_name || '').toLowerCase(); } else { switch (currentSort.column) { case 'product': valA = (a.product_name || '').toLowerCase(); valB = (b.product_name || '').toLowerCase(); break; case 'purchase': valA = new Date(a.purchase_date || 0); valB = new Date(b.purchase_date || 0); break; default: valA = new Date(a.expiration_date || 0); valB = new Date(b.expiration_date || 0); // Default is expiration } } } if (valA instanceof Date && valB instanceof Date) return currentSort.direction === 'asc' ? valA.getTime() - valB.getTime() : valB.getTime() - valA.getTime(); if (typeof valA === 'number' && typeof valB === 'number') return currentSort.direction === 'asc' ? valA - valB : valB - valA; if (typeof valA === 'string' && typeof valB === 'string') return currentSort.direction === 'asc' ? valA.localeCompare(valB) : valB.localeCompare(valA); return 0; }); if (displayWarranties.length === 0) { const colspan = isGlobalView ? 5 : 4; const noMatchMessage = i18next.t('messages.no_results', 'No results found'); tableBody.innerHTML = `${noMatchMessage}`; return; } displayWarranties.forEach(warranty => { const row = tableBody.insertRow(); row.setAttribute('data-warranty-id', String(warranty.id)); // Ensure ID is string for dataset row.style.cursor = 'pointer'; let statusText, statusClass; const todayForStatus = new Date(); todayForStatus.setHours(0,0,0,0); // Archived takes precedence: show as Archived regardless of expiration if (warranty.is_archived) { statusText = i18next.t('warranties.archived', 'Archived'); statusClass = 'status-archived'; } else if (warranty.is_lifetime) { statusText = i18next.t('warranties.lifetime'); statusClass = 'status-lifetime'; } else { const expirationDate = new Date(warranty.expiration_date); expirationDate.setHours(0,0,0,0); if (expirationDate <= todayForStatus) { statusText = i18next.t('warranties.expired'); statusClass = 'status-expired'; } else { const timeDiff = expirationDate - todayForStatus; const daysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); if (daysDiff <= EXPIRING_SOON_DAYS) { statusText = i18next.t('warranties.expiring_soon'); statusClass = 'status-expiring'; } else { statusText = i18next.t('warranties.active'); statusClass = 'status-active'; } } } row.className = statusClass; // Build row HTML based on current view mode const lifetimeText = i18next.t('warranties.lifetime'); const naText = i18next.t('warranties.na', 'N/A'); let rowHTML = ` ${escapeHTML(warranty.product_name)} ${warranty.purchase_date ? formatDateYYYYMMDD(new Date(warranty.purchase_date)) : naText} ${warranty.is_lifetime ? lifetimeText : (warranty.expiration_date ? formatDateYYYYMMDD(new Date(warranty.expiration_date)) : naText)} ${statusText} `; // Add owner column if in global view if (isGlobalView) { const ownerDisplay = warranty.user_display_name || warranty.username || 'Unknown User'; rowHTML += `${escapeHTML(ownerDisplay)}`; } row.innerHTML = rowHTML; // Make sure escapeHTML is used on all string data from warranty row.addEventListener('click', () => toggleWarrantyDetails(warranty.id, row)); }); } function getStatusPriority(expirationDateStr, today, isLifetime = false, isArchived = false) { if (isArchived) return -1; // Archived comes first (or last depending on sort direction) if (isLifetime) return 0; if (!expirationDateStr) return 4; const expirationDate = new Date(expirationDateStr); expirationDate.setHours(0,0,0,0); const todayNormalized = new Date(today); // Ensure today is also normalized if not already todayNormalized.setHours(0,0,0,0); if (isNaN(expirationDate.getTime())) return 4; if (expirationDate <= todayNormalized) return 3; const timeDiff = expirationDate - todayNormalized; const daysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); if (daysDiff <= EXPIRING_SOON_DAYS) return 2; return 1; } async function toggleWarrantyDetails(warrantyId, clickedRow, forceRefetch = false) { const existingDetailsRow = clickedRow.nextElementSibling; if (existingDetailsRow && existingDetailsRow.classList.contains('warranty-details-row')) { existingDetailsRow.remove(); clickedRow.classList.remove('details-expanded'); return; } document.querySelectorAll('.warranty-details-row').forEach(r => r.remove()); document.querySelectorAll('tr.details-expanded').forEach(r => r.classList.remove('details-expanded')); clickedRow.classList.add('details-expanded'); showLoading(); try { let warrantyDetails = null; if (!forceRefetch) { warrantyDetails = allWarranties.find(w => String(w.id) === String(warrantyId)); } // If forcing a refetch, or if cache lookup failed, or if cached data is deemed incomplete (e.g., missing notes) if (forceRefetch || !warrantyDetails || !warrantyDetails.notes) { if (forceRefetch) { console.log(`[toggleWarrantyDetails] Force refetching details for warranty ID: ${warrantyId}`); } else if (!warrantyDetails) { console.warn(`[toggleWarrantyDetails] Warranty ID: ${warrantyId} not found in local cache. Fetching.`); } else { // Implies !warrantyDetails.notes or other incompleteness checks in future console.warn(`[toggleWarrantyDetails] Warranty details for ID: ${warrantyId} in cache are incomplete. Fetching.`); } const token = window.auth.getToken(); if (!token) throw new Error('Authentication token not available.'); const response = await fetch(`/api/debug/warranty/${warrantyId}`, { headers: { 'Authorization': `Bearer ${token}` }}); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || 'Failed to fetch full warranty details for toggle.'); } const fetchedData = await response.json(); if (!fetchedData) throw new Error('Warranty data not found in fetch response for toggle.'); // API might return { warranty: {...} } or just {...} warrantyDetails = fetchedData.warranty ? fetchedData.warranty : fetchedData; // Update allWarranties cache in status.js with this fresh data const index = allWarranties.findIndex(w => String(w.id) === String(warrantyId)); if (index !== -1) { // Merge to preserve any local-only properties if necessary, though fetched should be canonical here allWarranties[index] = {...allWarranties[index], ...warrantyDetails}; console.log(`[toggleWarrantyDetails] Updated allWarranties cache for ID ${warrantyId} with fetched details.`); } else { allWarranties.push(warrantyDetails); // Should be rare if initDashboard ran console.log(`[toggleWarrantyDetails] Added fetched details for ID ${warrantyId} to allWarranties cache as it was not found.`); } } // At this point, warrantyDetails should be the one we want to render. // If forceRefetch was true, it's the data just fetched. // If forceRefetch was false, it's from cache. console.log('[toggleWarrantyDetails] FINAL warrantyDetails object to be rendered:', JSON.parse(JSON.stringify(warrantyDetails))); // Normalize paths in warrantyDetails, regardless of source (cache or fetch) warrantyDetails.invoice_path = warrantyDetails.invoice_path || null; warrantyDetails.manual_path = warrantyDetails.manual_path || null; warrantyDetails.other_document_path = warrantyDetails.other_document_path || null; const detailsRow = document.createElement('tr'); detailsRow.classList.add('warranty-details-row'); const detailsCell = document.createElement('td'); detailsCell.colSpan = clickedRow.cells.length; detailsCell.style.padding = '15px'; detailsCell.style.backgroundColor = 'var(--details-bg, #f9f9f9)'; let dHtml = '
'; dHtml += '

Core Information

'; dHtml += `

Product URL: ${warrantyDetails.product_url ? `${escapeHTML(warrantyDetails.product_url)}` : 'N/A'}

`; dHtml += `

Purchase Price: ${warrantyDetails.purchase_price !== null && warrantyDetails.purchase_price !== undefined ? escapeHTML(userCurrencySymbol) + parseFloat(warrantyDetails.purchase_price).toFixed(2) : 'N/A'}

`; dHtml += `

Vendor: ${escapeHTML(warrantyDetails.vendor || '') || 'N/A'}

`; dHtml += `

Warranty Type: ${escapeHTML(warrantyDetails.warranty_type || '') || 'N/A'}

`; dHtml += '

Documents & Files

'; // Invoice - check both local and Paperless-ngx if(warrantyDetails.invoice_path) { dHtml += `

Invoice: View Invoice

`; } else if(warrantyDetails.paperless_invoice_id) { dHtml += `

Invoice: View Invoice

`; } else { dHtml += `

Invoice: N/A

`; } // Manual - check both local and Paperless-ngx if(warrantyDetails.manual_path) { dHtml += `

Manual: View Manual

`; } else if(warrantyDetails.paperless_manual_id) { dHtml += `

Manual: View Manual

`; } else { dHtml += `

Manual: N/A

`; } // Other Files - check both local and Paperless-ngx if(warrantyDetails.other_document_path) { dHtml += `

Other Files: View Files

`; } else if(warrantyDetails.paperless_other_id) { dHtml += `

Other Files: View Files

`; } else { dHtml += `

Other Files: N/A

`; } dHtml += '
'; // DEBUG: Log serial numbers before rendering them in details view console.log('[toggleWarrantyDetails] Serial numbers in warrantyDetails before rendering HTML:', JSON.parse(JSON.stringify(warrantyDetails.serial_numbers || []))); if (warrantyDetails.serial_numbers && warrantyDetails.serial_numbers.length > 0) { dHtml += '

Serial Numbers

'; } if (warrantyDetails.notes && String(warrantyDetails.notes).trim() !== '') { dHtml += '

Notes

'; dHtml += `
${escapeHTML(warrantyDetails.notes)}
`; } dHtml += '
'; // end warranty-details-content dHtml += '
'; dHtml += `
`; detailsCell.innerHTML = dHtml; detailsRow.appendChild(detailsCell); clickedRow.parentNode.insertBefore(detailsRow, clickedRow.nextSibling); } catch (error) { console.error('Error in toggleWarrantyDetails (status.js IIFE):', error); showToast(`Error: ${error.message}`, 'error'); } finally { hideLoading(); } } function escapeHTML(str) { if (str === null || str === undefined) return ''; return String(str).replace(/[&<>"'\/]/g, s => ({'&':'&','<':'<','>':'>','"':'"',"'":''','/':'/'})[s]); } function refreshDashboard() { console.log("Refreshing dashboard from status.js IIFE..."); isDashboardInitialized = false; // Reset flag to allow refresh initDashboardPromise = null; // Clear any existing promise if (refreshDashboardBtn) refreshDashboardBtn.classList.add('loading'); initDashboard().finally(() => { if (refreshDashboardBtn) refreshDashboardBtn.classList.remove('loading'); }); } async function initDashboard() { // If already initialized and not being refreshed, skip if (isDashboardInitialized && !initDashboardPromise) { console.log('Dashboard already initialized, skipping...'); return; } // If currently initializing, wait for the existing promise if (initDashboardPromise) { console.log('Dashboard initialization already in progress, waiting...'); return initDashboardPromise; } console.log('Initializing dashboard (status.js IIFE)...'); // Create promise to track initialization initDashboardPromise = (async () => { try { showLoading(); await loadUserPreferences(); await initViewControls(); // Initialize view controls before fetching data const data = await fetchStatistics(); hideError(); // 1. Use data.all_warranties for the main table content updateRecentExpirations(data.all_warranties || []); // 2. Construct the status_distribution object for charts and summary const statusDistributionData = { active: data.active || 0, expiring_soon: data.expiring_soon || 0, expired: data.expired || 0, total: data.total || 0 }; if (Object.keys(statusDistributionData).length > 0 && statusDistributionData.total > 0) { currentStatusData = statusDistributionData; updateSummaryCounts(statusDistributionData); createStatusChart(statusDistributionData); } else { console.warn("Status distribution data from API is incomplete or zero. Displaying empty/default chart/summary."); updateSummaryCounts({}); createStatusChart({}); } // --- BEGIN: Expiration Timeline Chart Fix --- // Instead of using only API timeline, generate a comprehensive timeline from all warranties let allWarrantiesForTimeline = []; try { // Try to fetch all warranties for a complete timeline const token = window.auth && window.auth.getToken ? window.auth.getToken() : null; if (token) { const allWarrantiesResponse = await fetch('/api/warranties', { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (allWarrantiesResponse.ok) { allWarrantiesForTimeline = await allWarrantiesResponse.json(); } else { console.warn('Could not fetch all warranties for timeline, falling back to data.all_warranties'); allWarrantiesForTimeline = data.all_warranties || []; } } else { allWarrantiesForTimeline = data.all_warranties || []; } } catch (err) { console.error('Error fetching all warranties for timeline:', err); allWarrantiesForTimeline = data.all_warranties || []; } // Helper: Extract timeline data from all warranties function extractTimelineData(warranties) { // Map: { 'YYYY-MM': count } const timelineMap = {}; warranties.forEach(w => { if (w.is_lifetime) return; // Skip lifetime warranties if (!w.expiration_date) return; let expDate = w.expiration_date; if (typeof expDate === 'string') { // Accept both 'YYYY-MM-DD' and ISO expDate = expDate.split('T')[0]; const [year, month] = expDate.split('-'); if (year && month) { const key = `${year}-${month}`; timelineMap[key] = (timelineMap[key] || 0) + 1; } } else if (expDate instanceof Date && !isNaN(expDate.getTime())) { const year = expDate.getFullYear(); const month = (expDate.getMonth() + 1).toString().padStart(2, '0'); const key = `${year}-${month}`; timelineMap[key] = (timelineMap[key] || 0) + 1; } }); // Convert to array sorted by date ascending const timelineArr = Object.entries(timelineMap) .map(([key, count]) => { const [year, month] = key.split('-'); return { year: parseInt(year), month: parseInt(month), count }; }) .sort((a, b) => (a.year !== b.year) ? a.year - b.year : a.month - b.month); return timelineArr; } let timelineData = []; if (Array.isArray(allWarrantiesForTimeline) && allWarrantiesForTimeline.length > 0) { timelineData = extractTimelineData(allWarrantiesForTimeline); } if (timelineData.length > 0) { currentTimelineData = timelineData; createTimelineChart(timelineData); } else if (data.timeline && Array.isArray(data.timeline)) { // Fallback to API timeline if extraction fails currentTimelineData = data.timeline; createTimelineChart(data.timeline); } else { createTimelineChart([]); } // --- END: Expiration Timeline Chart Fix --- isDashboardInitialized = true; } catch (error) { console.error('Failed to initialize dashboard (status.js IIFE):', error); showError('Failed to load dashboard data.', error.message); isDashboardInitialized = false; // Allow retry on error } finally { hideLoading(); initDashboardPromise = null; // Clear the promise } })(); return initDashboardPromise; } async function loadUserPreferences() { try { if (!window.auth || !window.auth.getToken()) { console.warn('Auth or token not available for prefs in status.js'); return; } const token = window.auth.getToken(); const response = await fetch('/api/auth/preferences', { headers: { 'Authorization': `Bearer ${token}` } }); if (response.ok) { const prefs = await response.json(); if (prefs && prefs.expiring_soon_days !== undefined) { // Check for undefined specifically EXPIRING_SOON_DAYS = parseInt(prefs.expiring_soon_days, 10); console.log('Loaded EXPIRING_SOON_DAYS from prefs (status.js IIFE):', EXPIRING_SOON_DAYS); } if (prefs && prefs.currency_symbol) { userCurrencySymbol = prefs.currency_symbol; console.log('Loaded currency_symbol from prefs (status.js IIFE):', userCurrencySymbol); } } else { console.warn("Failed to load user preferences in status.js, status:", response.status); } } catch (error) { console.error('Error loading user preferences (status.js IIFE):', error); } } function attachSortListeners() { // DEFINITION ENSURED INSIDE IIFE const headers = document.querySelectorAll('#recentExpirationsTable th.sortable'); headers.forEach(header => { const newHeader = header.cloneNode(true); // Re-clone to ensure clean listeners if (header.parentNode) { header.parentNode.replaceChild(newHeader, header); } else { console.warn("Header's parentNode is null, cannot attach sort listener:", header); return; // Skip if header isn't properly in DOM } newHeader.addEventListener('click', () => { const column = newHeader.getAttribute('data-sort'); if (currentSort.column === column) { currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc'; } else { currentSort.column = column; currentSort.direction = 'asc'; } // TODO: Implement updateSortHeaderClasses() to visually show sort direction filterAndSortWarranties(); }); }); console.log("Attached sort listeners (status.js IIFE). Found headers:", headers.length); } document.addEventListener('click', async function(event) { if (event.target && event.target.classList.contains('edit-warranty-status-btn')) { const warrantyId = event.target.dataset.warrantyId; if (!warrantyId) { showToast('Cannot edit warranty: ID is missing.', 'error'); return; } console.log(`Edit button clicked for warranty ID (status.js IIFE): ${warrantyId}`); showLoading(); let finalWarrantyForModal = null; try { // ALWAYS fetch fresh details when editing from the status page to ensure modal has latest data. console.log(`[DEBUG status.js] Edit from status page for warranty ${warrantyId}. ALWAYS fetching fresh details from /api/debug/warranty/:id.`); showLoadingSpinner(); // Show spinner for this specific fetch const token = localStorage.getItem('auth_token') || (window.auth && window.auth.getToken()); if (!token) throw new Error('Authentication token not available for fetching fresh details.'); const response = await fetch(`/api/debug/warranty/${warrantyId}`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.message || `Failed to fetch fresh warranty details: ${response.status}`); } let freshlyFetchedWarranty = await response.json(); // IMPORTANT: Check if the API nests the warranty object (e.g., under a 'warranty' key) if (freshlyFetchedWarranty && freshlyFetchedWarranty.warranty && typeof freshlyFetchedWarranty.warranty === 'object') { freshlyFetchedWarranty = freshlyFetchedWarranty.warranty; } hideLoadingSpinner(); console.log(`[DEBUG status.js] Freshly fetched warranty data (from /api/debug/warranty):`, JSON.parse(JSON.stringify(freshlyFetchedWarranty))); // Start with freshly fetched data as the base finalWarrantyForModal = { ...freshlyFetchedWarranty }; // Attempt to get from cache just for potentially more complete local-only properties if needed (e.g. if API is sometimes slow to update derived fields) // This is a cautious approach; ideally, freshlyFetchedWarranty is canonical. let cachedWarranty = allWarranties.find(w => String(w.id) === String(warrantyId)); if (cachedWarranty) { console.log(`[DEBUG status.js] Also found cached warranty for ID ${warrantyId}:`, JSON.parse(JSON.stringify(cachedWarranty))); // Merge tags: Prioritize tags from cachedWarranty if available and fresh data lacks them or has empty if (Array.isArray(cachedWarranty.tags) && cachedWarranty.tags.length > 0 && (!finalWarrantyForModal.hasOwnProperty('tags') || (Array.isArray(finalWarrantyForModal.tags) && finalWarrantyForModal.tags.length === 0))) { console.log(`[DEBUG status.js] Using tags from cachedWarranty. Tags from cache:`, cachedWarranty.tags); finalWarrantyForModal.tags = cachedWarranty.tags; } else if (!finalWarrantyForModal.hasOwnProperty('tags') || !Array.isArray(finalWarrantyForModal.tags)) { console.log(`[DEBUG status.js] No valid 'tags' property in fresh or cached. Defaulting to empty array. Current final.tags:`, finalWarrantyForModal.tags); finalWarrantyForModal.tags = []; } // Merge/Format serial_numbers: Prioritize cached if simple array and fresh data has objects or is missing. // This logic might need refinement based on consistent API output for serials. const cachedSerialsAreSimpleArray = Array.isArray(cachedWarranty.serial_numbers) && (cachedWarranty.serial_numbers.length === 0 || typeof cachedWarranty.serial_numbers[0] === 'string'); if (cachedSerialsAreSimpleArray && cachedWarranty.serial_numbers.length > 0 && (!Array.isArray(finalWarrantyForModal.serial_numbers) || typeof finalWarrantyForModal.serial_numbers[0] === 'object')) { console.log(`[DEBUG status.js] Using serial_numbers from cachedWarranty (simple array). Serials:`, cachedWarranty.serial_numbers); finalWarrantyForModal.serial_numbers = cachedWarranty.serial_numbers; } else if (Array.isArray(finalWarrantyForModal.serial_numbers) && finalWarrantyForModal.serial_numbers.length > 0 && typeof finalWarrantyForModal.serial_numbers[0] === 'object') { console.log(`[DEBUG status.js] Freshly fetched serial_numbers are objects. Mapping to strings. Original:`, finalWarrantyForModal.serial_numbers); finalWarrantyForModal.serial_numbers = finalWarrantyForModal.serial_numbers .map(snObj => snObj && snObj.serial_number) .filter(sn => typeof sn === 'string' && sn.trim() !== ''); } else if (!Array.isArray(finalWarrantyForModal.serial_numbers) || (Array.isArray(finalWarrantyForModal.serial_numbers) && finalWarrantyForModal.serial_numbers.length > 0 && typeof finalWarrantyForModal.serial_numbers[0] !== 'string')){ console.log(`[DEBUG status.js] serial_numbers in final object is not a simple array of strings. Defaulting to empty array. Current serials:`, finalWarrantyForModal.serial_numbers); finalWarrantyForModal.serial_numbers = []; } // Vendor: Prioritize cached if available and fresh data lacks it or is empty if (typeof cachedWarranty.vendor === 'string' && cachedWarranty.vendor.trim() !== '' && (typeof finalWarrantyForModal.vendor !== 'string' || finalWarrantyForModal.vendor.trim() === '')) { console.log(`[DEBUG status.js] Using vendor from cachedWarranty. Vendor:`, cachedWarranty.vendor); finalWarrantyForModal.vendor = cachedWarranty.vendor; } } // Ensure vendor is at least an empty string if null/undefined after merging attempts if (typeof finalWarrantyForModal.vendor !== 'string') { console.log(`[DEBUG status.js] Vendor in final object is not a string after potential merge. Defaulting to empty string. Current vendor:`, finalWarrantyForModal.vendor); finalWarrantyForModal.vendor = ''; } if (!finalWarrantyForModal) { throw new Error('Failed to prepare warranty data for the modal.'); } // --- Date Formatting - CRITICAL: Ensure dates are YYYY-MM-DD strings for the modal --- console.log(`[DEBUG status.js] Before date formatting - Purchase Date: ${finalWarrantyForModal.purchase_date}, Type: ${typeof finalWarrantyForModal.purchase_date}`); console.log(`[DEBUG status.js] Before date formatting - Expiration Date: ${finalWarrantyForModal.expiration_date}, Type: ${typeof finalWarrantyForModal.expiration_date}`); if (finalWarrantyForModal.purchase_date) { try { finalWarrantyForModal.purchase_date = formatDateYYYYMMDD(new Date(finalWarrantyForModal.purchase_date)); } catch (e) { console.error(`Error formatting purchase_date ('${finalWarrantyForModal.purchase_date}'):`, e); // Decide on fallback: keep as is, or set to null/empty string if critical for modal // For now, let's log and keep it, openEditModal might handle it or throw error. } } if (finalWarrantyForModal.is_lifetime) { finalWarrantyForModal.expiration_date = null; // Lifetime warranties shouldn't have an expiration date for the form } else if (finalWarrantyForModal.expiration_date) { try { finalWarrantyForModal.expiration_date = formatDateYYYYMMDD(new Date(finalWarrantyForModal.expiration_date)); } catch (e) { console.error(`Error formatting expiration_date ('${finalWarrantyForModal.expiration_date}'):`, e); } } // Ensure all essential fields for the modal are present, even if null finalWarrantyForModal.product_name = finalWarrantyForModal.product_name || ''; finalWarrantyForModal.tags = finalWarrantyForModal.tags || []; finalWarrantyForModal.serial_numbers = finalWarrantyForModal.serial_numbers || []; finalWarrantyForModal.vendor = finalWarrantyForModal.vendor || ''; // Added explicit fallback for vendor finalWarrantyForModal.warranty_duration_years = finalWarrantyForModal.warranty_duration_years || 0; finalWarrantyForModal.warranty_duration_months = finalWarrantyForModal.warranty_duration_months || 0; finalWarrantyForModal.warranty_duration_days = finalWarrantyForModal.warranty_duration_days || 0; // Ensure file paths are present, default to null if not finalWarrantyForModal.invoice_path = finalWarrantyForModal.invoice_path || null; finalWarrantyForModal.manual_path = finalWarrantyForModal.manual_path || null; finalWarrantyForModal.other_document_path = finalWarrantyForModal.other_document_path || null; console.log('[DEBUG status.js] Final warranty object being passed to openEditModal (full object):', JSON.parse(JSON.stringify(finalWarrantyForModal))); console.log(`[DEBUG status.js] Final Tags:`, JSON.stringify(finalWarrantyForModal.tags)); console.log(`[DEBUG status.js] Final Serial Numbers:`, JSON.stringify(finalWarrantyForModal.serial_numbers)); console.log(`[DEBUG status.js] Final Vendor: '${finalWarrantyForModal.vendor}'`); if (typeof window.openEditModal === 'function') { window.currentEditingWarrantyIdStatusPage = warrantyId; // For observer await window.openEditModal(finalWarrantyForModal); } else { showToast('Edit modal functionality is not available.', 'error'); console.error("window.openEditModal is not a function. Ensure script.js is loaded and exposes it."); } } catch (error) { console.error('Error preparing for editing (status.js IIFE):', error); showToast(`Error: ${error.message || 'Could not load warranty details for editing.'}`, 'error'); } finally { hideLoading(); // Hide main page loading, spinner for fetch is handled separately } } }); // Save Warranty Observer logic (for refreshing after edit) if (typeof window.setupSaveWarrantyObserver !== 'function') { // Define only if not already defined (e.g. by script.js) window.setupSaveWarrantyObserver = (originalSaveFunction) => { return async function() { // This is the wrapper function const originalArguments = arguments; // Capture original arguments for the save function let warrantyIdToReopen = null; // Check if the edit was initiated from the status page *before* saving if (window.currentEditingWarrantyIdStatusPage) { warrantyIdToReopen = window.currentEditingWarrantyIdStatusPage; console.log(`[Observer status.js] Edit initiated from status page. Captured warrantyIdToReopen: ${warrantyIdToReopen}`); } else { console.log(`[Observer status.js] Edit NOT initiated from status page (window.currentEditingWarrantyIdStatusPage is falsy).`); } // Call the original save function (e.g., window.saveWarranty from script.js) const result = await originalSaveFunction.apply(this, originalArguments); console.log(`[Observer status.js] Original save function completed. Result:`, result); // After the save is complete and if it was a status page edit: console.log(`[Observer status.js] Checking condition to refresh dashboard. warrantyIdToReopen value: "${warrantyIdToReopen}"`); if (warrantyIdToReopen) { console.log(`[Observer status.js] Condition PASSED. Warranty ID ${warrantyIdToReopen} found. Refreshing dashboard and scheduling details re-open.`); console.log(`[Observer status.js] Warranty update detected for ID: ${warrantyIdToReopen}. Refreshing dashboard.`); showToast('Warranty updated. Refreshing list...', 'success'); // Refresh the dashboard (which re-fetches and re-renders the table) await initDashboard(); // After dashboard refresh, find and re-open the details for the edited warranty // Use a timeout to allow the DOM to fully update after initDashboard setTimeout(() => { console.log(`[Observer status.js] setTimeout: Attempting to re-open details for ID: ${warrantyIdToReopen}`); if (!warrantyIdToReopen) { console.error("[Observer status.js] setTimeout: warrantyIdToReopen is NULL or undefined! Cannot re-open."); delete window.currentEditingWarrantyIdStatusPage; return; } const rowSelector = `#recentExpirationsBody tr[data-warranty-id="${warrantyIdToReopen}"]`; console.log(`[Observer status.js] setTimeout: Looking for row with selector: "${rowSelector}"`); const rowToReopen = document.querySelector(rowSelector); if (rowToReopen) { console.log(`[Observer status.js] setTimeout: Found row for warranty ID: ${warrantyIdToReopen}. Calling toggleWarrantyDetails.`); try { toggleWarrantyDetails(warrantyIdToReopen, rowToReopen, true); // Pass true for forceRefetch console.log(`[Observer status.js] setTimeout: toggleWarrantyDetails called successfully for ID: ${warrantyIdToReopen}`); } catch (e) { console.error(`[Observer status.js] setTimeout: Error calling toggleWarrantyDetails for ID ${warrantyIdToReopen}:`, e); } } else { console.warn(`[Observer status.js] setTimeout: Could NOT find row for warranty ID ${warrantyIdToReopen} after refresh. Selector used: "${rowSelector}"`); } // It's crucial to clean up the global flag after we're done with it. delete window.currentEditingWarrantyIdStatusPage; console.log(`[Observer status.js] Cleaned up window.currentEditingWarrantyIdStatusPage for ID: ${warrantyIdToReopen}`); }, 100); // 100ms delay, can be adjusted if necessary } return result; // Return the result of the original save function }; }; } // DOMContentLoaded listener to initialize the dashboard and event listeners function initStatusPage() { if (isDOMHandlerAttached) { console.log('Status page DOM handler already attached, skipping...'); return; } isDOMHandlerAttached = true; console.log('Status page DOM loaded (status.js IIFE). Waiting for i18n initialization...'); const headerDarkModeToggle = document.getElementById('darkModeToggle'); if (headerDarkModeToggle) { headerDarkModeToggle.addEventListener('change', function() { setTheme(this.checked); // IIFE's setTheme (manages data-theme and localStorage) redrawChartsWithNewTheme(); // IIFE's redraw (manages status page charts) }); } else { console.log('Header darkModeToggle not found by status.js for direct listener attachment. Theme changes via localStorage/theme-loader.js should still work.'); } // Wait for i18n to be ready before initializing dashboard function waitForI18nAndInit() { if (window.i18n && window.i18n.t) { console.log('i18n is ready, initializing dashboard...'); initDashboard(); // Call IIFE's initDashboard } else { console.log('Waiting for i18n ready event...'); window.addEventListener('i18nReady', function(event) { console.log('i18n ready event received, initializing dashboard...', event.detail); initDashboard(); // Call IIFE's initDashboard }, { once: true }); // Fallback timeout in case i18n event doesn't fire setTimeout(() => { if (!window.i18n || !window.i18n.t) { console.warn('i18n initialization timeout, proceeding anyway...'); initDashboard(); } }, 5000); } } waitForI18nAndInit(); // Setup event listeners for status page specific controls if (refreshDashboardBtn) refreshDashboardBtn.addEventListener('click', refreshDashboard); if (searchWarranties) searchWarranties.addEventListener('input', filterAndSortWarranties); // Restore saved status filter selection on load if (statusFilter) { const savedStatus = loadStatusFilterPreference(); if (savedStatus && statusFilter.value !== savedStatus) { statusFilter.value = savedStatus; } statusFilter.addEventListener('change', function() { saveStatusFilterPreference(statusFilter.value); filterAndSortWarranties(); }); } if (exportBtn) { exportBtn.addEventListener('click', function() { console.log("Status page export button clicked (status.js IIFE)."); // Placeholder for actual export logic for status page data showToast('Export for status page table not fully implemented yet.', 'info'); }); } attachSortListeners(); // Call IIFE's attachSortListeners // Simplified attempt to wrap saveWarranty from script.js, within DOMContentLoaded setTimeout(() => { if (typeof window.saveWarranty === 'function' && window.saveWarrantyObserverAttachedByStatus !== true) { console.log('(STATUS.JS) Attempting to wrap window.saveWarranty via DOMContentLoaded/setTimeout.'); const originalSave = window.saveWarranty; window.saveWarranty = window.setupSaveWarrantyObserver(originalSave); window.saveWarrantyObserverAttachedByStatus = true; console.log('(STATUS.JS) window.saveWarranty hopefully WRAPPED by DOMContentLoaded/setTimeout.'); } else if (window.saveWarrantyObserverAttachedByStatus === true) { console.log('(STATUS.JS) window.saveWarranty ALREADY WRAPPED (checked in DOMContentLoaded/setTimeout).'); } else if (typeof window.saveWarranty !== 'function') { console.warn(`(STATUS.JS) DOMContentLoaded/setTimeout: window.saveWarranty NOT FOUND. Observer NOT attached.`); } else { console.warn(`(STATUS.JS) DOMContentLoaded/setTimeout: window.saveWarranty found, but NOT WRAPPED (flag: ${window.saveWarrantyObserverAttachedByStatus}). Observer NOT attached.`); } }, 500); // Delay by 500ms } // Add event listener with protection against multiple calls if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initStatusPage, { once: true }); } else { // DOM is already loaded setTimeout(initStatusPage, 0); } })();