Improve the frontend 5
This commit is contained in:
@@ -6,9 +6,7 @@ import {
|
||||
Target,
|
||||
DollarSign,
|
||||
Award,
|
||||
Lock,
|
||||
BarChart3,
|
||||
Package,
|
||||
Truck,
|
||||
Calendar
|
||||
} from 'lucide-react';
|
||||
@@ -22,8 +20,7 @@ import {
|
||||
ResponsiveContainer,
|
||||
Legend
|
||||
} from 'recharts';
|
||||
import { PageHeader } from '../../../components/layout';
|
||||
import { Card, StatsGrid, Button, Tabs } from '../../../components/ui';
|
||||
import { AnalyticsPageLayout, AnalyticsCard } from '../../../components/analytics';
|
||||
import { useSubscription } from '../../../api/hooks/subscription';
|
||||
import { useCurrentTenant } from '../../../stores/tenant.store';
|
||||
import { useProcurementDashboard, useProcurementTrends } from '../../../api/hooks/procurement';
|
||||
@@ -42,54 +39,6 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
||||
// Check if user has access to advanced analytics (professional/enterprise)
|
||||
const hasAdvancedAccess = canAccessAnalytics('advanced');
|
||||
|
||||
// Show loading state while subscription data is being fetched
|
||||
if (subscriptionInfo.loading) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="Analítica de Compras"
|
||||
description="Insights avanzados sobre planificación de compras y gestión de proveedores"
|
||||
/>
|
||||
<Card className="p-8 text-center">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[var(--color-primary)]"></div>
|
||||
<p className="text-[var(--text-secondary)]">Cargando información de suscripción...</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// If user doesn't have access to advanced analytics, show upgrade message
|
||||
if (!hasAdvancedAccess) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="Analítica de Compras"
|
||||
description="Insights avanzados sobre planificación de compras y gestión de proveedores"
|
||||
/>
|
||||
|
||||
<Card className="p-8 text-center">
|
||||
<Lock className="mx-auto h-12 w-12 text-[var(--text-tertiary)] mb-4" />
|
||||
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-2">
|
||||
Funcionalidad Exclusiva para Profesionales y Empresas
|
||||
</h3>
|
||||
<p className="text-[var(--text-secondary)] mb-4">
|
||||
La analítica avanzada de compras está disponible solo para planes Professional y Enterprise.
|
||||
Actualiza tu plan para acceder a análisis detallados de proveedores, optimización de costos y métricas de rendimiento.
|
||||
</p>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="md"
|
||||
onClick={() => window.location.hash = '#/app/settings/profile'}
|
||||
>
|
||||
Actualizar Plan
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Tab configuration
|
||||
const tabs = [
|
||||
{
|
||||
@@ -120,65 +69,50 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="Analítica de Compras"
|
||||
description="Insights avanzados sobre planificación de compras y gestión de proveedores"
|
||||
/>
|
||||
|
||||
{/* Summary Stats */}
|
||||
<StatsGrid
|
||||
stats={[
|
||||
{
|
||||
label: 'Planes Activos',
|
||||
value: dashboard?.summary?.total_plans || 0,
|
||||
icon: ShoppingCart,
|
||||
formatter: formatters.number
|
||||
},
|
||||
{
|
||||
label: 'Tasa de Cumplimiento',
|
||||
value: dashboard?.performance_metrics?.average_fulfillment_rate || 0,
|
||||
icon: Target,
|
||||
formatter: formatters.percentage,
|
||||
change: dashboard?.performance_metrics?.fulfillment_trend
|
||||
},
|
||||
{
|
||||
label: 'Entregas a Tiempo',
|
||||
value: dashboard?.performance_metrics?.average_on_time_delivery || 0,
|
||||
icon: Calendar,
|
||||
formatter: formatters.percentage,
|
||||
change: dashboard?.performance_metrics?.on_time_trend
|
||||
},
|
||||
{
|
||||
label: 'Variación de Costos',
|
||||
value: dashboard?.performance_metrics?.cost_accuracy || 0,
|
||||
icon: DollarSign,
|
||||
formatter: formatters.percentage,
|
||||
change: dashboard?.performance_metrics?.cost_variance_trend
|
||||
}
|
||||
]}
|
||||
loading={dashboardLoading}
|
||||
/>
|
||||
|
||||
{/* Tabs */}
|
||||
<Tabs
|
||||
items={tabs}
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
/>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className="space-y-6">
|
||||
<AnalyticsPageLayout
|
||||
title="Analítica de Compras"
|
||||
description="Insights avanzados sobre planificación de compras y gestión de proveedores"
|
||||
subscriptionLoading={subscriptionInfo.loading}
|
||||
hasAccess={hasAdvancedAccess}
|
||||
dataLoading={dashboardLoading}
|
||||
stats={[
|
||||
{
|
||||
title: 'Planes Activos',
|
||||
value: dashboard?.summary?.total_plans || 0,
|
||||
icon: ShoppingCart,
|
||||
formatter: formatters.number
|
||||
},
|
||||
{
|
||||
title: 'Tasa de Cumplimiento',
|
||||
value: dashboard?.performance_metrics?.average_fulfillment_rate || 0,
|
||||
icon: Target,
|
||||
formatter: formatters.percentage
|
||||
},
|
||||
{
|
||||
title: 'Entregas a Tiempo',
|
||||
value: dashboard?.performance_metrics?.average_on_time_delivery || 0,
|
||||
icon: Calendar,
|
||||
formatter: formatters.percentage
|
||||
},
|
||||
{
|
||||
title: 'Variación de Costos',
|
||||
value: dashboard?.performance_metrics?.cost_accuracy || 0,
|
||||
icon: DollarSign,
|
||||
formatter: formatters.percentage
|
||||
}
|
||||
]}
|
||||
statsColumns={4}
|
||||
tabs={tabs}
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
showMobileNotice={true}
|
||||
>
|
||||
{activeTab === 'overview' && (
|
||||
<>
|
||||
{/* Overview Tab */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Plan Status Distribution */}
|
||||
<Card>
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-4">
|
||||
Distribución de Estados de Planes
|
||||
</h3>
|
||||
<AnalyticsCard title="Distribución de Estados de Planes">
|
||||
<div className="space-y-3">
|
||||
{dashboard?.plan_status_distribution?.map((status: any) => (
|
||||
<div key={status.status} className="flex items-center justify-between">
|
||||
@@ -197,18 +131,13 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</AnalyticsCard>
|
||||
|
||||
{/* Critical Requirements */}
|
||||
<Card>
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-medium text-[var(--text-primary)]">
|
||||
Requerimientos Críticos
|
||||
</h3>
|
||||
<AlertCircle className="h-5 w-5 text-[var(--color-error)]" />
|
||||
</div>
|
||||
<AnalyticsCard
|
||||
title="Requerimientos Críticos"
|
||||
actions={<AlertCircle className="h-5 w-5 text-[var(--color-error)]" />}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[var(--text-secondary)]">Stock Crítico</span>
|
||||
@@ -229,16 +158,11 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</AnalyticsCard>
|
||||
</div>
|
||||
|
||||
{/* Recent Plans */}
|
||||
<Card>
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-4">
|
||||
Planes Recientes
|
||||
</h3>
|
||||
<AnalyticsCard title="Planes Recientes">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
@@ -273,8 +197,7 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</AnalyticsCard>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -282,50 +205,214 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
||||
<>
|
||||
{/* Performance Tab */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<Card>
|
||||
<div className="p-6 text-center">
|
||||
<AnalyticsCard>
|
||||
<div className="text-center">
|
||||
<Target className="mx-auto h-8 w-8 text-[var(--color-success)] mb-3" />
|
||||
<div className="text-3xl font-bold text-[var(--text-primary)] mb-1">
|
||||
{formatters.percentage(dashboard?.performance_metrics?.average_fulfillment_rate || 0)}
|
||||
</div>
|
||||
<div className="text-sm text-[var(--text-secondary)]">Tasa de Cumplimiento</div>
|
||||
</div>
|
||||
</Card>
|
||||
</AnalyticsCard>
|
||||
|
||||
<Card>
|
||||
<div className="p-6 text-center">
|
||||
<AnalyticsCard>
|
||||
<div className="text-center">
|
||||
<Calendar className="mx-auto h-8 w-8 text-[var(--color-info)] mb-3" />
|
||||
<div className="text-3xl font-bold text-[var(--text-primary)] mb-1">
|
||||
{formatters.percentage(dashboard?.performance_metrics?.average_on_time_delivery || 0)}
|
||||
</div>
|
||||
<div className="text-sm text-[var(--text-secondary)]">Entregas a Tiempo</div>
|
||||
</div>
|
||||
</Card>
|
||||
</AnalyticsCard>
|
||||
|
||||
<Card>
|
||||
<div className="p-6 text-center">
|
||||
<AnalyticsCard>
|
||||
<div className="text-center">
|
||||
<Award className="mx-auto h-8 w-8 text-[var(--color-warning)] mb-3" />
|
||||
<div className="text-3xl font-bold text-[var(--text-primary)] mb-1">
|
||||
{dashboard?.performance_metrics?.supplier_performance?.toFixed(1) || '0.0'}
|
||||
</div>
|
||||
<div className="text-sm text-[var(--text-secondary)]">Puntuación de Calidad</div>
|
||||
</div>
|
||||
</Card>
|
||||
</AnalyticsCard>
|
||||
</div>
|
||||
|
||||
{/* Performance Trend Chart */}
|
||||
<Card>
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-4">
|
||||
Tendencias de Rendimiento (Últimos 7 días)
|
||||
</h3>
|
||||
{trendsLoading ? (
|
||||
<div className="h-64 flex items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[var(--color-primary)]"></div>
|
||||
<AnalyticsCard title="Tendencias de Rendimiento (Últimos 7 días)" loading={trendsLoading}>
|
||||
{trends && trends.performance_trend && trends.performance_trend.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<LineChart data={trends.performance_trend}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="var(--border-primary)" />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
stroke="var(--text-tertiary)"
|
||||
tick={{ fill: 'var(--text-secondary)' }}
|
||||
/>
|
||||
<YAxis
|
||||
stroke="var(--text-tertiary)"
|
||||
tick={{ fill: 'var(--text-secondary)' }}
|
||||
tickFormatter={(value) => `${(value * 100).toFixed(0)}%`}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: 'var(--bg-primary)',
|
||||
border: '1px solid var(--border-primary)',
|
||||
borderRadius: '8px'
|
||||
}}
|
||||
formatter={(value: any) => `${(value * 100).toFixed(1)}%`}
|
||||
labelStyle={{ color: 'var(--text-primary)' }}
|
||||
/>
|
||||
<Legend />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="fulfillment_rate"
|
||||
stroke="var(--color-success)"
|
||||
strokeWidth={2}
|
||||
name="Tasa de Cumplimiento"
|
||||
dot={{ fill: 'var(--color-success)' }}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="on_time_rate"
|
||||
stroke="var(--color-info)"
|
||||
strokeWidth={2}
|
||||
name="Entregas a Tiempo"
|
||||
dot={{ fill: 'var(--color-info)' }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<div className="h-64 flex items-center justify-center text-[var(--text-tertiary)]">
|
||||
No hay datos de tendencias disponibles
|
||||
</div>
|
||||
)}
|
||||
</AnalyticsCard>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === 'suppliers' && (
|
||||
<>
|
||||
{/* Suppliers Tab */}
|
||||
<AnalyticsCard title="Rendimiento de Proveedores">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-[var(--border-primary)]">
|
||||
<th className="text-left py-3 px-4 text-[var(--text-secondary)] font-medium">Proveedor</th>
|
||||
<th className="text-right py-3 px-4 text-[var(--text-secondary)] font-medium">Órdenes</th>
|
||||
<th className="text-right py-3 px-4 text-[var(--text-secondary)] font-medium">Tasa Cumplimiento</th>
|
||||
<th className="text-right py-3 px-4 text-[var(--text-secondary)] font-medium">Entregas a Tiempo</th>
|
||||
<th className="text-right py-3 px-4 text-[var(--text-secondary)] font-medium">Calidad</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dashboard?.supplier_performance?.map((supplier: any) => (
|
||||
<tr key={supplier.id} className="border-b border-[var(--border-primary)] hover:bg-[var(--bg-secondary)]">
|
||||
<td className="py-3 px-4 text-[var(--text-primary)]">{supplier.name}</td>
|
||||
<td className="py-3 px-4 text-right text-[var(--text-primary)]">{supplier.total_orders}</td>
|
||||
<td className="py-3 px-4 text-right text-[var(--text-primary)]">
|
||||
{formatters.percentage(supplier.fulfillment_rate)}
|
||||
</td>
|
||||
<td className="py-3 px-4 text-right text-[var(--text-primary)]">
|
||||
{formatters.percentage(supplier.on_time_rate)}
|
||||
</td>
|
||||
<td className="py-3 px-4 text-right text-[var(--text-primary)]">
|
||||
{supplier.quality_score?.toFixed(1) || 'N/A'}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</AnalyticsCard>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === 'costs' && (
|
||||
<>
|
||||
{/* Costs Tab */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<AnalyticsCard title="Análisis de Costos">
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[var(--text-secondary)]">Costo Total Estimado</span>
|
||||
<span className="text-2xl font-bold text-[var(--text-primary)]">
|
||||
€{formatters.currency(dashboard?.summary?.total_estimated_cost || 0)}
|
||||
</span>
|
||||
</div>
|
||||
) : trends && trends.performance_trend && trends.performance_trend.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<LineChart data={trends.performance_trend}>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[var(--text-secondary)]">Costo Total Aprobado</span>
|
||||
<span className="text-2xl font-bold text-[var(--text-primary)]">
|
||||
€{formatters.currency(dashboard?.summary?.total_approved_cost || 0)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[var(--text-secondary)]">Variación Promedio</span>
|
||||
<span className={`text-2xl font-bold ${
|
||||
(dashboard?.summary?.cost_variance || 0) > 0
|
||||
? 'text-[var(--color-error)]'
|
||||
: 'text-[var(--color-success)]'
|
||||
}`}>
|
||||
€{formatters.currency(Math.abs(dashboard?.summary?.cost_variance || 0))}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</AnalyticsCard>
|
||||
|
||||
<AnalyticsCard title="Distribución de Costos por Categoría">
|
||||
<div className="space-y-3">
|
||||
{dashboard?.cost_by_category?.map((category: any) => (
|
||||
<div key={category.name} className="flex items-center justify-between">
|
||||
<span className="text-[var(--text-secondary)]">{category.name}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-32 h-2 bg-[var(--bg-tertiary)] rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-[var(--color-primary)]"
|
||||
style={{ width: `${(category.amount / (dashboard?.summary?.total_estimated_cost || 1)) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-[var(--text-primary)] w-20 text-right">
|
||||
€{formatters.currency(category.amount)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</AnalyticsCard>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === 'quality' && (
|
||||
<>
|
||||
{/* Quality Tab */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<AnalyticsCard title="Métricas de Calidad">
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[var(--text-secondary)]">Puntuación Promedio</span>
|
||||
<span className="text-3xl font-bold text-[var(--text-primary)]">
|
||||
{dashboard?.quality_metrics?.avg_score?.toFixed(1) || '0.0'} / 10
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[var(--text-secondary)]">Productos con Calidad Alta</span>
|
||||
<span className="text-2xl font-bold text-[var(--color-success)]">
|
||||
{dashboard?.quality_metrics?.high_quality_count || 0}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[var(--text-secondary)]">Productos con Calidad Baja</span>
|
||||
<span className="text-2xl font-bold text-[var(--color-error)]">
|
||||
{dashboard?.quality_metrics?.low_quality_count || 0}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</AnalyticsCard>
|
||||
|
||||
<AnalyticsCard title="Tendencia de Calidad (Últimos 7 días)" loading={trendsLoading}>
|
||||
{trends && trends.quality_trend && trends.quality_trend.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<LineChart data={trends.quality_trend}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="var(--border-primary)" />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
@@ -335,7 +422,8 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
||||
<YAxis
|
||||
stroke="var(--text-tertiary)"
|
||||
tick={{ fill: 'var(--text-secondary)' }}
|
||||
tickFormatter={(value) => `${(value * 100).toFixed(0)}%`}
|
||||
domain={[0, 10]}
|
||||
ticks={[0, 2, 4, 6, 8, 10]}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
@@ -343,233 +431,29 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
||||
border: '1px solid var(--border-primary)',
|
||||
borderRadius: '8px'
|
||||
}}
|
||||
formatter={(value: any) => `${(value * 100).toFixed(1)}%`}
|
||||
formatter={(value: any) => `${value.toFixed(1)} / 10`}
|
||||
labelStyle={{ color: 'var(--text-primary)' }}
|
||||
/>
|
||||
<Legend />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="fulfillment_rate"
|
||||
stroke="var(--color-success)"
|
||||
dataKey="quality_score"
|
||||
stroke="var(--color-warning)"
|
||||
strokeWidth={2}
|
||||
name="Tasa de Cumplimiento"
|
||||
dot={{ fill: 'var(--color-success)' }}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="on_time_rate"
|
||||
stroke="var(--color-info)"
|
||||
strokeWidth={2}
|
||||
name="Entregas a Tiempo"
|
||||
dot={{ fill: 'var(--color-info)' }}
|
||||
name="Puntuación de Calidad"
|
||||
dot={{ fill: 'var(--color-warning)' }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<div className="h-64 flex items-center justify-center text-[var(--text-tertiary)]">
|
||||
No hay datos de tendencias disponibles
|
||||
<div className="h-48 flex items-center justify-center text-[var(--text-tertiary)]">
|
||||
No hay datos de calidad disponibles
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === 'suppliers' && (
|
||||
<>
|
||||
{/* Suppliers Tab */}
|
||||
<Card>
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-4">
|
||||
Rendimiento de Proveedores
|
||||
</h3>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-[var(--border-primary)]">
|
||||
<th className="text-left py-3 px-4 text-[var(--text-secondary)] font-medium">Proveedor</th>
|
||||
<th className="text-right py-3 px-4 text-[var(--text-secondary)] font-medium">Órdenes</th>
|
||||
<th className="text-right py-3 px-4 text-[var(--text-secondary)] font-medium">Tasa Cumplimiento</th>
|
||||
<th className="text-right py-3 px-4 text-[var(--text-secondary)] font-medium">Entregas a Tiempo</th>
|
||||
<th className="text-right py-3 px-4 text-[var(--text-secondary)] font-medium">Calidad</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dashboard?.supplier_performance?.map((supplier: any) => (
|
||||
<tr key={supplier.id} className="border-b border-[var(--border-primary)] hover:bg-[var(--bg-secondary)]">
|
||||
<td className="py-3 px-4 text-[var(--text-primary)]">{supplier.name}</td>
|
||||
<td className="py-3 px-4 text-right text-[var(--text-primary)]">{supplier.total_orders}</td>
|
||||
<td className="py-3 px-4 text-right text-[var(--text-primary)]">
|
||||
{formatters.percentage(supplier.fulfillment_rate)}
|
||||
</td>
|
||||
<td className="py-3 px-4 text-right text-[var(--text-primary)]">
|
||||
{formatters.percentage(supplier.on_time_rate)}
|
||||
</td>
|
||||
<td className="py-3 px-4 text-right text-[var(--text-primary)]">
|
||||
{supplier.quality_score?.toFixed(1) || 'N/A'}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === 'costs' && (
|
||||
<>
|
||||
{/* Costs Tab */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Card>
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-4">
|
||||
Análisis de Costos
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[var(--text-secondary)]">Costo Total Estimado</span>
|
||||
<span className="text-2xl font-bold text-[var(--text-primary)]">
|
||||
€{formatters.currency(dashboard?.summary?.total_estimated_cost || 0)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[var(--text-secondary)]">Costo Total Aprobado</span>
|
||||
<span className="text-2xl font-bold text-[var(--text-primary)]">
|
||||
€{formatters.currency(dashboard?.summary?.total_approved_cost || 0)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[var(--text-secondary)]">Variación Promedio</span>
|
||||
<span className={`text-2xl font-bold ${
|
||||
(dashboard?.summary?.cost_variance || 0) > 0
|
||||
? 'text-[var(--color-error)]'
|
||||
: 'text-[var(--color-success)]'
|
||||
}`}>
|
||||
€{formatters.currency(Math.abs(dashboard?.summary?.cost_variance || 0))}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-4">
|
||||
Distribución de Costos por Categoría
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{dashboard?.cost_by_category?.map((category: any) => (
|
||||
<div key={category.name} className="flex items-center justify-between">
|
||||
<span className="text-[var(--text-secondary)]">{category.name}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-32 h-2 bg-[var(--bg-tertiary)] rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-[var(--color-primary)]"
|
||||
style={{ width: `${(category.amount / (dashboard?.summary?.total_estimated_cost || 1)) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-[var(--text-primary)] w-20 text-right">
|
||||
€{formatters.currency(category.amount)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</AnalyticsCard>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === 'quality' && (
|
||||
<>
|
||||
{/* Quality Tab */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Card>
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-4">
|
||||
Métricas de Calidad
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[var(--text-secondary)]">Puntuación Promedio</span>
|
||||
<span className="text-3xl font-bold text-[var(--text-primary)]">
|
||||
{dashboard?.quality_metrics?.avg_score?.toFixed(1) || '0.0'} / 10
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[var(--text-secondary)]">Productos con Calidad Alta</span>
|
||||
<span className="text-2xl font-bold text-[var(--color-success)]">
|
||||
{dashboard?.quality_metrics?.high_quality_count || 0}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[var(--text-secondary)]">Productos con Calidad Baja</span>
|
||||
<span className="text-2xl font-bold text-[var(--color-error)]">
|
||||
{dashboard?.quality_metrics?.low_quality_count || 0}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-4">
|
||||
Tendencia de Calidad (Últimos 7 días)
|
||||
</h3>
|
||||
{trendsLoading ? (
|
||||
<div className="h-48 flex items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[var(--color-primary)]"></div>
|
||||
</div>
|
||||
) : trends && trends.quality_trend && trends.quality_trend.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<LineChart data={trends.quality_trend}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="var(--border-primary)" />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
stroke="var(--text-tertiary)"
|
||||
tick={{ fill: 'var(--text-secondary)' }}
|
||||
/>
|
||||
<YAxis
|
||||
stroke="var(--text-tertiary)"
|
||||
tick={{ fill: 'var(--text-secondary)' }}
|
||||
domain={[0, 10]}
|
||||
ticks={[0, 2, 4, 6, 8, 10]}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: 'var(--bg-primary)',
|
||||
border: '1px solid var(--border-primary)',
|
||||
borderRadius: '8px'
|
||||
}}
|
||||
formatter={(value: any) => `${value.toFixed(1)} / 10`}
|
||||
labelStyle={{ color: 'var(--text-primary)' }}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="quality_score"
|
||||
stroke="var(--color-warning)"
|
||||
strokeWidth={2}
|
||||
name="Puntuación de Calidad"
|
||||
dot={{ fill: 'var(--color-warning)' }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<div className="h-48 flex items-center justify-center text-[var(--text-tertiary)]">
|
||||
No hay datos de calidad disponibles
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</AnalyticsPageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user