// Local cache for rates to enable instant client-side calculation let cachedRates = { USD: 0, EUR: 0, GBP: 0, AUD: 0, JPY: 0, AED: 0, SAR: 0, INR: 0, CNY: 0, QAR: 0 }; // Formats number values with commas function formatNumber(num) { return num.toString().replace(/\B(?=(\d{3})+(?!\n))/g, ","); } // Simple digit counter animation function animateValue(id, start, end, duration) { const obj = document.getElementById(id); if (!obj) return; if (start === end) { obj.textContent = formatNumber(end); return; } let startTimestamp = null; const step = (timestamp) => { if (!startTimestamp) startTimestamp = timestamp; const progress = Math.min((timestamp - startTimestamp) / duration, 1); obj.textContent = formatNumber(Math.floor(progress * (end - start) + start)); if (progress < 1) { window.requestAnimationFrame(step); } }; window.requestAnimationFrame(step); } // Fetch dashboard data async function fetchStats() { try { const response = await fetch('/api/stats'); if (!response.ok) throw new Error('API down'); const data = await response.json(); // Update stats with nice animations const subCount = data.subscribers || 0; const activeSubCount = data.active_subscriptions || 0; const thresholdCount = data.active_thresholds || 0; const currentSubs = parseInt(document.getElementById('stats-subscribers').textContent) || 0; const currentActive = parseInt(document.getElementById('stats-subscriptions').textContent) || 0; const currentThresholds = parseInt(document.getElementById('stats-thresholds').textContent) || 0; animateValue('stats-subscribers', currentSubs, subCount, 800); animateValue('stats-subscriptions', currentActive, activeSubCount, 800); animateValue('stats-thresholds', currentThresholds, thresholdCount, 800); // Update exchange rates if (data.rates) { Object.keys(data.rates).forEach(cur => { const rate = data.rates[cur] || 0; cachedRates[cur] = rate; const valEl = document.getElementById(`val-${cur.toLowerCase()}`); if (valEl) { valEl.innerHTML = `${rate.toFixed(2)} LKR`; } }); // Re-trigger calculator logic calculateConversion(); } // Update System Status indicators const statusTextEl = document.getElementById('system-status-text'); const pulseEl = document.querySelector('.pulse-dot'); if (data.system_status === 'online' && data.db_status === 'connected') { statusTextEl.textContent = 'SYSTEM ACTIVE'; pulseEl.style.backgroundColor = 'var(--accent-green)'; pulseEl.style.boxShadow = '0 0 10px var(--accent-green)'; } else if (data.db_status === 'error') { statusTextEl.textContent = 'DATABASE ERROR'; pulseEl.style.backgroundColor = 'var(--accent-pink)'; pulseEl.style.boxShadow = '0 0 10px var(--accent-pink)'; } else { statusTextEl.textContent = 'SYSTEM MAINTENANCE'; pulseEl.style.backgroundColor = 'var(--accent-purple)'; pulseEl.style.boxShadow = '0 0 10px var(--accent-purple)'; } } catch (error) { console.error('Error fetching dashboard stats:', error); // Display offline/error states const statusTextEl = document.getElementById('system-status-text'); const pulseEl = document.querySelector('.pulse-dot'); if (statusTextEl && pulseEl) { statusTextEl.textContent = 'SYSTEM OFFLINE'; pulseEl.style.backgroundColor = 'var(--accent-pink)'; pulseEl.style.boxShadow = '0 0 10px var(--accent-pink)'; } } } // Handle currency conversion calculations function calculateConversion() { const amountInput = document.getElementById('convert-amount'); const currencySelect = document.getElementById('convert-from'); const resultOutput = document.getElementById('conversion-output'); if (!amountInput || !currencySelect || !resultOutput) return; const amount = parseFloat(amountInput.value) || 0; const fromCur = currencySelect.value; const rate = cachedRates[fromCur] || 0; // Apply visual prefixes const prefixEl = document.querySelector('.converter-form .prefix'); if (prefixEl) { if (fromCur === 'USD' || fromCur === 'AUD') { prefixEl.textContent = '$'; } else if (fromCur === 'GBP') { prefixEl.textContent = '£'; } else if (fromCur === 'EUR') { prefixEl.textContent = '€'; } else if (fromCur === 'JPY' || fromCur === 'CNY') { prefixEl.textContent = '¥'; } else if (fromCur === 'INR') { prefixEl.textContent = '₹'; } else if (fromCur === 'AED') { prefixEl.textContent = 'DH'; } else if (fromCur === 'SAR') { prefixEl.textContent = 'SR'; } else if (fromCur === 'QAR') { prefixEl.textContent = 'QR'; } else { prefixEl.textContent = ''; } } if (amount <= 0 || rate === 0) { resultOutput.innerHTML = `0.00 LKR`; return; } const total = amount * rate; resultOutput.innerHTML = `${total.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} LKR`; } // Chart.js Trends Visualization Logic let trendsChart = null; const currencyColors = { USD: { border: '#06B6D4', glow: 'rgba(6, 182, 212, 0.25)', fill: 'rgba(6, 182, 212, 0.05)' }, EUR: { border: '#8B5CF6', glow: 'rgba(139, 92, 246, 0.25)', fill: 'rgba(139, 92, 246, 0.05)' }, GBP: { border: '#EC4899', glow: 'rgba(236, 72, 153, 0.25)', fill: 'rgba(236, 72, 153, 0.05)' }, AUD: { border: '#3B82F6', glow: 'rgba(59, 130, 246, 0.25)', fill: 'rgba(59, 130, 246, 0.05)' }, JPY: { border: '#10B981', glow: 'rgba(16, 185, 129, 0.25)', fill: 'rgba(16, 185, 129, 0.05)' }, AED: { border: '#F59E0B', glow: 'rgba(245, 158, 11, 0.25)', fill: 'rgba(245, 158, 11, 0.05)' }, SAR: { border: '#14B8A6', glow: 'rgba(20, 184, 166, 0.25)', fill: 'rgba(20, 184, 166, 0.05)' }, INR: { border: '#EF4444', glow: 'rgba(239, 68, 68, 0.25)', fill: 'rgba(239, 68, 68, 0.05)' }, CNY: { border: '#F43F5E', glow: 'rgba(244, 63, 94, 0.25)', fill: 'rgba(244, 63, 94, 0.05)' }, QAR: { border: '#6366F1', glow: 'rgba(99, 102, 241, 0.25)', fill: 'rgba(99, 102, 241, 0.05)' } }; async function updateTrendsChart() { const currencySelect = document.getElementById('chart-currency-select'); const daysSelect = document.getElementById('chart-days-select'); const canvas = document.getElementById('trends-chart'); if (!currencySelect || !daysSelect || !canvas) return; const currency = currencySelect.value; const days = parseInt(daysSelect.value) || 30; try { const response = await fetch(`/api/history?days=${days}`); if (!response.ok) throw new Error('API down'); const historyData = await response.json(); const records = historyData[currency] || []; const labels = records.map(r => r.date); const dataPoints = records.map(r => r.rate); const theme = currencyColors[currency] || currencyColors.USD; const ctx = canvas.getContext('2d'); // Dynamic gradient area under line const gradient = ctx.createLinearGradient(0, 0, 0, 300); gradient.addColorStop(0, theme.glow); gradient.addColorStop(1, 'rgba(11, 15, 25, 0)'); const chartData = { labels: labels, datasets: [{ label: `${currency} to LKR`, data: dataPoints, borderColor: theme.border, borderWidth: 3, backgroundColor: gradient, fill: true, tension: 0.4, pointBackgroundColor: theme.border, pointBorderColor: '#FFFFFF', pointBorderWidth: 1.5, pointRadius: labels.length > 15 ? 2.5 : 4, pointHoverRadius: 6, pointHoverBackgroundColor: theme.border, pointHoverBorderColor: '#FFFFFF', pointHoverBorderWidth: 2 }] }; if (trendsChart) { trendsChart.data = chartData; trendsChart.update(); } else { trendsChart = new Chart(ctx, { type: 'line', data: chartData, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { backgroundColor: 'rgba(17, 24, 39, 0.95)', titleColor: '#F3F4F6', bodyColor: '#F3F4F6', borderColor: 'rgba(255, 255, 255, 0.12)', borderWidth: 1, padding: 12, displayColors: false, callbacks: { label: function(context) { return `1 ${currency} = ${context.parsed.y.toFixed(2)} LKR`; } } } }, scales: { x: { grid: { color: 'rgba(255, 255, 255, 0.03)' }, ticks: { color: '#9CA3AF', font: { family: 'Plus Jakarta Sans', size: 11 }, maxRotation: 45, autoSkip: true, maxTicksLimit: 10 } }, y: { grid: { color: 'rgba(255, 255, 255, 0.03)' }, ticks: { color: '#9CA3AF', font: { family: 'Plus Jakarta Sans', size: 11 }, callback: function(value) { return value.toFixed(1) + ' LKR'; } } } } } }); } } catch (error) { console.error('Error drawing chart:', error); } } // Event Listeners initialization document.addEventListener('DOMContentLoaded', () => { // Initial fetch fetchStats(); // Setup inputs const amountInput = document.getElementById('convert-amount'); const currencySelect = document.getElementById('convert-from'); if (amountInput) amountInput.addEventListener('input', calculateConversion); if (currencySelect) currencySelect.addEventListener('change', calculateConversion); // Setup Chart dynamic bindings updateTrendsChart(); const chartCurrency = document.getElementById('chart-currency-select'); const chartDays = document.getElementById('chart-days-select'); if (chartCurrency) chartCurrency.addEventListener('change', updateTrendsChart); if (chartDays) chartDays.addEventListener('change', updateTrendsChart); // Auto-update dashboard metrics and rates every 60 seconds setInterval(fetchStats, 60000); });