/* QBO Excel Sync - Main JavaScript */ // Toast notification system function showToast(message, type = 'info', duration = 4000) { let container = document.querySelector('.toast-container'); if (!container) { container = document.createElement('div'); container.className = 'toast-container'; document.body.appendChild(container); } const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.innerHTML = ` ${escapeHtml(message)} `; container.appendChild(toast); setTimeout(() => { toast.style.animation = 'toast-out 0.3s ease forwards'; setTimeout(() => toast.remove(), 300); }, duration); } // HTML escape utility function escapeHtml(text) { if (text === null || text === undefined) return ''; const div = document.createElement('div'); div.textContent = String(text); return div.innerHTML; } // Format date for display function formatDate(dateString) { if (!dateString) return ''; const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); } // Format currency for display function formatCurrency(amount, currency = 'USD') { if (amount === null || amount === undefined) return ''; return new Intl.NumberFormat('en-US', { style: 'currency', currency: currency }).format(amount); } // Debounce function for search inputs function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // API request helper with error handling async function apiRequest(url, options = {}) { const defaultOptions = { headers: { 'Content-Type': 'application/json', }, }; try { const response = await fetch(url, { ...defaultOptions, ...options }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || `HTTP ${response.status}`); } return data; } catch (error) { console.error('API request error:', error); throw error; } } // Confirm dialog helper function confirmAction(message) { return confirm(message); } // Loading state management function setLoading(element, isLoading, loadingText = 'Loading...') { if (isLoading) { element.dataset.originalText = element.textContent; element.textContent = loadingText; element.disabled = true; } else { element.textContent = element.dataset.originalText || element.textContent; element.disabled = false; } } // Initialize tooltips (basic implementation) function initTooltips() { document.querySelectorAll('[data-tooltip]').forEach(el => { el.addEventListener('mouseenter', () => { const tooltip = document.createElement('div'); tooltip.className = 'tooltip'; tooltip.textContent = el.dataset.tooltip; document.body.appendChild(tooltip); const rect = el.getBoundingClientRect(); tooltip.style.top = `${rect.top - tooltip.offsetHeight - 8}px`; tooltip.style.left = `${rect.left + (rect.width - tooltip.offsetWidth) / 2}px`; el._tooltip = tooltip; }); el.addEventListener('mouseleave', () => { if (el._tooltip) { el._tooltip.remove(); delete el._tooltip; } }); }); } // Copy to clipboard async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); showToast('Copied to clipboard', 'success'); } catch (err) { showToast('Failed to copy', 'error'); } } // File size formatter function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // Check connection status async function checkConnectionStatus() { try { const response = await fetch('/api/connection/test'); const data = await response.json(); return data.connected === true; } catch (error) { return false; } } // Update connection indicator function updateConnectionIndicator(isConnected) { const indicator = document.querySelector('.connection-status'); if (indicator) { indicator.className = `connection-status ${isConnected ? 'connected' : 'disconnected'}`; indicator.querySelector('span:last-child').textContent = isConnected ? 'Connected' : 'Not Connected'; } } // Initialize on DOM ready document.addEventListener('DOMContentLoaded', () => { initTooltips(); // Add active class to current nav item const currentPath = window.location.pathname; document.querySelectorAll('.nav-link').forEach(link => { if (link.getAttribute('href') === currentPath) { link.classList.add('active'); } }); }); // Export for use in other scripts window.QBO = { showToast, escapeHtml, formatDate, formatCurrency, debounce, apiRequest, confirmAction, setLoading, copyToClipboard, formatFileSize, checkConnectionStatus, updateConnectionIndicator };