199 lines
5.6 KiB
JavaScript
199 lines
5.6 KiB
JavaScript
/* 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 = `
|
|
<span class="toast-message">${escapeHtml(message)}</span>
|
|
<button class="toast-close" onclick="this.parentElement.remove()">×</button>
|
|
`;
|
|
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
|
|
};
|