diff --git a/package-lock.json b/package-lock.json index 3dc0ea2..3dbed70 100755 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "doli-front", "version": "0.0.0", + "dependencies": { + "chart.js": "^4.5.1" + }, "devDependencies": { "vite": "^7.2.4" } @@ -453,6 +456,12 @@ "node": ">=18" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.55.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", @@ -810,6 +819,18 @@ "dev": true, "license": "MIT" }, + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/esbuild": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", diff --git a/package.json b/package.json index 4ff63ae..3f7e936 100755 --- a/package.json +++ b/package.json @@ -10,5 +10,8 @@ }, "devDependencies": { "vite": "^7.2.4" + }, + "dependencies": { + "chart.js": "^4.5.1" } } diff --git a/src/pages/DashboardPage.js b/src/pages/DashboardPage.js old mode 100755 new mode 100644 index 7b86cb1..5ddebde --- a/src/pages/DashboardPage.js +++ b/src/pages/DashboardPage.js @@ -1,40 +1,223 @@ import { auth } from '../services/auth.js'; +import { InvoiceModal } from '../components/InvoiceModal.js'; +import { + Chart, + BarController, + BarElement, + CategoryScale, + LinearScale, + Tooltip, + Legend +} from 'chart.js'; + +Chart.register(BarController, BarElement, CategoryScale, LinearScale, Tooltip, Legend); + +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; + +function getAuthHeaders() { + const token = localStorage.getItem('token'); + return { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }; +} + +async function fetchAllInvoices() { + const response = await fetch(`${API_BASE_URL}/api/Invoices?limit=500&page=1`, { + headers: getAuthHeaders() + }); + if (!response.ok) throw new Error('Error al cargar facturas'); + const data = await response.json(); + return Array.isArray(data) ? data : data.data || data.invoices || []; +} + +function calcKPIs(invoices) { + const total = invoices.length; + const totalBilled = invoices.reduce((sum, inv) => sum + (inv.total || 0), 0); + const paid = invoices.filter(inv => inv.status === 'paid').length; + const unpaid = invoices.filter(inv => inv.status === 'unpaid').length; + const draft = invoices.filter(inv => inv.status === 'draft').length; + const paidRate = total > 0 ? ((paid / total) * 100).toFixed(1) : '0.0'; + const pendingAmount = invoices.reduce((s, inv) => s + (inv.remainToPay || 0), 0); + return { total, totalBilled, paid, unpaid, draft, paidRate, pendingAmount }; +} + +function calcQuarterly(invoices) { + const year = new Date().getFullYear(); + const q = [0, 0, 0, 0]; + invoices.forEach(inv => { + if (!inv.date || !inv.total) return; + const d = new Date(inv.date); + if (d.getFullYear() !== year) return; + const m = d.getMonth(); + const idx = m <= 2 ? 0 : m <= 5 ? 1 : m <= 8 ? 2 : 3; + q[idx] += inv.total; + }); + return q; +} + +function formatCurrency(amount) { + return new Intl.NumberFormat('es-ES', { + style: 'currency', currency: 'EUR', maximumFractionDigits: 0 + }).format(amount || 0); +} + +function formatDate(dateStr) { + if (!dateStr) return '—'; + return new Date(dateStr).toLocaleDateString('es-ES', { + day: '2-digit', month: '2-digit', year: 'numeric' + }); +} + +function getStatusBadge(status) { + const map = { + draft: { label: 'Borrador', cls: 'badge-draft' }, + unpaid: { label: 'Pte. Pago', cls: 'badge-unpaid' }, + paid: { label: 'Pagada', cls: 'badge-paid' }, + }; + const s = map[status] || { label: status || '—', cls: 'badge-draft' }; + return `${s.label}`; +} + +function statusDotStyle(status) { + const map = { + paid: { bg: '#f0fdf4', color: '#16a34a' }, + unpaid: { bg: '#fffbeb', color: '#b45309' }, + draft: { bg: '#f3f4f6', color: '#6b7280' }, + }; + const s = map[status] || map.draft; + return `background:${s.bg};color:${s.color}`; +} + +function openInvoiceModal(invoiceId) { + const modal = InvoiceModal(invoiceId, null, () => {}); + document.body.appendChild(modal); +} export function renderDashboard() { - const user = auth.getUser(); + const user = auth.getUser(); + const userName = user?.identifier || user?.email || user?.username || 'Usuario'; + const year = new Date().getFullYear(); + const container = document.createElement('div'); container.className = 'dashboard'; container.innerHTML = /*html*/` -
${user?.email || 'Usuario'}
+Resumen de facturacion · ${year}