Spaces:
Running
Running
File size: 5,512 Bytes
188bf54 28b0bde 188bf54 28b0bde 188bf54 28b0bde 188bf54 28b0bde 188bf54 28b0bde 188bf54 28b0bde 188bf54 28b0bde 188bf54 28b0bde 188bf54 28b0bde 188bf54 28b0bde 188bf54 28b0bde 188bf54 28b0bde 188bf54 28b0bde 188bf54 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | import React, { useState, useEffect } from 'react';
import { db } from '../../firebase/config';
import { ref, onValue } from 'firebase/database';
import {
Chart as ChartJS, CategoryScale, LinearScale, PointElement,
LineElement, Title, Tooltip, Legend, BarElement
} from 'chart.js';
import { Line, Bar } from 'react-chartjs-2';
import { TrendingUp, Package, Users as UsersIcon, Calendar } from 'lucide-react';
ChartJS.register(
CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, BarElement
);
export default function Reports() {
const [salesData, setSalesData] = useState([0, 0, 0, 0, 0, 0, 0]);
const [topProducts, setTopProducts] = useState({ labels: [], data: [] });
const [stats, setStats] = useState({ totalSales: 0, orderCount: 0, avgTicket: 0 });
useEffect(() => {
onValue(ref(db, 'orders'), (snapshot) => {
const data = snapshot.val();
if (data) {
const orders = Object.values(data);
const weeklySales = [0, 0, 0, 0, 0, 0, 0];
const productCounts = {};
let total = 0;
orders.forEach(order => {
if (order.status === 'completed') {
total += order.total;
// Simple day mapping (last 7 days logic would be better but this is for demo)
const day = new Date(order.timestamp).getDay(); // 0-6
const index = (day + 6) % 7; // Map Mon-Sun
weeklySales[index] += order.total;
order.items.forEach(item => {
productCounts[item.name] = (productCounts[item.name] || 0) + item.qty;
});
}
});
setSalesData(weeklySales);
setStats({
totalSales: total,
orderCount: orders.filter(o => o.status === 'completed').length,
avgTicket: total / (orders.filter(o => o.status === 'completed').length || 1)
});
const sortedProducts = Object.entries(productCounts)
.sort((a,b) => b[1] - a[1])
.slice(0, 5);
setTopProducts({
labels: sortedProducts.map(p => p[0]),
data: sortedProducts.map(p => p[1])
});
}
});
}, []);
const lineData = {
labels: ['Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb', 'Dom'],
datasets: [{
label: 'Ventas ($)',
data: salesData,
borderColor: '#FF5A5F',
backgroundColor: 'rgba(255, 90, 95, 0.2)',
fill: true,
tension: 0.4
}]
};
const barData = {
labels: topProducts.labels,
datasets: [{
label: 'Unidades Vendidas',
data: topProducts.data,
backgroundColor: 'rgba(0, 166, 153, 0.7)',
borderRadius: 8
}]
};
const options = {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { labels: { color: '#9595a8' } } },
scales: {
x: { ticks: { color: '#9595a8' }, grid: { color: 'rgba(255,255,255,0.05)' } },
y: { ticks: { color: '#9595a8' }, grid: { color: 'rgba(255,255,255,0.05)' } }
}
};
return (
<div className="animate-fade-in">
<header style={{ marginBottom: '2.5rem' }}>
<h2 className="text-gradient" style={{ fontSize: '2rem', fontWeight: '800' }}>Análisis de Negocio</h2>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '1.5rem', marginTop: '1.5rem' }}>
<div className="glass-card" style={{ padding: '1.25rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', color: 'var(--text-muted)', fontSize: '0.8rem' }}>Ventas Totales <TrendingUp size={16} /></div>
<div style={{ fontSize: '1.5rem', fontWeight: '800', marginTop: '0.5rem' }}>${stats.totalSales.toFixed(2)}</div>
</div>
<div className="glass-card" style={{ padding: '1.25rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', color: 'var(--text-muted)', fontSize: '0.8rem' }}>Pedidos <Package size={16} /></div>
<div style={{ fontSize: '1.5rem', fontWeight: '800', marginTop: '0.5rem' }}>{stats.orderCount}</div>
</div>
<div className="glass-card" style={{ padding: '1.25rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', color: 'var(--text-muted)', fontSize: '0.8rem' }}>Ticket Promedio <Calendar size={16} /></div>
<div style={{ fontSize: '1.5rem', fontWeight: '800', marginTop: '0.5rem' }}>${stats.avgTicket.toFixed(2)}</div>
</div>
</div>
</header>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))', gap: '2rem' }}>
<div className="glass-card" style={{ height: '350px', display: 'flex', flexDirection: 'column' }}>
<h3 style={{ marginBottom: '1.5rem', fontSize: '1rem', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
Ventas por Día de la Semana
</h3>
<div style={{ flex: 1, position: 'relative' }}>
<Line data={lineData} options={options} />
</div>
</div>
<div className="glass-card" style={{ height: '350px', display: 'flex', flexDirection: 'column' }}>
<h3 style={{ marginBottom: '1.5rem', fontSize: '1rem' }}>Productos Estrella</h3>
<div style={{ flex: 1, position: 'relative' }}>
<Bar data={barData} options={options} />
</div>
</div>
</div>
</div>
);
}
|