/** * Charts and Data Visualization for Kayal Aqua * Mobile-responsive charts using Chart.js */ class KayalAquaCharts { constructor() { this.charts = {}; this.colors = { primary: '#1e3a5f', secondary: '#ffc107', success: '#28a745', danger: '#dc3545', warning: '#ffc107', info: '#17a2b8', light: '#f8f9fa', dark: '#495057' }; this.init(); } init() { // Initialize charts when DOM is ready if (typeof Chart !== 'undefined') { this.setupChartDefaults(); this.initializeAllCharts(); } else { // Load Chart.js if not already loaded this.loadChartJS().then(() => { this.setupChartDefaults(); this.initializeAllCharts(); }); } } async loadChartJS() { return new Promise((resolve, reject) => { if (typeof Chart !== 'undefined') { resolve(); return; } const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js'; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } setupChartDefaults() { Chart.defaults.font.family = "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif"; Chart.defaults.color = this.colors.dark; Chart.defaults.responsive = true; Chart.defaults.maintainAspectRatio = false; // Mobile-specific defaults if (window.innerWidth <= 768) { Chart.defaults.font.size = 12; Chart.defaults.plugins.legend.labels.boxWidth = 12; Chart.defaults.plugins.legend.labels.padding = 10; } } initializeAllCharts() { // Revenue vs Expenses Chart this.initRevenueExpensesChart(); // Business Segments Pie Chart this.initBusinessSegmentsChart(); // Monthly Trends Line Chart this.initMonthlyTrendsChart(); // Cash Flow Chart this.initCashFlowChart(); // Top Products/Services Chart this.initTopProductsChart(); } initRevenueExpensesChart() { const canvas = document.getElementById('revenueExpensesChart'); if (!canvas) return; const ctx = canvas.getContext('2d'); // Sample data - replace with actual data from API const data = { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], datasets: [ { label: 'Revenue', data: [65000, 59000, 80000, 81000, 56000, 75000], backgroundColor: this.colors.secondary + '80', borderColor: this.colors.secondary, borderWidth: 2, fill: true }, { label: 'Expenses', data: [28000, 48000, 40000, 35000, 40000, 45000], backgroundColor: this.colors.danger + '80', borderColor: this.colors.danger, borderWidth: 2, fill: true } ] }; this.charts.revenueExpenses = new Chart(ctx, { type: 'line', data: data, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return '₹' + value.toLocaleString(); } } } }, plugins: { title: { display: true, text: 'Revenue vs Expenses', font: { size: 16, weight: 'bold' }, color: this.colors.primary }, tooltip: { callbacks: { label: function(context) { return context.dataset.label + ': ₹' + context.parsed.y.toLocaleString(); } } } }, interaction: { intersect: false, mode: 'index' } } }); } initBusinessSegmentsChart() { const canvas = document.getElementById('businessSegmentsChart'); if (!canvas) return; const ctx = canvas.getContext('2d'); const data = { labels: ['Fish Sales', 'Dry Fish Sales', 'Ornamental Fish', 'Fish Fry/Roast', 'Cutting Service'], datasets: [{ data: [35, 25, 15, 20, 5], backgroundColor: [ this.colors.primary, this.colors.secondary, this.colors.success, this.colors.info, this.colors.warning ], borderWidth: 2, borderColor: '#fff' }] }; this.charts.businessSegments = new Chart(ctx, { type: 'doughnut', data: data, options: { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: 'Revenue by Business Segment', font: { size: 16, weight: 'bold' }, color: this.colors.primary }, legend: { position: window.innerWidth <= 768 ? 'bottom' : 'right', labels: { padding: window.innerWidth <= 768 ? 10 : 20, usePointStyle: true } }, tooltip: { callbacks: { label: function(context) { const total = context.dataset.data.reduce((a, b) => a + b, 0); const percentage = ((context.parsed / total) * 100).toFixed(1); return context.label + ': ' + percentage + '%'; } } } }, cutout: '60%' } }); } initMonthlyTrendsChart() { const canvas = document.getElementById('monthlyTrendsChart'); if (!canvas) return; const ctx = canvas.getContext('2d'); const data = { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], datasets: [ { label: 'Sales', data: [12, 19, 15, 25, 22, 30, 28, 35, 32, 38, 40, 45], borderColor: this.colors.primary, backgroundColor: this.colors.primary + '20', tension: 0.4, fill: true }, { label: 'Profit', data: [5, 8, 6, 12, 10, 15, 13, 18, 16, 20, 22, 25], borderColor: this.colors.success, backgroundColor: this.colors.success + '20', tension: 0.4, fill: true } ] }; this.charts.monthlyTrends = new Chart(ctx, { type: 'line', data: data, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return '₹' + (value * 1000).toLocaleString(); } } } }, plugins: { title: { display: true, text: 'Monthly Trends', font: { size: 16, weight: 'bold' }, color: this.colors.primary }, tooltip: { callbacks: { label: function(context) { return context.dataset.label + ': ₹' + (context.parsed.y * 1000).toLocaleString(); } } } } } }); } initCashFlowChart() { const canvas = document.getElementById('cashFlowChart'); if (!canvas) return; const ctx = canvas.getContext('2d'); const data = { labels: ['Week 1', 'Week 2', 'Week 3', 'Week 4'], datasets: [{ label: 'Cash Flow', data: [15000, -8000, 22000, -5000], backgroundColor: function(context) { const value = context.parsed.y; return value >= 0 ? '#28a745' : '#dc3545'; }, borderColor: function(context) { const value = context.parsed.y; return value >= 0 ? '#28a745' : '#dc3545'; }, borderWidth: 1 }] }; this.charts.cashFlow = new Chart(ctx, { type: 'bar', data: data, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return '₹' + value.toLocaleString(); } } } }, plugins: { title: { display: true, text: 'Weekly Cash Flow', font: { size: 16, weight: 'bold' }, color: this.colors.primary }, legend: { display: false }, tooltip: { callbacks: { label: function(context) { const value = context.parsed.y; const type = value >= 0 ? 'Inflow' : 'Outflow'; return type + ': ₹' + Math.abs(value).toLocaleString(); } } } } } }); } initTopProductsChart() { const canvas = document.getElementById('topProductsChart'); if (!canvas) return; const ctx = canvas.getContext('2d'); const data = { labels: ['Fresh Pomfret', 'Dry Fish Mix', 'Gold Fish', 'Fish Fry', 'Cutting Service'], datasets: [{ label: 'Revenue', data: [25000, 18000, 12000, 15000, 8000], backgroundColor: [ this.colors.primary, this.colors.secondary, this.colors.success, this.colors.info, this.colors.warning ], borderWidth: 1, borderColor: '#fff' }] }; this.charts.topProducts = new Chart(ctx, { type: 'horizontalBar', data: data, options: { responsive: true, maintainAspectRatio: false, indexAxis: 'y', scales: { x: { beginAtZero: true, ticks: { callback: function(value) { return '₹' + value.toLocaleString(); } } } }, plugins: { title: { display: true, text: 'Top Products/Services', font: { size: 16, weight: 'bold' }, color: this.colors.primary }, legend: { display: false }, tooltip: { callbacks: { label: function(context) { return context.label + ': ₹' + context.parsed.x.toLocaleString(); } } } } } }); } // Utility method to update chart data updateChartData(chartName, newData) { if (this.charts[chartName]) { this.charts[chartName].data = newData; this.charts[chartName].update(); } } // Method to destroy a chart destroyChart(chartName) { if (this.charts[chartName]) { this.charts[chartName].destroy(); delete this.charts[chartName]; } } // Method to resize charts for mobile resizeChartsForMobile() { Object.values(this.charts).forEach(chart => { chart.resize(); }); } // Method to get chart data from API async loadChartData(endpoint) { try { const response = await fetch(`api/${endpoint}`); const data = await response.json(); return data; } catch (error) { console.error('Error loading chart data:', error); return null; } } // Method to refresh all charts with new data async refreshAllCharts() { // Show loading state document.querySelectorAll('.chart-container').forEach(container => { container.classList.add('loading'); }); try { // Load data for each chart const revenueExpensesData = await this.loadChartData('dashboard.php?action=revenue_expenses'); const businessSegmentsData = await this.loadChartData('dashboard.php?action=business_segments'); const monthlyTrendsData = await this.loadChartData('dashboard.php?action=monthly_trends'); const cashFlowData = await this.loadChartData('dashboard.php?action=cash_flow'); const topProductsData = await this.loadChartData('dashboard.php?action=top_products'); // Update charts with new data if (revenueExpensesData) this.updateChartData('revenueExpenses', revenueExpensesData); if (businessSegmentsData) this.updateChartData('businessSegments', businessSegmentsData); if (monthlyTrendsData) this.updateChartData('monthlyTrends', monthlyTrendsData); if (cashFlowData) this.updateChartData('cashFlow', cashFlowData); if (topProductsData) this.updateChartData('topProducts', topProductsData); } catch (error) { console.error('Error refreshing charts:', error); } finally { // Hide loading state document.querySelectorAll('.chart-container').forEach(container => { container.classList.remove('loading'); }); } } // Method to export chart as image exportChartAsImage(chartName, filename) { if (this.charts[chartName]) { const link = document.createElement('a'); link.download = filename || `${chartName}_chart.png`; link.href = this.charts[chartName].toBase64Image(); link.click(); } } // Method to create mini dashboard charts for mobile createMiniCharts() { const miniChartContainers = document.querySelectorAll('.mini-chart'); miniChartContainers.forEach((container, index) => { const canvas = container.querySelector('canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); // Sample data for mini charts const miniData = { labels: ['', '', '', '', ''], datasets: [{ data: [12, 19, 15, 25, 22], borderColor: this.colors.secondary, backgroundColor: this.colors.secondary + '40', borderWidth: 2, fill: true, tension: 0.4, pointRadius: 0 }] }; new Chart(ctx, { type: 'line', data: miniData, options: { responsive: true, maintainAspectRatio: false, scales: { x: { display: false }, y: { display: false } }, plugins: { legend: { display: false }, tooltip: { enabled: false } }, elements: { point: { radius: 0 } } } }); }); } } // Initialize charts when DOM is ready document.addEventListener('DOMContentLoaded', () => { window.kayalAquaCharts = new KayalAquaCharts(); }); // Handle window resize for chart responsiveness window.addEventListener('resize', () => { if (window.kayalAquaCharts) { window.kayalAquaCharts.resizeChartsForMobile(); } }); // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = KayalAquaCharts; } // Global function to initialize charts (called from main.js) function initializeCharts() { if (window.kayalAquaCharts) { window.kayalAquaCharts.refreshAllCharts(); } }