From a78ed7268c48ad42ae870376311a6d4cb63722c4 Mon Sep 17 00:00:00 2001
From: Olivia Mestre Llobell <59211313+BYolivia@users.noreply.github.com>
Date: Tue, 3 Feb 2026 20:46:02 +0100
Subject: [PATCH] Add charts for invoice status and bar data; update dashboard
layout and styles
---
package-lock.json | 21 ++
package.json | 3 +
src/components/charts/BarChart.js | 79 ++++++
src/components/charts/InvoiceStatusChart.js | 84 ++++++
src/components/dashboard.js | 268 +++++++++++++++++++-
5 files changed, 451 insertions(+), 4 deletions(-)
create mode 100644 src/components/charts/BarChart.js
create mode 100644 src/components/charts/InvoiceStatusChart.js
diff --git a/package-lock.json b/package-lock.json
index 3dc0ea2..3dbed70 100644
--- 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 100644
--- 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/components/charts/BarChart.js b/src/components/charts/BarChart.js
new file mode 100644
index 0000000..ce4d9c2
--- /dev/null
+++ b/src/components/charts/BarChart.js
@@ -0,0 +1,79 @@
+import { Chart, BarController, BarElement, CategoryScale, LinearScale, Tooltip, Legend } from 'chart.js';
+
+Chart.register(BarController, BarElement, CategoryScale, LinearScale, Tooltip, Legend);
+
+const DEFAULT_DATA = {
+ labels: ['T1', 'T2', 'T3', 'T4'],
+ values: [12500.00, 18300.50, 9800.75, 15400.00]
+};
+
+export function renderBarChart(data = DEFAULT_DATA) {
+ const wrapper = document.createElement('div');
+ wrapper.className = 'chart-wrapper';
+
+ wrapper.innerHTML = `
+
Ingresos por trimestre
+
+ `;
+
+ const canvas = wrapper.querySelector('canvas');
+
+ const chart = new Chart(canvas, {
+ type: 'bar',
+ data: {
+ labels: data.labels,
+ datasets: [{
+ label: 'Ganado (€)',
+ data: data.values,
+ backgroundColor: '#3b82f6',
+ borderRadius: 6,
+ borderSkipped: false
+ }]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: true,
+ plugins: {
+ legend: {
+ display: false
+ },
+ tooltip: {
+ callbacks: {
+ label: (ctx) => {
+ const value = ctx.parsed.y.toLocaleString('es-ES', { minimumFractionDigits: 2 });
+ return ` ${value} €`;
+ }
+ }
+ }
+ },
+ scales: {
+ y: {
+ beginAtZero: true,
+ ticks: {
+ callback: (val) => `${val.toLocaleString('es-ES')} €`
+ },
+ grid: {
+ color: '#f3f4f6'
+ }
+ },
+ x: {
+ grid: {
+ display: false
+ }
+ }
+ }
+ }
+ });
+
+ wrapper._chart = chart;
+ return wrapper;
+}
+
+export function updateBarChart(wrapper, newData) {
+ const chart = wrapper._chart;
+ if (!chart) return;
+
+ chart.data.labels = newData.labels;
+ chart.data.datasets[0].data = newData.values;
+ chart.update();
+}
diff --git a/src/components/charts/InvoiceStatusChart.js b/src/components/charts/InvoiceStatusChart.js
new file mode 100644
index 0000000..db545a7
--- /dev/null
+++ b/src/components/charts/InvoiceStatusChart.js
@@ -0,0 +1,84 @@
+import { Chart, DoughnutController, ArcElement, Tooltip, Legend } from 'chart.js';
+
+Chart.register(DoughnutController, ArcElement, Tooltip, Legend);
+
+const STATUS_LABELS = {
+ draft: 'Borrador',
+ unpaid: 'Pendiente',
+ paid: 'Pagada'
+};
+
+const STATUS_COLORS = {
+ draft: '#6b7280',
+ unpaid: '#f59e0b',
+ paid: '#22c55e'
+};
+
+const DEFAULT_DATA = {
+ draft: 1200.50,
+ unpaid: 3400.00,
+ paid: 8500.75
+};
+
+export function renderInvoiceStatusChart(data = DEFAULT_DATA) {
+ const wrapper = document.createElement('div');
+ wrapper.className = 'chart-wrapper';
+
+ wrapper.innerHTML = `
+ Facturas por estado
+
+ `;
+
+ const canvas = wrapper.querySelector('canvas');
+ const statuses = Object.keys(data);
+
+ const chart = new Chart(canvas, {
+ type: 'doughnut',
+ data: {
+ labels: statuses.map(s => STATUS_LABELS[s] || s),
+ datasets: [{
+ data: statuses.map(s => data[s]),
+ backgroundColor: statuses.map(s => STATUS_COLORS[s] || '#9ca3af'),
+ borderWidth: 2,
+ borderColor: '#ffffff'
+ }]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: true,
+ plugins: {
+ legend: {
+ position: 'bottom',
+ labels: {
+ padding: 16,
+ usePointStyle: true,
+ pointStyleWidth: 10,
+ font: { size: 13 }
+ }
+ },
+ tooltip: {
+ callbacks: {
+ label: (ctx) => {
+ const value = ctx.parsed.toLocaleString('es-ES', { minimumFractionDigits: 2 });
+ return ` ${ctx.label}: ${value} €`;
+ }
+ }
+ }
+ }
+ }
+ });
+
+ wrapper._chart = chart;
+ return wrapper;
+}
+
+export function updateInvoiceStatusChart(wrapper, newData) {
+ const chart = wrapper._chart;
+ if (!chart) return;
+
+ const statuses = Object.keys(newData);
+ chart.data.labels = statuses.map(s => STATUS_LABELS[s] || s);
+ chart.data.datasets[0].data = statuses.map(s => newData[s]);
+ chart.data.datasets[0].backgroundColor = statuses.map(s => STATUS_COLORS[s] || '#9ca3af');
+ chart.update();
+}
diff --git a/src/components/dashboard.js b/src/components/dashboard.js
index 3dfdb73..8c42603 100644
--- a/src/components/dashboard.js
+++ b/src/components/dashboard.js
@@ -1,11 +1,271 @@
-export function renderDashboard() {
+import { renderInvoiceStatusChart } from './charts/InvoiceStatusChart.js';
+import { renderBarChart } from './charts/BarChart.js';
+
+// Configuración de las tarjetas de estadísticas
+const statsConfig = [
+ {
+ id: 'totalFacturado',
+ title: 'Total Facturado',
+ icon: ``,
+ iconBg: '#374151',
+ valueColor: '#3b82f6',
+ subtitle: 'Este mes',
+ formatValue: (val) => `${val.toLocaleString('es-ES', { minimumFractionDigits: 2 })} €`
+ },
+ {
+ id: 'facturasEmitidas',
+ title: 'Facturas Emitidas',
+ icon: ``,
+ iconBg: '#22c55e',
+ valueColor: '#1f2937',
+ subtitle: 'Este mes',
+ formatValue: (val) => val.toString()
+ },
+ {
+ id: 'clientesActivos',
+ title: 'Clientes Activos',
+ icon: ``,
+ iconBg: '#f59e0b',
+ valueColor: '#1f2937',
+ subtitle: 'Con factura este mes',
+ formatValue: (val) => val.toString()
+ },
+ {
+ id: 'pendienteCobro',
+ title: 'Pendiente de Cobro',
+ icon: ``,
+ iconBg: '#8b5cf6',
+ valueColor: '#1f2937',
+ subtitle: 'Facturas vencidas',
+ formatValue: (val) => val.toString()
+ }
+];
+
+// Función para crear una tarjeta individual
+function createStatCard(config, value) {
+ return `
+
+
+ ${config.icon}
+
+
+ ${config.title}
+ ${config.formatValue(value)}
+ ${config.subtitle}
+
+
+ `;
+}
+
+// Función para renderizar todas las tarjetas
+export function renderStatsCards(data = {}) {
+ const defaultData = {
+ totalFacturado: 0,
+ facturasEmitidas: 0,
+ clientesActivos: 0,
+ pendienteCobro: 0
+ };
+
+ const statsData = { ...defaultData, ...data };
+
+ return statsConfig
+ .map(config => createStatCard(config, statsData[config.id]))
+ .join('');
+}
+
+// Función para actualizar un valor específico
+export function updateStatValue(containerId, statId, newValue) {
+ const container = document.querySelector(`#${containerId} [data-stat-id="${statId}"] .stat-value`);
+ if (container) {
+ const config = statsConfig.find(c => c.id === statId);
+ if (config) {
+ container.textContent = config.formatValue(newValue);
+ }
+ }
+}
+
+// Dashboard completo con estilos incluidos
+export function renderDashboard(initialData = {}) {
const container = document.createElement('div');
container.className = 'dashboard-container';
+ // Datos por defecto (puedes pasarlos como parámetro)
+ const data = {
+ totalFacturado: 50.00,
+ facturasEmitidas: 0,
+ clientesActivos: 0,
+ pendienteCobro: 0,
+ ...initialData
+ };
+
container.innerHTML = /*html*/`
- Welcome to the Dashboard
- This is the main dashboard page.
+
+
+
+
Dashboard
+
Resumen de facturación y estado de tus facturas
+
+
+
+ ${renderStatsCards(data)}
+
+
+
+
`;
+ const chartsRow = container.querySelector('#charts-row');
+ chartsRow.appendChild(renderInvoiceStatusChart());
+ chartsRow.appendChild(renderBarChart());
+
return container;
-}
\ No newline at end of file
+}
+
+// Ejemplo de uso:
+// import { renderDashboard, updateStatValue } from './dashboard-stats.js';
+//
+// // Renderizar con datos iniciales
+// const dashboard = renderDashboard({
+// totalFacturado: 50.00,
+// facturasEmitidas: 0,
+// clientesActivos: 0,
+// pendienteCobro: 0
+// });
+// document.body.appendChild(dashboard);
+//
+// // Actualizar un valor dinámicamente
+// updateStatValue('stats-container', 'totalFacturado', 1500.50);
\ No newline at end of file