/** * Easy Invoice Reports JavaScript * * Handles all reporting functionality including tab switching, * chart rendering, and export features for the reports page. * * @package EasyInvoice * @since 1.0.0 */ (function($) { 'use strict'; const EasyInvoiceReports = { /** * Initialize the reporting functionality */ init: function() { this.ensurePaymentTabVisibility(); this.initCharts(); this.setupTabSwitching(); this.setupExportHandlers(); }, /** * Ensure payment tab is properly displayed by default */ ensurePaymentTabVisibility: function() { const paymentReportTab = document.getElementById('payment-report-content'); const invoiceReportTab = document.getElementById('invoice-report-content'); if (paymentReportTab && invoiceReportTab) { // Set proper visibility immediately paymentReportTab.classList.remove('hidden'); paymentReportTab.classList.add('block'); paymentReportTab.style.display = 'block'; invoiceReportTab.classList.add('hidden'); invoiceReportTab.classList.remove('block'); invoiceReportTab.style.display = 'none'; // Set proper button state const paymentButton = document.getElementById('tab-payment-report'); const invoiceButton = document.getElementById('tab-invoice-report'); if (paymentButton && invoiceButton) { paymentButton.classList.add('active-tab', 'border-indigo-500', 'text-indigo-600'); paymentButton.classList.remove('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300'); invoiceButton.classList.remove('active-tab', 'border-indigo-500', 'text-indigo-600'); invoiceButton.classList.add('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300'); } // Also set the visibility with a delay to override any other scripts setTimeout(function() { paymentReportTab.classList.remove('hidden'); paymentReportTab.classList.add('block'); paymentReportTab.style.display = 'block !important'; invoiceReportTab.classList.add('hidden'); invoiceReportTab.classList.remove('block'); invoiceReportTab.style.display = 'none'; }, 100); } }, /** * Initialize all chart visualizations */ initCharts: function() { this.initRevenueChart(); this.initStatusChart(); this.initPaymentMethodsChart(); this.initInvoiceStatusChart(); }, /** * Initialize monthly revenue chart */ initRevenueChart: function() { try { if (!document.getElementById('revenue-chart')) { return; } const monthlyRevenue = easy_invoice_reports.monthly_revenue || {}; const monthlyRevenueLabels = Object.keys(monthlyRevenue); const monthlyRevenueData = Object.values(monthlyRevenue); const revenueCtx = document.getElementById('revenue-chart').getContext('2d'); new Chart(revenueCtx, { type: 'bar', data: { labels: monthlyRevenueLabels, datasets: [{ label: 'Revenue', data: monthlyRevenueData, backgroundColor: 'rgba(79, 70, 229, 0.2)', borderColor: 'rgba(79, 70, 229, 1)', borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return '$' + value.toLocaleString(); } } } }, plugins: { tooltip: { callbacks: { label: function(context) { return '$' + context.raw.toLocaleString(); } } } } } }); } catch (error) { // Error initializing revenue chart this.handleChartError('revenue-chart'); } }, /** * Initialize invoice status pie chart */ initStatusChart: function() { try { if (!document.getElementById('status-chart')) { return; } const invoiceStatus = easy_invoice_reports.invoice_status || {}; // Filter out the counts key const statusLabels = Object.keys(invoiceStatus).filter(key => key !== 'counts'); const statusData = statusLabels.map(label => invoiceStatus[label]); const statusColors = [ 'rgba(34, 197, 94, 0.8)', // Green for Paid 'rgba(234, 179, 8, 0.8)', // Yellow for Unpaid 'rgba(239, 68, 68, 0.8)', // Red for Overdue 'rgba(209, 213, 219, 0.8)' // Gray for Draft ]; const statusCtx = document.getElementById('status-chart').getContext('2d'); new Chart(statusCtx, { type: 'doughnut', data: { labels: statusLabels, datasets: [{ data: statusData, backgroundColor: statusColors, borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { callbacks: { label: function(context) { return context.label + ': ' + context.raw + '%'; } } } }, cutout: '70%' } }); } catch (error) { // Error initializing status chart this.handleChartError('status-chart'); } }, /** * Initialize payment methods chart */ initPaymentMethodsChart: function() { try { if (!document.getElementById('payment-methods-chart')) { return; } const paymentReport = easy_invoice_reports.payment_report || {}; const paymentMethods = paymentReport.by_method || {}; const methods = []; const counts = []; const methodColors = []; const colorPalette = [ 'rgba(79, 70, 229, 0.8)', 'rgba(59, 130, 246, 0.8)', 'rgba(16, 185, 129, 0.8)', 'rgba(245, 158, 11, 0.8)', 'rgba(239, 68, 68, 0.8)', 'rgba(107, 114, 128, 0.8)' ]; let i = 0; for (const method in paymentMethods) { methods.push(this.capitalizeFirst(method)); counts.push(paymentMethods[method].count); methodColors.push(colorPalette[i % colorPalette.length]); i++; } if (methods.length > 0) { const paymentMethodsCtx = document.getElementById('payment-methods-chart').getContext('2d'); new Chart(paymentMethodsCtx, { type: 'pie', data: { labels: methods, datasets: [{ data: counts, backgroundColor: methodColors, borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right' }, tooltip: { callbacks: { label: function(context) { const total = context.dataset.data.reduce((a, b) => a + b, 0); const percentage = Math.round((context.raw / total) * 100); return context.label + ': ' + context.raw + ' (' + percentage + '%)'; } } } } } }); } else { this.handleChartError('payment-methods-chart', 'No payment method data available'); } } catch (error) { // Error initializing payment methods chart this.handleChartError('payment-methods-chart'); } }, /** * Initialize invoice status detailed chart */ initInvoiceStatusChart: function() { try { if (!document.getElementById('invoice-status-chart')) { return; } const invoiceReport = easy_invoice_reports.invoice_report || {}; const statusData = invoiceReport.by_status || {}; const statusList = { 'paid': 'Paid', 'unpaid': 'Unpaid', 'overdue': 'Overdue', 'draft': 'Draft', 'canceled': 'Canceled', 'other': 'Other' }; const statusChartColors = [ 'rgba(34, 197, 94, 0.8)', // Green for Paid 'rgba(234, 179, 8, 0.8)', // Yellow for Unpaid 'rgba(239, 68, 68, 0.8)', // Red for Overdue 'rgba(209, 213, 219, 0.8)', // Gray for Draft 'rgba(107, 114, 128, 0.8)', // Dark Gray for Canceled 'rgba(156, 163, 175, 0.8)' // Medium Gray for Other ]; const statuses = []; const statusCounts = []; const colors = []; let i = 0; for (const statusKey in statusList) { const data = statusData[statusKey] || { count: 0 }; if (data.count > 0) { statuses.push(statusList[statusKey]); statusCounts.push(data.count); colors.push(statusChartColors[i]); } i++; } if (statuses.length > 0) { const invoiceStatusCtx = document.getElementById('invoice-status-chart').getContext('2d'); new Chart(invoiceStatusCtx, { type: 'pie', data: { labels: statuses, datasets: [{ data: statusCounts, backgroundColor: colors, borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right' }, tooltip: { callbacks: { label: function(context) { const total = context.dataset.data.reduce((a, b) => a + b, 0); const percentage = Math.round((context.raw / total) * 100); return context.label + ': ' + context.raw + ' (' + percentage + '%)'; } } } } } }); } else { this.handleChartError('invoice-status-chart', 'No invoice status data available'); } } catch (error) { // Error initializing invoice status chart this.handleChartError('invoice-status-chart'); } }, /** * Handle chart initialization errors */ handleChartError: function(canvasId, message) { const canvas = document.getElementById(canvasId); if (!canvas) return; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.font = '14px Arial'; ctx.fillStyle = '#6B7280'; ctx.textAlign = 'center'; ctx.fillText(message || 'Error loading chart data', canvas.width / 2, canvas.height / 2); }, /** * Setup tab switching functionality */ setupTabSwitching: function() { const self = this; const tabButtons = document.querySelectorAll('.tab-button'); tabButtons.forEach(button => { button.addEventListener('click', function(e) { // Prevent default behavior that might cause page reload e.preventDefault(); // Get button ID safely const buttonId = this.id || ''; // Store the target tab ID before modifying classes const targetId = buttonId.replace('tab-', '') + '-content'; // Remove active class from all buttons first tabButtons.forEach(btn => { btn.classList.remove('active-tab', 'border-indigo-500', 'text-indigo-600'); btn.classList.add('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300'); }); // Add active class to clicked button this.classList.add('active-tab', 'border-indigo-500', 'text-indigo-600'); this.classList.remove('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300'); // Get all tab contents const paymentReportTab = document.getElementById('payment-report-content'); const invoiceReportTab = document.getElementById('invoice-report-content'); // Hide both tabs first if (paymentReportTab) { paymentReportTab.classList.add('hidden'); paymentReportTab.classList.remove('block'); paymentReportTab.style.display = 'none'; } if (invoiceReportTab) { invoiceReportTab.classList.add('hidden'); invoiceReportTab.classList.remove('block'); invoiceReportTab.style.display = 'none'; } // Show the target tab const targetTab = document.getElementById(targetId); if (targetTab) { // Short delay helps ensure DOM updates properly setTimeout(() => { targetTab.classList.remove('hidden'); targetTab.classList.add('block'); targetTab.style.display = 'block'; }, 10); } else { // Fallback to payment report if target not found if (paymentReportTab) { setTimeout(() => { paymentReportTab.classList.remove('hidden'); paymentReportTab.classList.add('block'); paymentReportTab.style.display = 'block'; }, 10); } } }); }); }, /** * Setup report export functionality */ setupExportHandlers: function() { this.setupMainReportExport(); this.setupDetailedReportExport(); }, /** * Setup main report export */ setupMainReportExport: function() { const self = this; const exportButton = document.getElementById('export-report'); if (!exportButton) return; exportButton.addEventListener('click', function() { // Create a timestamp for the filename const timestamp = self.getFormattedDate(); // Generate CSV content let csvContent = 'data:text/csv;charset=utf-8,'; // Add report title and date range const title = document.querySelector('h1') ? document.querySelector('h1').textContent : 'Reports'; csvContent += 'Easy Invoice Report: ' + title + '\r\n'; csvContent += 'Date Range: ' + easy_invoice_reports.start_date + ' to ' + easy_invoice_reports.end_date + '\r\n\r\n'; // Add summary stats from the global data const stats = easy_invoice_reports.summary_stats || {}; csvContent += 'Summary Statistics\r\n'; csvContent += 'Total Revenue,$' + self.formatNumber(stats.total_revenue) + '\r\n'; csvContent += 'Total Invoices,' + stats.total_invoices + '\r\n'; csvContent += 'Active Clients,' + stats.active_clients + '\r\n'; csvContent += 'Average Payment Time,' + stats.avg_payment_time + ' days\r\n\r\n'; // Add monthly revenue const monthlyRevenue = easy_invoice_reports.monthly_revenue || {}; csvContent += 'Monthly Revenue\r\n'; csvContent += 'Month,Revenue\r\n'; for (const month in monthlyRevenue) { csvContent += month + ',$' + self.formatNumber(monthlyRevenue[month]) + '\r\n'; } csvContent += '\r\n'; // Add invoice status const invoiceStatus = easy_invoice_reports.invoice_status || {}; csvContent += 'Invoice Status\r\n'; csvContent += 'Status,Percentage,Count\r\n'; for (const status in invoiceStatus) { if (status !== 'counts') { const counts = invoiceStatus.counts || {}; const count = counts[status.toLowerCase()] || 0; csvContent += status + ',' + invoiceStatus[status] + '%,' + count + '\r\n'; } } csvContent += '\r\n'; // Add top clients const topClients = easy_invoice_reports.top_clients || []; csvContent += 'Top Clients by Revenue\r\n'; csvContent += 'Client,Email,Total Amount,Invoices,Last Invoice\r\n'; topClients.forEach(function(client) { const lastInvoice = client.last_invoice ? self.formatDate(client.last_invoice) : 'N/A'; csvContent += self.escapeCsvValue(client.name) + ',' + self.escapeCsvValue(client.email) + ',' + '$' + self.formatNumber(client.total_amount) + ',' + client.total_invoices + ',' + lastInvoice + '\r\n'; }); self.triggerDownload(csvContent, 'easy-invoice-report-' + timestamp + '.csv'); }); }, /** * Setup detailed report export */ setupDetailedReportExport: function() { const self = this; const exportButton = document.getElementById('export-detailed-report'); if (!exportButton) return; exportButton.addEventListener('click', function() { // Create a timestamp for the filename const timestamp = self.getFormattedDate(); // Determine which tab is active const activeTab = document.querySelector('.tab-button.active-tab'); if (!activeTab) return; const activeTabId = activeTab.id; let reportType = activeTabId === 'tab-payment-report' ? 'payment' : 'invoice'; let reportData, fileName, headers, rows = []; if (reportType === 'payment') { // Export payment report const paymentReport = easy_invoice_reports.payment_report || {}; reportData = paymentReport.payments || []; fileName = 'easy-invoice-payment-report-' + timestamp + '.csv'; headers = ['Date', 'Invoice', 'Amount', 'Method', 'Status', 'Transaction ID']; // Generate rows reportData.forEach(function(payment) { const date = self.formatDate(payment.date); const row = [ date, payment.invoice_number, payment.currency_symbol + self.formatNumber(payment.amount), self.capitalizeFirst(payment.payment_method), self.capitalizeFirst(payment.status), payment.transaction_id || '-' ]; rows.push(row); }); } else { // Export invoice report const invoiceReport = easy_invoice_reports.invoice_report || {}; reportData = invoiceReport.invoices || []; fileName = 'easy-invoice-invoice-report-' + timestamp + '.csv'; headers = ['Date', 'Due Date', 'Invoice #', 'Client', 'Amount', 'Status']; // Generate rows reportData.forEach(function(invoice) { const issueDate = self.formatDate(invoice.issue_date); const dueDate = invoice.due_date ? self.formatDate(invoice.due_date) : '-'; const row = [ issueDate, dueDate, invoice.invoice_number, self.escapeCsvValue(invoice.client_name), '$' + self.formatNumber(invoice.total), self.capitalizeFirst(invoice.status) ]; rows.push(row); }); } // Generate CSV content let csvContent = 'data:text/csv;charset=utf-8,'; csvContent += headers.join(',') + '\r\n'; rows.forEach(function(row) { csvContent += row.join(',') + '\r\n'; }); self.triggerDownload(csvContent, fileName); }); }, /** * Trigger CSV download */ triggerDownload: function(csvContent, fileName) { const encodedUri = encodeURI(csvContent); const link = document.createElement('a'); link.setAttribute('href', encodedUri); link.setAttribute('download', fileName); document.body.appendChild(link); // Trigger download link.click(); document.body.removeChild(link); }, /** * Format date for display */ formatDate: function(dateString) { const date = new Date(dateString); return date.toLocaleDateString(); }, /** * Get formatted date for filenames */ getFormattedDate: function() { const date = new Date(); return date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0') + '-' + String(date.getDate()).padStart(2, '0'); }, /** * Format number with commas for thousands */ formatNumber: function(num) { return parseFloat(num).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }, /** * Capitalize first letter of a string */ capitalizeFirst: function(str) { if (!str) return ''; return str.charAt(0).toUpperCase() + str.slice(1); }, /** * Escape values for CSV */ escapeCsvValue: function(value) { if (!value) return ''; // If the value contains a comma, quote, or newline, wrap in quotes and escape internal quotes if (/[",\n\r]/.test(value)) { return '"' + value.replace(/"/g, '""') + '"'; } return value; } }; // Initialize when document is ready $(document).ready(function() { EasyInvoiceReports.init(); }); })(jQuery);