2026-02-03 19:46:02 +00:00
|
|
|
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: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
|
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
|
|
|
|
|
<line x1="8" y1="21" x2="16" y2="21"></line>
|
|
|
|
|
<line x1="12" y1="17" x2="12" y2="21"></line>
|
|
|
|
|
</svg>`,
|
|
|
|
|
iconBg: '#374151',
|
|
|
|
|
valueColor: '#3b82f6',
|
|
|
|
|
subtitle: 'Este mes',
|
|
|
|
|
formatValue: (val) => `${val.toLocaleString('es-ES', { minimumFractionDigits: 2 })} €`
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'facturasEmitidas',
|
|
|
|
|
title: 'Facturas Emitidas',
|
|
|
|
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
|
|
|
|
<polyline points="14 2 14 8 20 8"></polyline>
|
|
|
|
|
<line x1="12" y1="18" x2="12" y2="12"></line>
|
|
|
|
|
<line x1="9" y1="15" x2="15" y2="15"></line>
|
|
|
|
|
</svg>`,
|
|
|
|
|
iconBg: '#22c55e',
|
|
|
|
|
valueColor: '#1f2937',
|
|
|
|
|
subtitle: 'Este mes',
|
|
|
|
|
formatValue: (val) => val.toString()
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'clientesActivos',
|
|
|
|
|
title: 'Clientes Activos',
|
|
|
|
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
|
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
|
|
|
|
|
<circle cx="9" cy="7" r="4"></circle>
|
|
|
|
|
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
|
|
|
|
|
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
|
|
|
|
|
</svg>`,
|
|
|
|
|
iconBg: '#f59e0b',
|
|
|
|
|
valueColor: '#1f2937',
|
|
|
|
|
subtitle: 'Con factura este mes',
|
|
|
|
|
formatValue: (val) => val.toString()
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'pendienteCobro',
|
|
|
|
|
title: 'Pendiente de Cobro',
|
|
|
|
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
|
<circle cx="12" cy="12" r="10"></circle>
|
|
|
|
|
<polyline points="12 6 12 12 16 14"></polyline>
|
|
|
|
|
</svg>`,
|
|
|
|
|
iconBg: '#8b5cf6',
|
|
|
|
|
valueColor: '#1f2937',
|
|
|
|
|
subtitle: 'Facturas vencidas',
|
|
|
|
|
formatValue: (val) => val.toString()
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Función para crear una tarjeta individual
|
|
|
|
|
function createStatCard(config, value) {
|
|
|
|
|
return `
|
|
|
|
|
<div class="stat-card" data-stat-id="${config.id}">
|
|
|
|
|
<div class="stat-icon" style="background-color: ${config.iconBg}">
|
|
|
|
|
${config.icon}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-content">
|
|
|
|
|
<span class="stat-title">${config.title}</span>
|
|
|
|
|
<span class="stat-value" style="color: ${config.valueColor}">${config.formatValue(value)}</span>
|
|
|
|
|
<span class="stat-subtitle">${config.subtitle}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 = {}) {
|
2026-01-23 18:52:47 +00:00
|
|
|
const container = document.createElement('div');
|
|
|
|
|
container.className = 'dashboard-container';
|
|
|
|
|
|
2026-02-03 19:46:02 +00:00
|
|
|
// Datos por defecto (puedes pasarlos como parámetro)
|
|
|
|
|
const data = {
|
|
|
|
|
totalFacturado: 50.00,
|
|
|
|
|
facturasEmitidas: 0,
|
|
|
|
|
clientesActivos: 0,
|
|
|
|
|
pendienteCobro: 0,
|
|
|
|
|
...initialData
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-23 18:52:47 +00:00
|
|
|
container.innerHTML = /*html*/`
|
2026-02-03 19:46:02 +00:00
|
|
|
<style>
|
|
|
|
|
.dashboard-container {
|
|
|
|
|
padding: 24px;
|
|
|
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dashboard-text h2 {
|
|
|
|
|
margin: 0 0 8px 0;
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dashboard-text p {
|
|
|
|
|
margin: 0 0 24px 0;
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.data-container {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
padding: 20px 24px;
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-width: 200px;
|
|
|
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
|
|
|
border: 1px solid #f3f4f6;
|
|
|
|
|
transition: box-shadow 0.2s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card:hover {
|
|
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-icon {
|
|
|
|
|
width: 48px;
|
|
|
|
|
height: 48px;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
color: white;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-icon svg {
|
|
|
|
|
width: 24px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-content {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-title {
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-value {
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
line-height: 1.2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-subtitle {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.charts-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chart-wrapper {
|
|
|
|
|
flex: 1;
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
padding: 24px;
|
|
|
|
|
border: 1px solid #f3f4f6;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chart-title {
|
|
|
|
|
margin: 0 0 16px 0;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.data-container {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card {
|
|
|
|
|
min-width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.charts-row {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
<div class="dashboard-text">
|
|
|
|
|
<h2>Dashboard</h2>
|
|
|
|
|
<p>Resumen de facturación y estado de tus facturas</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="data-container" id="stats-container">
|
|
|
|
|
${renderStatsCards(data)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="charts-row" id="charts-row">
|
|
|
|
|
</div>
|
2026-01-23 18:52:47 +00:00
|
|
|
`;
|
|
|
|
|
|
2026-02-03 19:46:02 +00:00
|
|
|
const chartsRow = container.querySelector('#charts-row');
|
|
|
|
|
chartsRow.appendChild(renderInvoiceStatusChart());
|
|
|
|
|
chartsRow.appendChild(renderBarChart());
|
|
|
|
|
|
2026-01-23 18:52:47 +00:00
|
|
|
return container;
|
2026-02-03 19:46:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|