/* 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
};