[MCH-FE] feat: Connect Dashboard to real API
Replace hardcoded mock data with TanStack Query hooks: - dashboardApi.getStats() for stats cards (sales, orders, customers, fiado) - ordersApi.getAll() for recent orders list - inventoryApi.getLowStock() for low stock alerts Add loading spinners and error states for each section. Add TypeScript interfaces for API response types. Add formatCurrency helper for MXN formatting. Add Spanish labels for order status. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c8cf78e0db
commit
2c4db175c2
@ -1,3 +1,4 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import {
|
import {
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
ShoppingCart,
|
ShoppingCart,
|
||||||
@ -5,26 +6,35 @@ import {
|
|||||||
CreditCard,
|
CreditCard,
|
||||||
Package,
|
Package,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
|
Loader2,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
import { dashboardApi, ordersApi, inventoryApi } from '../lib/api';
|
||||||
|
|
||||||
const stats = [
|
interface DashboardStats {
|
||||||
{ name: 'Ventas Hoy', value: '$1,240', change: '+12%', icon: TrendingUp, color: 'green' },
|
salesToday: number;
|
||||||
{ name: 'Pedidos', value: '23', change: '+5', icon: ShoppingCart, color: 'blue' },
|
ordersCount: number;
|
||||||
{ name: 'Clientes', value: '156', change: '+3', icon: Users, color: 'purple' },
|
customersCount: number;
|
||||||
{ name: 'Fiados Pendientes', value: '$2,100', change: '-$450', icon: CreditCard, color: 'orange' },
|
pendingFiado: number;
|
||||||
];
|
salesChange: string;
|
||||||
|
ordersChange: string;
|
||||||
|
customersChange: string;
|
||||||
|
fiadoChange: string;
|
||||||
|
}
|
||||||
|
|
||||||
const recentOrders = [
|
interface Order {
|
||||||
{ id: 'MCH-001', customer: 'Maria Lopez', total: 156.00, status: 'ready', time: '10:30 AM' },
|
id: string;
|
||||||
{ id: 'MCH-002', customer: 'Juan Perez', total: 89.50, status: 'preparing', time: '10:45 AM' },
|
customer: { name: string } | null;
|
||||||
{ id: 'MCH-003', customer: 'Ana Garcia', total: 234.00, status: 'pending', time: '11:00 AM' },
|
total: number;
|
||||||
];
|
status: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
const lowStockProducts = [
|
interface LowStockProduct {
|
||||||
{ name: 'Coca-Cola 600ml', stock: 5, minStock: 10 },
|
id: string;
|
||||||
{ name: 'Pan Bimbo', stock: 2, minStock: 5 },
|
name: string;
|
||||||
{ name: 'Leche Lala 1L', stock: 3, minStock: 8 },
|
stock: number;
|
||||||
];
|
minStock: number;
|
||||||
|
}
|
||||||
|
|
||||||
const statusColors: Record<string, string> = {
|
const statusColors: Record<string, string> = {
|
||||||
pending: 'bg-yellow-100 text-yellow-800',
|
pending: 'bg-yellow-100 text-yellow-800',
|
||||||
@ -33,7 +43,111 @@ const statusColors: Record<string, string> = {
|
|||||||
completed: 'bg-gray-100 text-gray-800',
|
completed: 'bg-gray-100 text-gray-800',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const statusLabels: Record<string, string> = {
|
||||||
|
pending: 'Pendiente',
|
||||||
|
preparing: 'Preparando',
|
||||||
|
ready: 'Listo',
|
||||||
|
completed: 'Completado',
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatCurrency(amount: number): string {
|
||||||
|
return new Intl.NumberFormat('es-MX', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'MXN',
|
||||||
|
}).format(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LoadingSpinner() {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center p-8">
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin text-primary-600" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ErrorMessage({ message }: { message: string }) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center p-8 text-red-600">
|
||||||
|
<AlertCircle className="h-5 w-5 mr-2" />
|
||||||
|
<span>{message}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
|
// Fetch dashboard stats
|
||||||
|
const {
|
||||||
|
data: statsData,
|
||||||
|
isLoading: statsLoading,
|
||||||
|
error: statsError,
|
||||||
|
} = useQuery({
|
||||||
|
queryKey: ['dashboard-stats'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await dashboardApi.getStats();
|
||||||
|
return response.data as DashboardStats;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch recent orders
|
||||||
|
const {
|
||||||
|
data: ordersData,
|
||||||
|
isLoading: ordersLoading,
|
||||||
|
error: ordersError,
|
||||||
|
} = useQuery({
|
||||||
|
queryKey: ['recent-orders'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await ordersApi.getAll({ status: undefined });
|
||||||
|
return (response.data as Order[]).slice(0, 5);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch low stock products
|
||||||
|
const {
|
||||||
|
data: lowStockData,
|
||||||
|
isLoading: lowStockLoading,
|
||||||
|
error: lowStockError,
|
||||||
|
} = useQuery({
|
||||||
|
queryKey: ['low-stock'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await inventoryApi.getLowStock();
|
||||||
|
return response.data as LowStockProduct[];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build stats array from API data
|
||||||
|
const stats = statsData
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: 'Ventas Hoy',
|
||||||
|
value: formatCurrency(statsData.salesToday),
|
||||||
|
change: statsData.salesChange,
|
||||||
|
icon: TrendingUp,
|
||||||
|
color: 'green',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Pedidos',
|
||||||
|
value: String(statsData.ordersCount),
|
||||||
|
change: statsData.ordersChange,
|
||||||
|
icon: ShoppingCart,
|
||||||
|
color: 'blue',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Clientes',
|
||||||
|
value: String(statsData.customersCount),
|
||||||
|
change: statsData.customersChange,
|
||||||
|
icon: Users,
|
||||||
|
color: 'purple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Fiados Pendientes',
|
||||||
|
value: formatCurrency(statsData.pendingFiado),
|
||||||
|
change: statsData.fiadoChange,
|
||||||
|
icon: CreditCard,
|
||||||
|
color: 'orange',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
@ -42,6 +156,11 @@ export function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats Grid */}
|
{/* Stats Grid */}
|
||||||
|
{statsLoading ? (
|
||||||
|
<LoadingSpinner />
|
||||||
|
) : statsError ? (
|
||||||
|
<ErrorMessage message="Error al cargar estadisticas" />
|
||||||
|
) : (
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
{stats.map((stat) => (
|
{stats.map((stat) => (
|
||||||
<div key={stat.name} className="card">
|
<div key={stat.name} className="card">
|
||||||
@ -62,6 +181,7 @@ export function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||||
{/* Recent Orders */}
|
{/* Recent Orders */}
|
||||||
@ -75,25 +195,35 @@ export function Dashboard() {
|
|||||||
Ver todos
|
Ver todos
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
{ordersLoading ? (
|
||||||
|
<LoadingSpinner />
|
||||||
|
) : ordersError ? (
|
||||||
|
<ErrorMessage message="Error al cargar pedidos" />
|
||||||
|
) : ordersData && ordersData.length > 0 ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{recentOrders.map((order) => (
|
{ordersData.map((order) => (
|
||||||
<div
|
<div
|
||||||
key={order.id}
|
key={order.id}
|
||||||
className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"
|
className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">{order.id}</p>
|
<p className="font-medium">{order.id.slice(0, 8).toUpperCase()}</p>
|
||||||
<p className="text-sm text-gray-500">{order.customer}</p>
|
<p className="text-sm text-gray-500">
|
||||||
|
{order.customer?.name || 'Cliente General'}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="font-medium">${order.total.toFixed(2)}</p>
|
<p className="font-medium">{formatCurrency(order.total)}</p>
|
||||||
<span className={`inline-block px-2 py-1 text-xs rounded-full ${statusColors[order.status]}`}>
|
<span className={`inline-block px-2 py-1 text-xs rounded-full ${statusColors[order.status] || 'bg-gray-100 text-gray-800'}`}>
|
||||||
{order.status}
|
{statusLabels[order.status] || order.status}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-center text-gray-500 py-4">No hay pedidos recientes</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Low Stock Alert */}
|
{/* Low Stock Alert */}
|
||||||
@ -107,10 +237,15 @@ export function Dashboard() {
|
|||||||
Ver inventario
|
Ver inventario
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
{lowStockLoading ? (
|
||||||
|
<LoadingSpinner />
|
||||||
|
) : lowStockError ? (
|
||||||
|
<ErrorMessage message="Error al cargar inventario" />
|
||||||
|
) : lowStockData && lowStockData.length > 0 ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{lowStockProducts.map((product) => (
|
{lowStockData.map((product) => (
|
||||||
<div
|
<div
|
||||||
key={product.name}
|
key={product.id}
|
||||||
className="flex items-center justify-between p-3 bg-orange-50 rounded-lg"
|
className="flex items-center justify-between p-3 bg-orange-50 rounded-lg"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@ -124,6 +259,9 @@ export function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-center text-gray-500 py-4">No hay productos con stock bajo</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user