352 lines
12 KiB
TypeScript
352 lines
12 KiB
TypeScript
import React from 'react';
|
|
import { TrendingUp, TrendingDown, AlertTriangle, CheckCircle, Clock, DollarSign } from 'lucide-react';
|
|
import { Card, Badge } from '../../components/ui';
|
|
import { PageHeader } from '../../components/layout';
|
|
import { DashboardCard, KPIWidget, QuickActions, RecentActivity, ActivityType, ActivityStatus } from '../../components/domain/dashboard';
|
|
|
|
const DashboardPage: React.FC = () => {
|
|
const kpiData = [
|
|
{
|
|
title: 'Ventas Hoy',
|
|
value: {
|
|
current: 1247,
|
|
previous: 1112,
|
|
format: 'currency' as const,
|
|
prefix: '€'
|
|
},
|
|
trend: {
|
|
direction: 'up' as const,
|
|
value: 12,
|
|
isPositive: true,
|
|
comparisonPeriod: 'vs ayer'
|
|
},
|
|
icon: <DollarSign className="w-5 h-5" />,
|
|
},
|
|
{
|
|
title: 'Órdenes Pendientes',
|
|
value: {
|
|
current: 23,
|
|
previous: 24,
|
|
format: 'number' as const
|
|
},
|
|
trend: {
|
|
direction: 'down' as const,
|
|
value: 4.2,
|
|
isPositive: false,
|
|
comparisonPeriod: 'vs ayer'
|
|
},
|
|
icon: <Clock className="w-5 h-5" />,
|
|
},
|
|
{
|
|
title: 'Productos Vendidos',
|
|
value: {
|
|
current: 156,
|
|
previous: 144,
|
|
format: 'number' as const
|
|
},
|
|
trend: {
|
|
direction: 'up' as const,
|
|
value: 8.3,
|
|
isPositive: true,
|
|
comparisonPeriod: 'vs ayer'
|
|
},
|
|
icon: <CheckCircle className="w-5 h-5" />,
|
|
},
|
|
{
|
|
title: 'Stock Crítico',
|
|
value: {
|
|
current: 4,
|
|
previous: 2,
|
|
format: 'number' as const
|
|
},
|
|
trend: {
|
|
direction: 'up' as const,
|
|
value: 100,
|
|
isPositive: false,
|
|
comparisonPeriod: 'vs ayer'
|
|
},
|
|
status: 'warning' as const,
|
|
icon: <AlertTriangle className="w-5 h-5" />,
|
|
},
|
|
];
|
|
|
|
const quickActions = [
|
|
{
|
|
id: 'production',
|
|
title: 'Nueva Orden de Producción',
|
|
description: 'Crear nueva orden de producción',
|
|
icon: <TrendingUp className="w-6 h-6" />,
|
|
onClick: () => window.location.href = '/app/operations/production',
|
|
href: '/app/operations/production'
|
|
},
|
|
{
|
|
id: 'inventory',
|
|
title: 'Gestionar Inventario',
|
|
description: 'Administrar stock de productos',
|
|
icon: <CheckCircle className="w-6 h-6" />,
|
|
onClick: () => window.location.href = '/app/operations/inventory',
|
|
href: '/app/operations/inventory'
|
|
},
|
|
{
|
|
id: 'sales',
|
|
title: 'Ver Ventas',
|
|
description: 'Analizar ventas y reportes',
|
|
icon: <DollarSign className="w-6 h-6" />,
|
|
onClick: () => window.location.href = '/app/analytics/sales',
|
|
href: '/app/analytics/sales'
|
|
},
|
|
{
|
|
id: 'settings',
|
|
title: 'Configuración',
|
|
description: 'Ajustar configuración del sistema',
|
|
icon: <AlertTriangle className="w-6 h-6" />,
|
|
onClick: () => window.location.href = '/app/settings',
|
|
href: '/app/settings'
|
|
},
|
|
];
|
|
|
|
const recentActivities = [
|
|
{
|
|
id: '1',
|
|
type: ActivityType.PRODUCTION,
|
|
title: 'Orden de producción completada',
|
|
description: 'Pan de Molde Integral - 20 unidades',
|
|
timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
|
|
status: ActivityStatus.SUCCESS,
|
|
},
|
|
{
|
|
id: '2',
|
|
type: ActivityType.INVENTORY,
|
|
title: 'Stock bajo detectado',
|
|
description: 'Levadura fresca necesita reposición',
|
|
timestamp: new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString(),
|
|
status: ActivityStatus.WARNING,
|
|
},
|
|
{
|
|
id: '3',
|
|
type: ActivityType.SALES,
|
|
title: 'Venta registrada',
|
|
description: '€45.50 - Croissants y café',
|
|
timestamp: new Date(Date.now() - 4 * 60 * 60 * 1000).toISOString(),
|
|
status: ActivityStatus.INFO,
|
|
},
|
|
];
|
|
|
|
const productionStatus = {
|
|
today: {
|
|
target: 150,
|
|
completed: 95,
|
|
inProgress: 18,
|
|
pending: 37,
|
|
},
|
|
efficiency: 85,
|
|
};
|
|
|
|
const salesData = {
|
|
today: 1247,
|
|
yesterday: 1112,
|
|
thisWeek: 8934,
|
|
thisMonth: 35678,
|
|
};
|
|
|
|
const inventoryAlerts = [
|
|
{ item: 'Levadura Fresca', current: 2, min: 5, status: 'critical' },
|
|
{ item: 'Harina Integral', current: 8, min: 10, status: 'low' },
|
|
{ item: 'Mantequilla', current: 15, min: 20, status: 'low' },
|
|
];
|
|
|
|
const topProducts = [
|
|
{ name: 'Pan de Molde', sold: 45, revenue: 202.50 },
|
|
{ name: 'Croissants', sold: 32, revenue: 192.00 },
|
|
{ name: 'Baguettes', sold: 28, revenue: 84.00 },
|
|
{ name: 'Magdalenas', sold: 24, revenue: 72.00 },
|
|
];
|
|
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<PageHeader
|
|
title="Panel de Control"
|
|
description="Vista general de tu panadería"
|
|
/>
|
|
|
|
{/* KPI Cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
{kpiData.map((kpi, index) => (
|
|
<KPIWidget
|
|
key={index}
|
|
title={kpi.title}
|
|
value={kpi.value}
|
|
trend={kpi.trend}
|
|
icon={kpi.icon}
|
|
status={kpi.status}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
{/* Production Status */}
|
|
<Card className="p-6">
|
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Estado de Producción</h3>
|
|
|
|
<div className="space-y-4">
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-[var(--text-secondary)]">Progreso del Día</span>
|
|
<span className="text-sm font-medium">
|
|
{productionStatus.today.completed} / {productionStatus.today.target}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="w-full bg-[var(--bg-quaternary)] rounded-full h-2">
|
|
<div
|
|
className="bg-[var(--color-info)] h-2 rounded-full"
|
|
style={{
|
|
width: `${(productionStatus.today.completed / productionStatus.today.target) * 100}%`
|
|
}}
|
|
></div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-3 gap-4 mt-4">
|
|
<div className="text-center">
|
|
<p className="text-2xl font-bold text-[var(--color-success)]">{productionStatus.today.completed}</p>
|
|
<p className="text-xs text-[var(--text-secondary)]">Completado</p>
|
|
</div>
|
|
<div className="text-center">
|
|
<p className="text-2xl font-bold text-[var(--color-info)]">{productionStatus.today.inProgress}</p>
|
|
<p className="text-xs text-[var(--text-secondary)]">En Proceso</p>
|
|
</div>
|
|
<div className="text-center">
|
|
<p className="text-2xl font-bold text-[var(--color-primary)]">{productionStatus.today.pending}</p>
|
|
<p className="text-xs text-[var(--text-secondary)]">Pendiente</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-4 pt-4 border-t">
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-[var(--text-secondary)]">Eficiencia</span>
|
|
<span className="text-sm font-medium text-purple-600">{productionStatus.efficiency}%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Sales Summary */}
|
|
<Card className="p-6">
|
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Resumen de Ventas</h3>
|
|
|
|
<div className="space-y-4">
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-[var(--text-secondary)]">Hoy</span>
|
|
<span className="text-lg font-semibold text-[var(--color-success)]">€{salesData.today.toLocaleString()}</span>
|
|
</div>
|
|
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-[var(--text-secondary)]">Ayer</span>
|
|
<div className="flex items-center">
|
|
<span className="text-sm font-medium mr-2">€{salesData.yesterday.toLocaleString()}</span>
|
|
{salesData.today > salesData.yesterday ? (
|
|
<TrendingUp className="h-4 w-4 text-[var(--color-success)]" />
|
|
) : (
|
|
<TrendingDown className="h-4 w-4 text-[var(--color-error)]" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-[var(--text-secondary)]">Esta Semana</span>
|
|
<span className="text-sm font-medium">€{salesData.thisWeek.toLocaleString()}</span>
|
|
</div>
|
|
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-sm text-[var(--text-secondary)]">Este Mes</span>
|
|
<span className="text-sm font-medium">€{salesData.thisMonth.toLocaleString()}</span>
|
|
</div>
|
|
|
|
<div className="mt-4 pt-4 border-t">
|
|
<div className="text-center">
|
|
<p className="text-xs text-[var(--text-secondary)]">Crecimiento vs ayer</p>
|
|
<p className="text-lg font-semibold text-[var(--color-success)]">
|
|
+{(((salesData.today - salesData.yesterday) / salesData.yesterday) * 100).toFixed(1)}%
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Inventory Alerts */}
|
|
<Card className="p-6">
|
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Alertas de Inventario</h3>
|
|
|
|
<div className="space-y-3">
|
|
{inventoryAlerts.map((alert, index) => (
|
|
<div key={index} className="flex items-center justify-between p-3 bg-[var(--color-error)]/10 rounded-lg">
|
|
<div>
|
|
<p className="text-sm font-medium text-[var(--text-primary)]">{alert.item}</p>
|
|
<p className="text-xs text-[var(--text-secondary)]">Stock: {alert.current} / Mín: {alert.min}</p>
|
|
</div>
|
|
<Badge variant={alert.status === 'critical' ? 'red' : 'yellow'}>
|
|
{alert.status === 'critical' ? 'Crítico' : 'Bajo'}
|
|
</Badge>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="mt-4 pt-4 border-t">
|
|
<button className="w-full text-sm text-[var(--color-info)] hover:text-[var(--color-info)] font-medium">
|
|
Ver Todo el Inventario →
|
|
</button>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* Top Products */}
|
|
<Card className="p-6">
|
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Productos Más Vendidos</h3>
|
|
|
|
<div className="space-y-3">
|
|
{topProducts.map((product, index) => (
|
|
<div key={index} className="flex items-center justify-between">
|
|
<div className="flex items-center">
|
|
<span className="text-sm font-medium text-[var(--text-secondary)] w-6">{index + 1}.</span>
|
|
<span className="text-sm text-[var(--text-primary)]">{product.name}</span>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="text-sm font-medium text-[var(--text-primary)]">{product.sold} unidades</p>
|
|
<p className="text-xs text-[var(--color-success)]">€{product.revenue.toFixed(2)}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="mt-4 pt-4 border-t">
|
|
<button className="w-full text-sm text-[var(--color-info)] hover:text-[var(--color-info)] font-medium">
|
|
Ver Análisis Completo →
|
|
</button>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* Recent Activity */}
|
|
<Card className="p-6">
|
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Actividad Reciente</h3>
|
|
|
|
<RecentActivity activities={recentActivities} />
|
|
|
|
<div className="mt-4 pt-4 border-t">
|
|
<button className="w-full text-sm text-[var(--color-info)] hover:text-[var(--color-info)] font-medium">
|
|
Ver Toda la Actividad →
|
|
</button>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Quick Actions */}
|
|
<Card className="p-6">
|
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Acciones Rápidas</h3>
|
|
<QuickActions actions={quickActions} />
|
|
</Card>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DashboardPage; |