JezK
Edit File: dispatch-counter.js
jQuery(document).ready(function($) { let autoRefreshInterval; let countdownInterval; let isAutoRefreshActive = true; let isOperational = $('#operational-toggle').is(':checked'); let countdownSeconds = 30; let lastRefreshTime = Date.now(); let backgroundRefreshInterval; // Initialize auto refresh startAutoRefresh(); // Items breakdown is now always visible - no toggle needed // Handle operational toggle window.toggleOperationalDay = function(checked) { isOperational = checked; // Update URL without page reload const url = new URL(window.location); if (checked) { url.searchParams.set('operational', '1'); } else { url.searchParams.delete('operational'); } window.history.pushState({}, '', url); // Refresh counts immediately console.log('Operational toggle changed - force refreshing...'); refreshCounts(true); }; // Manual refresh button $('#refresh-counter').on('click', function() { console.log('Manual refresh triggered...'); refreshCounts(true); // Force refresh on manual click }); // Toggle auto refresh $('#toggle-auto-refresh').on('click', function() { if (isAutoRefreshActive) { stopAutoRefresh(); $(this).text('Resume'); $('#auto-refresh-status').text('Paused'); } else { startAutoRefresh(); $(this).text('Pause'); $('#auto-refresh-status').text('Every 30 seconds'); } }); // Start auto refresh function startAutoRefresh() { isAutoRefreshActive = true; // Primary refresh interval (30 seconds) autoRefreshInterval = setInterval(function() { console.log('Auto-refreshing dispatch counts...'); refreshCounts(true); // Force refresh lastRefreshTime = Date.now(); }, 30000); // 30 seconds // Background refresh interval (more frequent for background tabs) backgroundRefreshInterval = setInterval(function() { const timeSinceLastRefresh = Date.now() - lastRefreshTime; const isHidden = document.hidden; // If tab is hidden and it's been more than 35 seconds since last refresh, force refresh if (isHidden && timeSinceLastRefresh > 35000) { console.log('Background refresh triggered - ensuring fresh data'); refreshCounts(true); lastRefreshTime = Date.now(); } // If tab is visible and it's been more than 35 seconds, something went wrong, force refresh if (!isHidden && timeSinceLastRefresh > 35000) { console.log('Failsafe refresh triggered - main interval may have failed'); refreshCounts(true); lastRefreshTime = Date.now(); } }, 10000); // Check every 10 seconds // Show countdown timer startCountdownTimer(); } // Stop auto refresh function stopAutoRefresh() { isAutoRefreshActive = false; if (autoRefreshInterval) { clearInterval(autoRefreshInterval); } if (backgroundRefreshInterval) { clearInterval(backgroundRefreshInterval); } stopCountdownTimer(); } // Refresh counts function function refreshCounts(forceRefresh = false) { showLoading(true); const requestData = { action: 'get_dispatch_count', nonce: dispatchCounter.nonce, operational: isOperational ? '1' : '0', timestamp: Date.now() // Cache busting }; if (forceRefresh) { requestData.force_refresh = '1'; console.log('Force refreshing with cache bust:', requestData.timestamp); } $.ajax({ url: dispatchCounter.ajax_url, method: 'POST', data: requestData, cache: false, // Disable caching timeout: 10000, // 10 second timeout success: function(response) { if (response.success) { updateCountDisplay(response.data); updateLastUpdatedTime(); resetCountdownTimer(); lastRefreshTime = Date.now(); // Track successful refresh console.log('Dispatch counts updated successfully'); } else { showError('Failed to fetch dispatch counts: ' + (response.data || 'Unknown error')); } }, error: function(xhr, status, error) { console.error('AJAX Error:', status, error); showError('Network error: ' + error); // Retry after 5 seconds if it's an auto-refresh if (forceRefresh && isAutoRefreshActive) { setTimeout(function() { console.log('Retrying failed refresh...'); refreshCounts(true); }, 5000); } }, complete: function() { showLoading(false); } }); } // Update count display function updateCountDisplay(data) { // Update counts with animation animateCountChange('#total-orders-count', data.total_orders); animateCountChange('#total-boxes-count', data.total_boxes); animateCountChange('#processing-orders-count', data.processing_orders); animateCountChange('#completed-orders-count', data.completed_orders); // Update dispatch date info const dispatchDate = new Date(data.dispatch_date); const formattedDate = dispatchDate.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); $('#dispatch-date-info').text(formattedDate); // Update description const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); const tomorrowFormatted = tomorrow.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' }); if (data.is_operational) { $('#dispatch-date-description').text(`Showing dispatch count for next operational day (${formattedDate})`); } else { $('#dispatch-date-description').text(`Showing dispatch count for tomorrow (${tomorrowFormatted})`); } // Update card colors based on status updateCardStyles(data); // Update items breakdown updateItemsBreakdown(data.items_breakdown); } // Animate count changes function animateCountChange(selector, newValue) { const $element = $(selector); const currentValue = parseInt($element.text()) || 0; if (currentValue !== newValue) { $element.addClass('count-changing'); // Animate the number change $({ countNum: currentValue }).animate({ countNum: newValue }, { duration: 800, easing: 'swing', step: function() { $element.text(Math.floor(this.countNum)); }, complete: function() { $element.text(newValue); $element.removeClass('count-changing'); } }); } } // Update card styles based on data function updateCardStyles(data) { // Remove all status classes $('.dispatch-card').removeClass('high-count medium-count low-count zero-count'); // Add appropriate classes based on counts updateCardStatus('#total-orders-count', data.total_orders, [0, 5, 15]); updateCardStatus('#total-boxes-count', data.total_boxes, [0, 10, 30]); updateCardStatus('#processing-orders-count', data.processing_orders, [0, 3, 10]); updateCardStatus('#completed-orders-count', data.completed_orders, [0, 2, 8]); } // Update individual card status function updateCardStatus(selector, value, thresholds) { const $card = $(selector).closest('.dispatch-card'); if (value === 0) { $card.addClass('zero-count'); } else if (value <= thresholds[1]) { $card.addClass('low-count'); } else if (value <= thresholds[2]) { $card.addClass('medium-count'); } else { $card.addClass('high-count'); } } // Show/hide loading indicator function showLoading(show) { if (show) { $('#dispatch-loading').show(); $('#dispatch-counter-grid').addClass('loading'); } else { $('#dispatch-loading').hide(); $('#dispatch-counter-grid').removeClass('loading'); } } // Update last updated time function updateLastUpdatedTime() { const now = new Date(); const timeString = now.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }) + ' ' + now.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', second: '2-digit', hour12: true }); $('#last-updated-info').text(timeString); // Start updating "time since" display updateTimeSinceDisplay(); } // Update "time since last update" display function updateTimeSinceDisplay() { const timeSinceRefresh = Date.now() - lastRefreshTime; const secondsAgo = Math.floor(timeSinceRefresh / 1000); const minutesAgo = Math.floor(secondsAgo / 60); let timeSinceText = ''; if (minutesAgo > 0) { timeSinceText = `${minutesAgo} minute${minutesAgo > 1 ? 's' : ''} ago`; } else if (secondsAgo > 0) { timeSinceText = `${secondsAgo} second${secondsAgo > 1 ? 's' : ''} ago`; } else { timeSinceText = 'Just now'; } $('#time-since-update').text(timeSinceText); } // Update items breakdown display function updateItemsBreakdown(itemsData) { const $content = $('#items-breakdown-content'); if (!itemsData || Object.keys(itemsData).length === 0) { $content.html('<p>No items to display for this date.</p>'); return; } // Generate HTML for items breakdown let html = '<div class="items-breakdown">'; html += '<h4>Items Required</h4>'; html += '<div class="items-grid">'; // Categorize items const categories = { 'Main Dishes': [], 'Side Dishes': [], 'Bread & Rice': [], 'Accompaniments': [], 'Others': [] }; Object.entries(itemsData).forEach(([item, quantity]) => { const itemInfo = { name: item, quantity: quantity }; if (item.toLowerCase().includes('main') || item.toLowerCase().includes('biryani')) { categories['Main Dishes'].push(itemInfo); } else if (item.toLowerCase().includes('side') || item.toLowerCase().includes('dal')) { categories['Side Dishes'].push(itemInfo); } else if (item.toLowerCase().includes('roti') || item.toLowerCase().includes('rice')) { categories['Bread & Rice'].push(itemInfo); } else if (item.toLowerCase().includes('raita') || item.toLowerCase().includes('pickle') || item.toLowerCase().includes('papad')) { categories['Accompaniments'].push(itemInfo); } else { categories['Others'].push(itemInfo); } }); // Generate HTML for each category Object.entries(categories).forEach(([category, items]) => { if (items.length > 0) { html += `<div class="item-category">`; html += `<h5>${category}</h5>`; html += `<ul class="item-list">`; items.forEach(item => { html += `<li class="item-entry">`; html += `<span class="item-name">${escapeHtml(item.name)}</span>`; html += `<span class="item-quantity">${item.quantity}</span>`; html += `</li>`; }); html += `</ul>`; html += `</div>`; } }); html += '</div>'; html += '</div>'; $content.html(html); } // Helper function to escape HTML function escapeHtml(text) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return text.replace(/[&<>"']/g, function(m) { return map[m]; }); } // Show error messages function showError(message) { // Create error notice if it doesn't exist let $errorNotice = $('#dispatch-error-notice'); if ($errorNotice.length === 0) { $errorNotice = $('<div id="dispatch-error-notice" class="notice notice-error is-dismissible" style="margin: 20px 0;"><p></p></div>'); $('.wrap h1').after($errorNotice); } $errorNotice.find('p').text(message); $errorNotice.show(); // Auto-hide after 5 seconds setTimeout(function() { $errorNotice.fadeOut(); }, 5000); } // Page visibility API - keep auto refresh running but catch up when visible if (typeof document.hidden !== "undefined") { document.addEventListener("visibilitychange", function() { if (document.hidden) { console.log('Tab hidden - auto refresh continues in background'); // Don't stop auto refresh, just log the event } else { console.log('Tab visible - performing catch-up refresh'); // Force refresh immediately when tab becomes visible to catch up refreshCounts(true); } }); } // Keyboard shortcuts $(document).on('keydown', function(e) { // R key to refresh (when not in input fields) if (e.key === 'r' && !$(e.target).is('input, textarea, select')) { e.preventDefault(); refreshCounts(); } // Space to toggle auto refresh (when not in input fields) if (e.key === ' ' && !$(e.target).is('input, textarea, select')) { e.preventDefault(); $('#toggle-auto-refresh').click(); } }); // Tooltip functionality $('[data-tooltip]').hover( function() { const tooltip = $(this).data('tooltip'); $(this).append('<div class="dispatch-tooltip">' + tooltip + '</div>'); }, function() { $('.dispatch-tooltip').remove(); } ); // Countdown timer functions function startCountdownTimer() { countdownSeconds = 30; updateCountdownDisplay(); countdownInterval = setInterval(function() { countdownSeconds--; updateCountdownDisplay(); if (countdownSeconds <= 0) { countdownSeconds = 30; // Reset for next cycle } }, 1000); } function stopCountdownTimer() { if (countdownInterval) { clearInterval(countdownInterval); } $('#auto-refresh-status').text('Paused'); } function resetCountdownTimer() { countdownSeconds = 30; updateCountdownDisplay(); } function updateCountdownDisplay() { if (isAutoRefreshActive) { const isHidden = document.hidden; const statusText = isHidden ? `Refreshing in background (${countdownSeconds}s)` : `Next refresh in ${countdownSeconds}s`; $('#auto-refresh-status').text(statusText); // Add visual indicator when countdown is low (only when tab is visible) if (countdownSeconds <= 5 && !isHidden) { $('#auto-refresh-status').addClass('countdown-urgent'); } else { $('#auto-refresh-status').removeClass('countdown-urgent'); } // Add background indicator when tab is hidden if (isHidden) { $('#auto-refresh-status').addClass('background-refresh'); } else { $('#auto-refresh-status').removeClass('background-refresh'); } } } // Force refresh on window focus (when user returns to tab) $(window).on('focus', function() { if (isAutoRefreshActive) { console.log('Window focused - force refreshing...'); refreshCounts(true); } }); // Update "time since" display every 10 seconds setInterval(updateTimeSinceDisplay, 10000); // Initial refresh after page load setTimeout(function() { console.log('Initial page load refresh...'); refreshCounts(true); }, 1000); });