Add page UI imporvements
This commit is contained in:
@@ -108,23 +108,23 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
|||||||
showMobileNotice={true}
|
showMobileNotice={true}
|
||||||
>
|
>
|
||||||
{activeTab === 'overview' && (
|
{activeTab === 'overview' && (
|
||||||
<>
|
<div className="space-y-6">
|
||||||
{/* Overview Tab */}
|
{/* Overview Tab */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
{/* Plan Status Distribution */}
|
{/* Plan Status Distribution */}
|
||||||
<AnalyticsCard title="Distribución de Estados de Planes">
|
<AnalyticsCard title="Distribución de Estados de Planes">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{dashboard?.plan_status_distribution?.map((status: any) => (
|
{dashboard?.plan_status_distribution?.map((status: any) => (
|
||||||
<div key={status.status} className="flex items-center justify-between">
|
<div key={status.status} className="flex items-center justify-between p-3 bg-[var(--bg-secondary)] rounded-lg hover:bg-[var(--bg-tertiary)] transition-colors">
|
||||||
<span className="text-[var(--text-secondary)]">{status.status}</span>
|
<span className="text-sm font-medium text-[var(--text-primary)] capitalize">{status.status.replace('_', ' ')}</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-32 h-2 bg-[var(--bg-tertiary)] rounded-full overflow-hidden">
|
<div className="w-32 h-2 bg-[var(--bg-tertiary)] rounded-full overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="h-full bg-[var(--color-primary)]"
|
className="h-full bg-[var(--color-primary)] rounded-full transition-all"
|
||||||
style={{ width: `${(status.count / (dashboard?.summary?.total_plans || 1)) * 100}%` }}
|
style={{ width: `${(status.count / (dashboard?.summary?.total_plans || 1)) * 100}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-medium text-[var(--text-primary)] w-8 text-right">
|
<span className="text-sm font-bold text-[var(--text-primary)] w-10 text-right">
|
||||||
{status.count}
|
{status.count}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -139,20 +139,29 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
|||||||
actions={<AlertCircle className="h-5 w-5 text-[var(--color-error)]" />}
|
actions={<AlertCircle className="h-5 w-5 text-[var(--color-error)]" />}
|
||||||
>
|
>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center p-4 bg-red-50 dark:bg-red-900/20 rounded-lg">
|
||||||
<span className="text-[var(--text-secondary)]">Stock Crítico</span>
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-[var(--color-error)]"></div>
|
||||||
|
<span className="text-sm font-medium text-[var(--text-secondary)]">Stock Crítico</span>
|
||||||
|
</div>
|
||||||
<span className="text-2xl font-bold text-[var(--color-error)]">
|
<span className="text-2xl font-bold text-[var(--color-error)]">
|
||||||
{dashboard?.critical_requirements?.low_stock || 0}
|
{dashboard?.critical_requirements?.low_stock || 0}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center p-4 bg-orange-50 dark:bg-orange-900/20 rounded-lg">
|
||||||
<span className="text-[var(--text-secondary)]">Entregas Atrasadas</span>
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-[var(--color-warning)]"></div>
|
||||||
|
<span className="text-sm font-medium text-[var(--text-secondary)]">Entregas Atrasadas</span>
|
||||||
|
</div>
|
||||||
<span className="text-2xl font-bold text-[var(--color-warning)]">
|
<span className="text-2xl font-bold text-[var(--color-warning)]">
|
||||||
{dashboard?.critical_requirements?.overdue || 0}
|
{dashboard?.critical_requirements?.overdue || 0}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
||||||
<span className="text-[var(--text-secondary)]">Alta Prioridad</span>
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-[var(--color-info)]"></div>
|
||||||
|
<span className="text-sm font-medium text-[var(--text-secondary)]">Alta Prioridad</span>
|
||||||
|
</div>
|
||||||
<span className="text-2xl font-bold text-[var(--color-info)]">
|
<span className="text-2xl font-bold text-[var(--color-info)]">
|
||||||
{dashboard?.critical_requirements?.high_priority || 0}
|
{dashboard?.critical_requirements?.high_priority || 0}
|
||||||
</span>
|
</span>
|
||||||
@@ -163,33 +172,33 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
|||||||
|
|
||||||
{/* Recent Plans */}
|
{/* Recent Plans */}
|
||||||
<AnalyticsCard title="Planes Recientes">
|
<AnalyticsCard title="Planes Recientes">
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto -mx-6">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-[var(--border-primary)]">
|
<tr className="border-b border-[var(--border-primary)] bg-[var(--bg-secondary)]">
|
||||||
<th className="text-left py-3 px-4 text-[var(--text-secondary)] font-medium">Plan</th>
|
<th className="text-left py-3 px-6 text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">Plan</th>
|
||||||
<th className="text-left py-3 px-4 text-[var(--text-secondary)] font-medium">Fecha</th>
|
<th className="text-left py-3 px-6 text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">Fecha</th>
|
||||||
<th className="text-left py-3 px-4 text-[var(--text-secondary)] font-medium">Estado</th>
|
<th className="text-left py-3 px-6 text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">Estado</th>
|
||||||
<th className="text-right py-3 px-4 text-[var(--text-secondary)] font-medium">Requerimientos</th>
|
<th className="text-right py-3 px-6 text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">Requerimientos</th>
|
||||||
<th className="text-right py-3 px-4 text-[var(--text-secondary)] font-medium">Costo Total</th>
|
<th className="text-right py-3 px-6 text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">Costo Total</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{dashboard?.recent_plans?.map((plan: any) => (
|
{dashboard?.recent_plans?.map((plan: any) => (
|
||||||
<tr key={plan.id} className="border-b border-[var(--border-primary)] hover:bg-[var(--bg-secondary)]">
|
<tr key={plan.id} className="border-b border-[var(--border-primary)] hover:bg-[var(--bg-secondary)] transition-colors">
|
||||||
<td className="py-3 px-4 text-[var(--text-primary)]">{plan.plan_number}</td>
|
<td className="py-4 px-6 text-sm font-medium text-[var(--text-primary)]">{plan.plan_number}</td>
|
||||||
<td className="py-3 px-4 text-[var(--text-secondary)]">
|
<td className="py-4 px-6 text-sm text-[var(--text-secondary)]">
|
||||||
{new Date(plan.plan_date).toLocaleDateString()}
|
{new Date(plan.plan_date).toLocaleDateString()}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 px-4">
|
<td className="py-4 px-6">
|
||||||
<span className={`px-2 py-1 rounded-full text-xs ${getStatusColor(plan.status)}`}>
|
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(plan.status)}`}>
|
||||||
{plan.status}
|
{plan.status}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 px-4 text-right text-[var(--text-primary)]">
|
<td className="py-4 px-6 text-sm text-right font-medium text-[var(--text-primary)]">
|
||||||
{plan.total_requirements}
|
{plan.total_requirements}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 px-4 text-right text-[var(--text-primary)]">
|
<td className="py-4 px-6 text-sm text-right font-bold text-[var(--text-primary)]">
|
||||||
€{formatters.currency(plan.total_estimated_cost)}
|
€{formatters.currency(plan.total_estimated_cost)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -198,40 +207,46 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</AnalyticsCard>
|
</AnalyticsCard>
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'performance' && (
|
{activeTab === 'performance' && (
|
||||||
<>
|
<div className="space-y-6">
|
||||||
{/* Performance Tab */}
|
{/* Performance Tab */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
<AnalyticsCard>
|
<AnalyticsCard>
|
||||||
<div className="text-center">
|
<div className="text-center p-4">
|
||||||
<Target className="mx-auto h-8 w-8 text-[var(--color-success)] mb-3" />
|
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-green-100 dark:bg-green-900/30 mb-4">
|
||||||
<div className="text-3xl font-bold text-[var(--text-primary)] mb-1">
|
<Target className="h-8 w-8 text-[var(--color-success)]" />
|
||||||
|
</div>
|
||||||
|
<div className="text-4xl font-bold text-[var(--text-primary)] mb-2">
|
||||||
{formatters.percentage(dashboard?.performance_metrics?.average_fulfillment_rate || 0)}
|
{formatters.percentage(dashboard?.performance_metrics?.average_fulfillment_rate || 0)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-[var(--text-secondary)]">Tasa de Cumplimiento</div>
|
<div className="text-sm font-medium text-[var(--text-secondary)]">Tasa de Cumplimiento</div>
|
||||||
</div>
|
</div>
|
||||||
</AnalyticsCard>
|
</AnalyticsCard>
|
||||||
|
|
||||||
<AnalyticsCard>
|
<AnalyticsCard>
|
||||||
<div className="text-center">
|
<div className="text-center p-4">
|
||||||
<Calendar className="mx-auto h-8 w-8 text-[var(--color-info)] mb-3" />
|
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-blue-100 dark:bg-blue-900/30 mb-4">
|
||||||
<div className="text-3xl font-bold text-[var(--text-primary)] mb-1">
|
<Calendar className="h-8 w-8 text-[var(--color-info)]" />
|
||||||
|
</div>
|
||||||
|
<div className="text-4xl font-bold text-[var(--text-primary)] mb-2">
|
||||||
{formatters.percentage(dashboard?.performance_metrics?.average_on_time_delivery || 0)}
|
{formatters.percentage(dashboard?.performance_metrics?.average_on_time_delivery || 0)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-[var(--text-secondary)]">Entregas a Tiempo</div>
|
<div className="text-sm font-medium text-[var(--text-secondary)]">Entregas a Tiempo</div>
|
||||||
</div>
|
</div>
|
||||||
</AnalyticsCard>
|
</AnalyticsCard>
|
||||||
|
|
||||||
<AnalyticsCard>
|
<AnalyticsCard>
|
||||||
<div className="text-center">
|
<div className="text-center p-4">
|
||||||
<Award className="mx-auto h-8 w-8 text-[var(--color-warning)] mb-3" />
|
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-orange-100 dark:bg-orange-900/30 mb-4">
|
||||||
<div className="text-3xl font-bold text-[var(--text-primary)] mb-1">
|
<Award className="h-8 w-8 text-[var(--color-warning)]" />
|
||||||
|
</div>
|
||||||
|
<div className="text-4xl font-bold text-[var(--text-primary)] mb-2">
|
||||||
{dashboard?.performance_metrics?.supplier_performance?.toFixed(1) || '0.0'}
|
{dashboard?.performance_metrics?.supplier_performance?.toFixed(1) || '0.0'}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-[var(--text-secondary)]">Puntuación de Calidad</div>
|
<div className="text-sm font-medium text-[var(--text-secondary)]">Puntuación de Calidad</div>
|
||||||
</div>
|
</div>
|
||||||
</AnalyticsCard>
|
</AnalyticsCard>
|
||||||
</div>
|
</div>
|
||||||
@@ -239,84 +254,110 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
|||||||
{/* Performance Trend Chart */}
|
{/* Performance Trend Chart */}
|
||||||
<AnalyticsCard title="Tendencias de Rendimiento (Últimos 7 días)" loading={trendsLoading}>
|
<AnalyticsCard title="Tendencias de Rendimiento (Últimos 7 días)" loading={trendsLoading}>
|
||||||
{trends && trends.performance_trend && trends.performance_trend.length > 0 ? (
|
{trends && trends.performance_trend && trends.performance_trend.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
<div className="pt-4">
|
||||||
|
<ResponsiveContainer width="100%" height={350}>
|
||||||
<LineChart data={trends.performance_trend}>
|
<LineChart data={trends.performance_trend}>
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="var(--border-primary)" />
|
<CartesianGrid strokeDasharray="3 3" stroke="var(--border-primary)" opacity={0.3} />
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="date"
|
dataKey="date"
|
||||||
stroke="var(--text-tertiary)"
|
stroke="var(--text-tertiary)"
|
||||||
tick={{ fill: 'var(--text-secondary)' }}
|
tick={{ fill: 'var(--text-secondary)', fontSize: 12 }}
|
||||||
|
tickLine={{ stroke: 'var(--border-primary)' }}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
stroke="var(--text-tertiary)"
|
stroke="var(--text-tertiary)"
|
||||||
tick={{ fill: 'var(--text-secondary)' }}
|
tick={{ fill: 'var(--text-secondary)', fontSize: 12 }}
|
||||||
tickFormatter={(value) => `${(value * 100).toFixed(0)}%`}
|
tickFormatter={(value) => `${(value * 100).toFixed(0)}%`}
|
||||||
|
tickLine={{ stroke: 'var(--border-primary)' }}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
contentStyle={{
|
contentStyle={{
|
||||||
backgroundColor: 'var(--bg-primary)',
|
backgroundColor: 'var(--bg-primary)',
|
||||||
border: '1px solid var(--border-primary)',
|
border: '1px solid var(--border-primary)',
|
||||||
borderRadius: '8px'
|
borderRadius: '8px',
|
||||||
|
boxShadow: 'var(--shadow-lg)'
|
||||||
}}
|
}}
|
||||||
formatter={(value: any) => `${(value * 100).toFixed(1)}%`}
|
formatter={(value: any) => `${(value * 100).toFixed(1)}%`}
|
||||||
labelStyle={{ color: 'var(--text-primary)' }}
|
labelStyle={{ color: 'var(--text-primary)', fontWeight: 600 }}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
<Legend wrapperStyle={{ paddingTop: '20px' }} />
|
||||||
<Line
|
<Line
|
||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey="fulfillment_rate"
|
dataKey="fulfillment_rate"
|
||||||
stroke="var(--color-success)"
|
stroke="var(--color-success)"
|
||||||
strokeWidth={2}
|
strokeWidth={3}
|
||||||
name="Tasa de Cumplimiento"
|
name="Tasa de Cumplimiento"
|
||||||
dot={{ fill: 'var(--color-success)' }}
|
dot={{ fill: 'var(--color-success)', r: 4 }}
|
||||||
|
activeDot={{ r: 6 }}
|
||||||
/>
|
/>
|
||||||
<Line
|
<Line
|
||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey="on_time_rate"
|
dataKey="on_time_rate"
|
||||||
stroke="var(--color-info)"
|
stroke="var(--color-info)"
|
||||||
strokeWidth={2}
|
strokeWidth={3}
|
||||||
name="Entregas a Tiempo"
|
name="Entregas a Tiempo"
|
||||||
dot={{ fill: 'var(--color-info)' }}
|
dot={{ fill: 'var(--color-info)', r: 4 }}
|
||||||
|
activeDot={{ r: 6 }}
|
||||||
/>
|
/>
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-64 flex items-center justify-center text-[var(--text-tertiary)]">
|
<div className="h-80 flex flex-col items-center justify-center text-[var(--text-tertiary)]">
|
||||||
No hay datos de tendencias disponibles
|
<BarChart3 className="h-16 w-16 mb-4 opacity-20" />
|
||||||
|
<p className="text-sm">No hay datos de tendencias disponibles</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</AnalyticsCard>
|
</AnalyticsCard>
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'suppliers' && (
|
{activeTab === 'suppliers' && (
|
||||||
<>
|
<div className="space-y-6">
|
||||||
{/* Suppliers Tab */}
|
{/* Suppliers Tab */}
|
||||||
<AnalyticsCard title="Rendimiento de Proveedores">
|
<AnalyticsCard title="Rendimiento de Proveedores">
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto -mx-6">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-[var(--border-primary)]">
|
<tr className="border-b border-[var(--border-primary)] bg-[var(--bg-secondary)]">
|
||||||
<th className="text-left py-3 px-4 text-[var(--text-secondary)] font-medium">Proveedor</th>
|
<th className="text-left py-3 px-6 text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">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-6 text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">Ó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-6 text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">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-6 text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">Puntualidad</th>
|
||||||
<th className="text-right py-3 px-4 text-[var(--text-secondary)] font-medium">Calidad</th>
|
<th className="text-right py-3 px-6 text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">Calidad</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{dashboard?.supplier_performance?.map((supplier: any) => (
|
{dashboard?.supplier_performance?.map((supplier: any) => (
|
||||||
<tr key={supplier.id} className="border-b border-[var(--border-primary)] hover:bg-[var(--bg-secondary)]">
|
<tr key={supplier.id} className="border-b border-[var(--border-primary)] hover:bg-[var(--bg-secondary)] transition-colors">
|
||||||
<td className="py-3 px-4 text-[var(--text-primary)]">{supplier.name}</td>
|
<td className="py-4 px-6 text-sm font-semibold 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-4 px-6 text-sm text-right font-medium text-[var(--text-primary)]">
|
||||||
<td className="py-3 px-4 text-right text-[var(--text-primary)]">
|
<span className="inline-flex items-center justify-center px-3 py-1 rounded-full bg-[var(--bg-tertiary)]">
|
||||||
|
{supplier.total_orders}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-6 text-sm text-right">
|
||||||
|
<span className={`inline-flex items-center px-3 py-1 rounded-full font-semibold ${
|
||||||
|
supplier.fulfillment_rate >= 0.9 ? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300' :
|
||||||
|
supplier.fulfillment_rate >= 0.7 ? 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300' :
|
||||||
|
'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
|
||||||
|
}`}>
|
||||||
{formatters.percentage(supplier.fulfillment_rate)}
|
{formatters.percentage(supplier.fulfillment_rate)}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 px-4 text-right text-[var(--text-primary)]">
|
<td className="py-4 px-6 text-sm text-right">
|
||||||
|
<span className={`inline-flex items-center px-3 py-1 rounded-full font-semibold ${
|
||||||
|
supplier.on_time_rate >= 0.9 ? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300' :
|
||||||
|
supplier.on_time_rate >= 0.7 ? 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300' :
|
||||||
|
'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'
|
||||||
|
}`}>
|
||||||
{formatters.percentage(supplier.on_time_rate)}
|
{formatters.percentage(supplier.on_time_rate)}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-3 px-4 text-right text-[var(--text-primary)]">
|
<td className="py-4 px-6 text-sm text-right">
|
||||||
|
<span className="font-bold text-[var(--text-primary)]">
|
||||||
{supplier.quality_score?.toFixed(1) || 'N/A'}
|
{supplier.quality_score?.toFixed(1) || 'N/A'}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
@@ -324,54 +365,72 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</AnalyticsCard>
|
</AnalyticsCard>
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'costs' && (
|
{activeTab === 'costs' && (
|
||||||
<>
|
<div className="space-y-6">
|
||||||
{/* Costs Tab */}
|
{/* Costs Tab */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<AnalyticsCard title="Análisis de Costos">
|
<AnalyticsCard title="Análisis de Costos">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center p-4 bg-[var(--bg-secondary)] rounded-lg">
|
||||||
<span className="text-[var(--text-secondary)]">Costo Total Estimado</span>
|
<div>
|
||||||
<span className="text-2xl font-bold text-[var(--text-primary)]">
|
<span className="text-xs text-[var(--text-tertiary)] uppercase tracking-wide">Costo Total Estimado</span>
|
||||||
|
<div className="text-3xl font-bold text-[var(--text-primary)] mt-1">
|
||||||
€{formatters.currency(dashboard?.summary?.total_estimated_cost || 0)}
|
€{formatters.currency(dashboard?.summary?.total_estimated_cost || 0)}
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
</div>
|
||||||
<span className="text-[var(--text-secondary)]">Costo Total Aprobado</span>
|
<DollarSign className="h-12 w-12 text-[var(--color-info)] opacity-20" />
|
||||||
<span className="text-2xl font-bold text-[var(--text-primary)]">
|
</div>
|
||||||
|
<div className="flex justify-between items-center p-4 bg-[var(--bg-secondary)] rounded-lg">
|
||||||
|
<div>
|
||||||
|
<span className="text-xs text-[var(--text-tertiary)] uppercase tracking-wide">Costo Total Aprobado</span>
|
||||||
|
<div className="text-3xl font-bold text-[var(--text-primary)] mt-1">
|
||||||
€{formatters.currency(dashboard?.summary?.total_approved_cost || 0)}
|
€{formatters.currency(dashboard?.summary?.total_approved_cost || 0)}
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
</div>
|
||||||
<span className="text-[var(--text-secondary)]">Variación Promedio</span>
|
<DollarSign className="h-12 w-12 text-[var(--color-success)] opacity-20" />
|
||||||
<span className={`text-2xl font-bold ${
|
</div>
|
||||||
|
<div className="flex justify-between items-center p-4 bg-[var(--bg-secondary)] rounded-lg">
|
||||||
|
<div>
|
||||||
|
<span className="text-xs text-[var(--text-tertiary)] uppercase tracking-wide">Variación Promedio</span>
|
||||||
|
<div className={`text-3xl font-bold mt-1 ${
|
||||||
(dashboard?.summary?.cost_variance || 0) > 0
|
(dashboard?.summary?.cost_variance || 0) > 0
|
||||||
? 'text-[var(--color-error)]'
|
? 'text-[var(--color-error)]'
|
||||||
: 'text-[var(--color-success)]'
|
: 'text-[var(--color-success)]'
|
||||||
}`}>
|
}`}>
|
||||||
€{formatters.currency(Math.abs(dashboard?.summary?.cost_variance || 0))}
|
{(dashboard?.summary?.cost_variance || 0) > 0 ? '+' : ''}€{formatters.currency(dashboard?.summary?.cost_variance || 0)}
|
||||||
</span>
|
</div>
|
||||||
|
</div>
|
||||||
|
<TrendingUp className={`h-12 w-12 opacity-20 ${
|
||||||
|
(dashboard?.summary?.cost_variance || 0) > 0
|
||||||
|
? 'text-[var(--color-error)]'
|
||||||
|
: 'text-[var(--color-success)] rotate-180'
|
||||||
|
}`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AnalyticsCard>
|
</AnalyticsCard>
|
||||||
|
|
||||||
<AnalyticsCard title="Distribución de Costos por Categoría">
|
<AnalyticsCard title="Distribución de Costos por Categoría">
|
||||||
<div className="space-y-3">
|
<div className="space-y-4">
|
||||||
{dashboard?.cost_by_category?.map((category: any) => (
|
{dashboard?.cost_by_category?.map((category: any) => (
|
||||||
<div key={category.name} className="flex items-center justify-between">
|
<div key={category.name} className="space-y-2">
|
||||||
<span className="text-[var(--text-secondary)]">{category.name}</span>
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<span className="text-sm font-medium text-[var(--text-primary)]">{category.name}</span>
|
||||||
<div className="w-32 h-2 bg-[var(--bg-tertiary)] rounded-full overflow-hidden">
|
<span className="text-sm font-bold text-[var(--text-primary)]">
|
||||||
|
€{formatters.currency(category.amount)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<div className="w-full h-3 bg-[var(--bg-tertiary)] rounded-full overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="h-full bg-[var(--color-primary)]"
|
className="h-full bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-primary-light)] rounded-full transition-all"
|
||||||
style={{ width: `${(category.amount / (dashboard?.summary?.total_estimated_cost || 1)) * 100}%` }}
|
style={{ width: `${(category.amount / (dashboard?.summary?.total_estimated_cost || 1)) * 100}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-medium text-[var(--text-primary)] w-20 text-right">
|
<span className="absolute -top-1 right-0 text-xs font-medium text-[var(--text-tertiary)]">
|
||||||
€{formatters.currency(category.amount)}
|
{((category.amount / (dashboard?.summary?.total_estimated_cost || 1)) * 100).toFixed(1)}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -379,79 +438,93 @@ const ProcurementAnalyticsPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</AnalyticsCard>
|
</AnalyticsCard>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === 'quality' && (
|
{activeTab === 'quality' && (
|
||||||
<>
|
<div className="space-y-6">
|
||||||
{/* Quality Tab */}
|
{/* Quality Tab */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<AnalyticsCard title="Métricas de Calidad">
|
<AnalyticsCard title="Métricas de Calidad">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex flex-col items-center p-6 bg-gradient-to-br from-[var(--color-primary)]/10 to-[var(--color-primary-light)]/5 rounded-lg">
|
||||||
<span className="text-[var(--text-secondary)]">Puntuación Promedio</span>
|
<span className="text-sm text-[var(--text-secondary)] mb-2">Puntuación Promedio</span>
|
||||||
<span className="text-3xl font-bold text-[var(--text-primary)]">
|
<div className="flex items-baseline gap-1">
|
||||||
{dashboard?.quality_metrics?.avg_score?.toFixed(1) || '0.0'} / 10
|
<span className="text-5xl font-bold text-[var(--text-primary)]">
|
||||||
|
{dashboard?.quality_metrics?.avg_score?.toFixed(1) || '0.0'}
|
||||||
</span>
|
</span>
|
||||||
|
<span className="text-2xl font-medium text-[var(--text-secondary)]">/ 10</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
</div>
|
||||||
<span className="text-[var(--text-secondary)]">Productos con Calidad Alta</span>
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="flex flex-col items-center p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
|
||||||
|
<Award className="h-8 w-8 text-[var(--color-success)] mb-2" />
|
||||||
<span className="text-2xl font-bold text-[var(--color-success)]">
|
<span className="text-2xl font-bold text-[var(--color-success)]">
|
||||||
{dashboard?.quality_metrics?.high_quality_count || 0}
|
{dashboard?.quality_metrics?.high_quality_count || 0}
|
||||||
</span>
|
</span>
|
||||||
|
<span className="text-xs text-[var(--text-secondary)] text-center mt-1">Calidad Alta</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex flex-col items-center p-4 bg-red-50 dark:bg-red-900/20 rounded-lg">
|
||||||
<span className="text-[var(--text-secondary)]">Productos con Calidad Baja</span>
|
<AlertTriangle className="h-8 w-8 text-[var(--color-error)] mb-2" />
|
||||||
<span className="text-2xl font-bold text-[var(--color-error)]">
|
<span className="text-2xl font-bold text-[var(--color-error)]">
|
||||||
{dashboard?.quality_metrics?.low_quality_count || 0}
|
{dashboard?.quality_metrics?.low_quality_count || 0}
|
||||||
</span>
|
</span>
|
||||||
|
<span className="text-xs text-[var(--text-secondary)] text-center mt-1">Calidad Baja</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AnalyticsCard>
|
</AnalyticsCard>
|
||||||
|
|
||||||
<AnalyticsCard title="Tendencia de Calidad (Últimos 7 días)" loading={trendsLoading}>
|
<AnalyticsCard title="Tendencia de Calidad (Últimos 7 días)" loading={trendsLoading}>
|
||||||
{trends && trends.quality_trend && trends.quality_trend.length > 0 ? (
|
{trends && trends.quality_trend && trends.quality_trend.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height={200}>
|
<div className="pt-4">
|
||||||
|
<ResponsiveContainer width="100%" height={280}>
|
||||||
<LineChart data={trends.quality_trend}>
|
<LineChart data={trends.quality_trend}>
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="var(--border-primary)" />
|
<CartesianGrid strokeDasharray="3 3" stroke="var(--border-primary)" opacity={0.3} />
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="date"
|
dataKey="date"
|
||||||
stroke="var(--text-tertiary)"
|
stroke="var(--text-tertiary)"
|
||||||
tick={{ fill: 'var(--text-secondary)' }}
|
tick={{ fill: 'var(--text-secondary)', fontSize: 12 }}
|
||||||
|
tickLine={{ stroke: 'var(--border-primary)' }}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
stroke="var(--text-tertiary)"
|
stroke="var(--text-tertiary)"
|
||||||
tick={{ fill: 'var(--text-secondary)' }}
|
tick={{ fill: 'var(--text-secondary)', fontSize: 12 }}
|
||||||
domain={[0, 10]}
|
domain={[0, 10]}
|
||||||
ticks={[0, 2, 4, 6, 8, 10]}
|
ticks={[0, 2, 4, 6, 8, 10]}
|
||||||
|
tickLine={{ stroke: 'var(--border-primary)' }}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
contentStyle={{
|
contentStyle={{
|
||||||
backgroundColor: 'var(--bg-primary)',
|
backgroundColor: 'var(--bg-primary)',
|
||||||
border: '1px solid var(--border-primary)',
|
border: '1px solid var(--border-primary)',
|
||||||
borderRadius: '8px'
|
borderRadius: '8px',
|
||||||
|
boxShadow: 'var(--shadow-lg)'
|
||||||
}}
|
}}
|
||||||
formatter={(value: any) => `${value.toFixed(1)} / 10`}
|
formatter={(value: any) => `${value.toFixed(1)} / 10`}
|
||||||
labelStyle={{ color: 'var(--text-primary)' }}
|
labelStyle={{ color: 'var(--text-primary)', fontWeight: 600 }}
|
||||||
/>
|
/>
|
||||||
<Line
|
<Line
|
||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey="quality_score"
|
dataKey="quality_score"
|
||||||
stroke="var(--color-warning)"
|
stroke="var(--color-warning)"
|
||||||
strokeWidth={2}
|
strokeWidth={3}
|
||||||
name="Puntuación de Calidad"
|
name="Puntuación de Calidad"
|
||||||
dot={{ fill: 'var(--color-warning)' }}
|
dot={{ fill: 'var(--color-warning)', r: 4 }}
|
||||||
|
activeDot={{ r: 6 }}
|
||||||
/>
|
/>
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-48 flex items-center justify-center text-[var(--text-tertiary)]">
|
<div className="h-80 flex flex-col items-center justify-center text-[var(--text-tertiary)]">
|
||||||
No hay datos de calidad disponibles
|
<Award className="h-16 w-16 mb-4 opacity-20" />
|
||||||
|
<p className="text-sm">No hay datos de calidad disponibles</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</AnalyticsCard>
|
</AnalyticsCard>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
</AnalyticsPageLayout>
|
</AnalyticsPageLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -144,58 +144,68 @@ const ProductionAnalyticsPage: React.FC = () => {
|
|||||||
mobileNoticeText={t('mobile.swipe_scroll_interact')}
|
mobileNoticeText={t('mobile.swipe_scroll_interact')}
|
||||||
>
|
>
|
||||||
{/* Tab Content */}
|
{/* Tab Content */}
|
||||||
<div className="min-h-screen">
|
<div className="space-y-6">
|
||||||
{/* Overview Tab - Mixed Dashboard */}
|
{/* Overview Tab - Mixed Dashboard */}
|
||||||
{activeTab === 'overview' && (
|
{activeTab === 'overview' && (
|
||||||
<div className="grid gap-6">
|
<div className="grid grid-cols-1 gap-6">
|
||||||
<LiveBatchTrackerWidget />
|
<LiveBatchTrackerWidget />
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<OnTimeCompletionWidget />
|
<OnTimeCompletionWidget />
|
||||||
|
<CapacityUtilizationWidget />
|
||||||
|
</div>
|
||||||
<QualityScoreTrendsWidget />
|
<QualityScoreTrendsWidget />
|
||||||
<WasteDefectTrackerWidget />
|
<WasteDefectTrackerWidget />
|
||||||
<CapacityUtilizationWidget />
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Bakery Operations Tab */}
|
{/* Bakery Operations Tab */}
|
||||||
{activeTab === 'operations' && (
|
{activeTab === 'operations' && (
|
||||||
<div className="grid gap-6">
|
<div className="grid grid-cols-1 gap-6">
|
||||||
<TodaysScheduleSummaryWidget />
|
<TodaysScheduleSummaryWidget />
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<OnTimeCompletionWidget />
|
<OnTimeCompletionWidget />
|
||||||
<LiveBatchTrackerWidget />
|
|
||||||
<CapacityUtilizationWidget />
|
<CapacityUtilizationWidget />
|
||||||
</div>
|
</div>
|
||||||
|
<LiveBatchTrackerWidget />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Cost & Efficiency Tab */}
|
{/* Cost & Efficiency Tab */}
|
||||||
{activeTab === 'cost-efficiency' && (
|
{activeTab === 'cost-efficiency' && (
|
||||||
<div className="grid gap-6">
|
<div className="grid grid-cols-1 gap-6">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<CostPerUnitWidget />
|
<CostPerUnitWidget />
|
||||||
<WasteDefectTrackerWidget />
|
|
||||||
<YieldPerformanceWidget />
|
<YieldPerformanceWidget />
|
||||||
</div>
|
</div>
|
||||||
|
<WasteDefectTrackerWidget />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Quality Assurance Tab */}
|
{/* Quality Assurance Tab */}
|
||||||
{activeTab === 'quality' && (
|
{activeTab === 'quality' && (
|
||||||
<div className="grid gap-6">
|
<div className="grid grid-cols-1 gap-6">
|
||||||
<QualityScoreTrendsWidget />
|
<QualityScoreTrendsWidget />
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<WasteDefectTrackerWidget />
|
<WasteDefectTrackerWidget />
|
||||||
<TopDefectTypesWidget />
|
<TopDefectTypesWidget />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Equipment & Maintenance Tab */}
|
{/* Equipment & Maintenance Tab */}
|
||||||
{activeTab === 'equipment' && (
|
{activeTab === 'equipment' && (
|
||||||
<div className="grid gap-6">
|
<div className="grid grid-cols-1 gap-6">
|
||||||
<EquipmentStatusWidget />
|
<EquipmentStatusWidget />
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<MaintenanceScheduleWidget />
|
<MaintenanceScheduleWidget />
|
||||||
<EquipmentEfficiencyWidget />
|
<EquipmentEfficiencyWidget />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* AI Insights Tab */}
|
{/* AI Insights Tab */}
|
||||||
{activeTab === 'ai-insights' && (
|
{activeTab === 'ai-insights' && (
|
||||||
<div className="grid gap-6">
|
<div className="grid grid-cols-1 gap-6">
|
||||||
<AIInsightsWidget />
|
<AIInsightsWidget />
|
||||||
<PredictiveMaintenanceWidget />
|
<PredictiveMaintenanceWidget />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -218,19 +218,20 @@ const AIInsightsPage: React.FC = () => {
|
|||||||
showMobileNotice={true}
|
showMobileNotice={true}
|
||||||
>
|
>
|
||||||
{/* Category Filter */}
|
{/* Category Filter */}
|
||||||
<Card className="p-6">
|
<Card className="p-6 bg-gradient-to-br from-[var(--bg-primary)] to-[var(--bg-secondary)]">
|
||||||
<div className="flex flex-wrap gap-2">
|
<h3 className="text-sm font-semibold text-[var(--text-secondary)] uppercase tracking-wider mb-4">Filtrar por Categoría</h3>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<button
|
<button
|
||||||
key={category.value}
|
key={category.value}
|
||||||
onClick={() => setSelectedCategory(category.value)}
|
onClick={() => setSelectedCategory(category.value)}
|
||||||
className={`px-4 py-2 rounded-full text-sm font-medium transition-colors ${
|
className={`px-5 py-2.5 rounded-full text-sm font-semibold transition-all transform hover:scale-105 ${
|
||||||
selectedCategory === category.value
|
selectedCategory === category.value
|
||||||
? 'bg-blue-600 text-white'
|
? 'bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-primary-light)] text-white shadow-lg'
|
||||||
: 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)] hover:bg-[var(--bg-quaternary)]'
|
: 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)] hover:bg-[var(--bg-quaternary)] border border-[var(--border-primary)]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{category.label} ({category.count})
|
{category.label} <span className="ml-1 opacity-75">({category.count})</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -239,26 +240,32 @@ const AIInsightsPage: React.FC = () => {
|
|||||||
{/* Insights List */}
|
{/* Insights List */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{filteredInsights.map((insight) => (
|
{filteredInsights.map((insight) => (
|
||||||
<Card key={insight.id} className="p-6">
|
<Card key={insight.id} className="p-6 hover:shadow-lg transition-shadow border-l-4" style={{borderLeftColor: insight.priority === 'high' ? 'var(--color-error)' : insight.priority === 'medium' ? 'var(--color-warning)' : 'var(--color-success)'}}>
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center space-x-3 mb-3">
|
<div className="flex items-center space-x-3 mb-4">
|
||||||
<div className={`p-2 rounded-lg ${getTypeColor(insight.type)}`}>
|
<div className={`p-3 rounded-xl ${getTypeColor(insight.type)} shadow-sm`}>
|
||||||
{getTypeIcon(insight.type)}
|
{getTypeIcon(insight.type)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<Badge variant={getPriorityColor(insight.priority)}>
|
<Badge variant={getPriorityColor(insight.priority)}>
|
||||||
{insight.priority === 'high' ? 'Alta' : insight.priority === 'medium' ? 'Media' : 'Baja'} Prioridad
|
{insight.priority === 'high' ? '🔴 Alta' : insight.priority === 'medium' ? '🟡 Media' : '🟢 Baja'} Prioridad
|
||||||
|
</Badge>
|
||||||
|
<Badge variant="secondary">
|
||||||
|
<Target className="w-3 h-3 mr-1" />
|
||||||
|
{insight.confidence}% confianza
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant="secondary">{insight.confidence}% confianza</Badge>
|
|
||||||
{insight.actionable && (
|
{insight.actionable && (
|
||||||
<Badge variant="primary">Accionable</Badge>
|
<Badge variant="primary">
|
||||||
|
<Zap className="w-3 h-3 mr-1" />
|
||||||
|
Accionable
|
||||||
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">{insight.title}</h3>
|
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-2">{insight.title}</h3>
|
||||||
<p className="text-[var(--text-secondary)] mb-3">{getInsightDescription(insight)}</p>
|
<p className="text-[var(--text-secondary)] leading-relaxed mb-4">{getInsightDescription(insight)}</p>
|
||||||
|
|
||||||
{/* Impact */}
|
{/* Impact */}
|
||||||
{insight.impact_value && insight.impact_type && (
|
{insight.impact_value && insight.impact_type && (
|
||||||
|
|||||||
@@ -119,7 +119,8 @@ const EventRegistryPage: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Controls Bar */}
|
{/* Controls Bar */}
|
||||||
<div className="mb-6 flex flex-wrap items-center justify-between gap-4">
|
<Card className="p-4 mb-6 bg-gradient-to-r from-[var(--bg-primary)] to-[var(--bg-secondary)]">
|
||||||
|
<div className="flex flex-wrap items-center justify-between gap-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant={showFilters ? 'primary' : 'secondary'}
|
variant={showFilters ? 'primary' : 'secondary'}
|
||||||
@@ -166,6 +167,7 @@ const EventRegistryPage: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="flex gap-6">
|
<div className="flex gap-6">
|
||||||
@@ -213,38 +215,38 @@ const EventRegistryPage: React.FC = () => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto -mx-6">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead className="border-b border-gray-200 bg-gray-50">
|
<thead className="border-b border-[var(--border-primary)] bg-[var(--bg-secondary)]">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-[var(--text-secondary)]">
|
||||||
Timestamp
|
Timestamp
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-[var(--text-secondary)]">
|
||||||
Servicio
|
Servicio
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-[var(--text-secondary)]">
|
||||||
Acción
|
Acción
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-[var(--text-secondary)]">
|
||||||
Recurso
|
Recurso
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-[var(--text-secondary)]">
|
||||||
Severidad
|
Severidad
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-[var(--text-secondary)]">
|
||||||
Descripción
|
Descripción
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
<th className="px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-[var(--text-secondary)]">
|
||||||
Acciones
|
Acciones
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-200 bg-white">
|
<tbody className="divide-y divide-[var(--border-primary)] bg-[var(--bg-primary)]">
|
||||||
{auditLogs.map((event) => (
|
{auditLogs.map((event) => (
|
||||||
<tr
|
<tr
|
||||||
key={event.id}
|
key={event.id}
|
||||||
className="hover:bg-gray-50 transition-colors"
|
className="hover:bg-[var(--bg-secondary)] transition-colors"
|
||||||
>
|
>
|
||||||
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-900">
|
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-900">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
@@ -299,31 +301,31 @@ const EventRegistryPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
<div className="border-t border-gray-200 bg-gray-50 px-6 py-4">
|
<div className="border-t border-[var(--border-primary)] bg-[var(--bg-secondary)] px-6 py-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="text-sm text-gray-700">
|
<div className="text-sm text-[var(--text-secondary)]">
|
||||||
Mostrando{' '}
|
Mostrando{' '}
|
||||||
<span className="font-medium">
|
<span className="font-semibold text-[var(--text-primary)]">
|
||||||
{(currentPage - 1) * pageSize + 1}
|
{(currentPage - 1) * pageSize + 1}
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
a{' '}
|
a{' '}
|
||||||
<span className="font-medium">
|
<span className="font-semibold text-[var(--text-primary)]">
|
||||||
{Math.min(currentPage * pageSize, auditLogs.length)}
|
{Math.min(currentPage * pageSize, auditLogs.length)}
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
de{' '}
|
de{' '}
|
||||||
<span className="font-medium">{auditLogs.length}</span>{' '}
|
<span className="font-semibold text-[var(--text-primary)]">{auditLogs.length}</span>{' '}
|
||||||
eventos
|
eventos
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
|
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
|
||||||
disabled={currentPage === 1}
|
disabled={currentPage === 1}
|
||||||
>
|
>
|
||||||
Anterior
|
← Anterior
|
||||||
</Button>
|
</Button>
|
||||||
<span className="text-sm text-gray-700">
|
<span className="text-sm font-medium text-[var(--text-primary)] px-3 py-1 bg-[var(--bg-primary)] rounded-lg">
|
||||||
Página {currentPage} de {totalPages}
|
Página {currentPage} de {totalPages}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
@@ -332,7 +334,7 @@ const EventRegistryPage: React.FC = () => {
|
|||||||
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
|
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
|
||||||
disabled={currentPage === totalPages}
|
disabled={currentPage === totalPages}
|
||||||
>
|
>
|
||||||
Siguiente
|
Siguiente →
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -254,9 +254,9 @@ const ForecastingPage: React.FC = () => {
|
|||||||
{/* Ingredient Selection Section */}
|
{/* Ingredient Selection Section */}
|
||||||
<div className="lg:col-span-2 space-y-6">
|
<div className="lg:col-span-2 space-y-6">
|
||||||
{/* Ingredients Grid - Similar to POSPage products */}
|
{/* Ingredients Grid - Similar to POSPage products */}
|
||||||
<Card className="p-4">
|
<Card className="p-6">
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center">
|
<h3 className="text-lg font-semibold mb-4 flex items-center text-[var(--text-primary)]">
|
||||||
<Brain className="w-5 h-5 mr-2" />
|
<Brain className="w-5 h-5 mr-2 text-[var(--color-primary)]" />
|
||||||
Ingredientes Disponibles ({products.length})
|
Ingredientes Disponibles ({products.length})
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
@@ -333,8 +333,8 @@ const ForecastingPage: React.FC = () => {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Period Selection */}
|
{/* Period Selection */}
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center">
|
<h3 className="text-lg font-semibold mb-4 flex items-center text-[var(--text-primary)]">
|
||||||
<Calendar className="w-5 h-5 mr-2" />
|
<Calendar className="w-5 h-5 mr-2 text-[var(--color-info)]" />
|
||||||
Configuración
|
Configuración
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@@ -345,7 +345,7 @@ const ForecastingPage: React.FC = () => {
|
|||||||
<select
|
<select
|
||||||
value={forecastPeriod}
|
value={forecastPeriod}
|
||||||
onChange={(e) => setForecastPeriod(e.target.value)}
|
onChange={(e) => setForecastPeriod(e.target.value)}
|
||||||
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md"
|
className="w-full px-4 py-3 border border-[var(--border-secondary)] rounded-lg bg-[var(--bg-primary)] text-[var(--text-primary)] focus:ring-2 focus:ring-[var(--color-primary)] focus:border-transparent transition-all"
|
||||||
disabled={isGenerating}
|
disabled={isGenerating}
|
||||||
>
|
>
|
||||||
{periods.map(period => (
|
{periods.map(period => (
|
||||||
@@ -355,11 +355,14 @@ const ForecastingPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedProduct && (
|
{selectedProduct && (
|
||||||
<div className="p-3 bg-[var(--bg-secondary)] rounded-lg">
|
<div className="p-4 bg-gradient-to-br from-[var(--color-primary)]/10 to-[var(--color-primary-light)]/5 rounded-lg border border-[var(--color-primary)]/20">
|
||||||
<p className="text-sm font-medium text-[var(--text-primary)]">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<Package className="w-4 h-4 text-[var(--color-primary)]" />
|
||||||
|
<p className="text-sm font-semibold text-[var(--text-primary)]">
|
||||||
{products.find(p => p.id === selectedProduct)?.name}
|
{products.find(p => p.id === selectedProduct)?.name}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-[var(--text-secondary)]">
|
</div>
|
||||||
|
<p className="text-xs text-[var(--text-secondary)] ml-6">
|
||||||
Predicción para {forecastPeriod} días
|
Predicción para {forecastPeriod} días
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -368,9 +371,9 @@ const ForecastingPage: React.FC = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Generate Forecast */}
|
{/* Generate Forecast */}
|
||||||
<Card className="p-6">
|
<Card className="p-6 bg-gradient-to-br from-[var(--bg-primary)] to-[var(--bg-secondary)]">
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center">
|
<h3 className="text-lg font-semibold mb-4 flex items-center text-[var(--text-primary)]">
|
||||||
<Zap className="w-5 h-5 mr-2" />
|
<Zap className="w-5 h-5 mr-2 text-[var(--color-warning)]" />
|
||||||
Generar Predicción
|
Generar Predicción
|
||||||
</h3>
|
</h3>
|
||||||
<Button
|
<Button
|
||||||
@@ -393,9 +396,12 @@ const ForecastingPage: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{!selectedProduct && (
|
{!selectedProduct && (
|
||||||
<p className="text-xs text-[var(--text-tertiary)] mt-2 text-center">
|
<div className="mt-3 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
||||||
|
<p className="text-xs text-[var(--color-info)] text-center flex items-center justify-center gap-2">
|
||||||
|
<AlertTriangle className="w-4 h-4" />
|
||||||
Selecciona un ingrediente para continuar
|
Selecciona un ingrediente para continuar
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -403,27 +409,30 @@ const ForecastingPage: React.FC = () => {
|
|||||||
|
|
||||||
{/* Results Section */}
|
{/* Results Section */}
|
||||||
{hasGeneratedForecast && forecasts.length > 0 && (
|
{hasGeneratedForecast && forecasts.length > 0 && (
|
||||||
<>
|
<div className="space-y-6">
|
||||||
{/* Results Header */}
|
{/* Results Header */}
|
||||||
<Card className="p-6">
|
<Card className="p-6 bg-gradient-to-br from-[var(--bg-primary)] to-[var(--bg-secondary)]">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-bold text-[var(--text-primary)]">Resultados de Predicción</h3>
|
<h3 className="text-2xl font-bold text-[var(--text-primary)] flex items-center gap-2">
|
||||||
<p className="text-[var(--text-secondary)]">
|
<TrendingUp className="w-6 h-6 text-[var(--color-success)]" />
|
||||||
|
Resultados de Predicción
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-[var(--text-secondary)] mt-1">
|
||||||
{products.find(p => p.id === selectedProduct)?.name} • {forecastPeriod} días
|
{products.find(p => p.id === selectedProduct)?.name} • {forecastPeriod} días
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Chart View */}
|
{/* Chart View */}
|
||||||
<div className="min-h-96">
|
<div className="min-h-96 p-4 bg-[var(--bg-primary)] rounded-lg">
|
||||||
<DemandChart
|
<DemandChart
|
||||||
data={forecasts}
|
data={forecasts}
|
||||||
product={selectedProduct}
|
product={selectedProduct}
|
||||||
period={forecastPeriod}
|
period={forecastPeriod}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
error={hasError ? 'Error al cargar las predicciones' : null}
|
error={hasError ? 'Error al cargar las predicciones' : null}
|
||||||
height={400}
|
height={450}
|
||||||
title=""
|
title=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -433,25 +442,25 @@ const ForecastingPage: React.FC = () => {
|
|||||||
{currentInsights.length > 0 && (
|
{currentInsights.length > 0 && (
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4 flex items-center">
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4 flex items-center">
|
||||||
<Activity className="w-5 h-5 mr-2" />
|
<Activity className="w-5 h-5 mr-2 text-[var(--color-info)]" />
|
||||||
Factores de Influencia
|
Factores de Influencia
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{currentInsights.map((insight, index) => {
|
{currentInsights.map((insight, index) => {
|
||||||
const IconComponent = insight.icon;
|
const IconComponent = insight.icon;
|
||||||
return (
|
return (
|
||||||
<div key={index} className="flex items-start space-x-3 p-4 bg-[var(--bg-secondary)] rounded-lg">
|
<div key={index} className="flex items-start space-x-3 p-4 bg-[var(--bg-secondary)] rounded-lg hover:bg-[var(--bg-tertiary)] transition-all border border-transparent hover:border-[var(--border-primary)]">
|
||||||
<div className={`p-2 rounded-lg ${
|
<div className={`p-2 rounded-lg ${
|
||||||
insight.impact === 'positive' ? 'bg-[var(--color-success-100)] text-[var(--color-success-600)]' :
|
insight.impact === 'positive' ? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300' :
|
||||||
insight.impact === 'high' ? 'bg-[var(--color-error-100)] text-[var(--color-error-600)]' :
|
insight.impact === 'high' ? 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300' :
|
||||||
insight.impact === 'moderate' ? 'bg-[var(--color-warning-100)] text-[var(--color-warning-600)]' :
|
insight.impact === 'moderate' ? 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300' :
|
||||||
'bg-[var(--color-info-100)] text-[var(--color-info-600)]'
|
'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300'
|
||||||
}`}>
|
}`}>
|
||||||
<IconComponent className="w-4 h-4" />
|
<IconComponent className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="text-sm font-medium text-[var(--text-primary)]">{insight.title}</p>
|
<p className="text-sm font-semibold text-[var(--text-primary)]">{insight.title}</p>
|
||||||
<p className="text-xs text-[var(--text-secondary)]">{insight.description}</p>
|
<p className="text-xs text-[var(--text-secondary)] mt-1">{insight.description}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -459,7 +468,7 @@ const ForecastingPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Help Section - Only when no models available */}
|
{/* Help Section - Only when no models available */}
|
||||||
|
|||||||
@@ -226,48 +226,48 @@ const PerformanceAnalyticsPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{/* Vista General Tab */}
|
{/* Vista General Tab */}
|
||||||
{activeTab === 'overview' && !isLoading && (
|
{activeTab === 'overview' && !isLoading && (
|
||||||
<>
|
<div className="space-y-6">
|
||||||
{/* Department Comparison Matrix */}
|
{/* Department Comparison Matrix */}
|
||||||
{departments && departments.length > 0 && (
|
{departments && departments.length > 0 && (
|
||||||
<AnalyticsCard title="Comparación de Departamentos">
|
<AnalyticsCard title="Comparación de Departamentos">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{departments.map((dept) => (
|
{departments.map((dept) => (
|
||||||
<div key={dept.department_id} className="border rounded-lg p-4">
|
<div key={dept.department_id} className="border border-[var(--border-primary)] rounded-lg p-5 bg-[var(--bg-secondary)] hover:bg-[var(--bg-tertiary)] transition-all">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h4 className="font-medium text-[var(--text-primary)]">
|
<h4 className="font-semibold text-lg text-[var(--text-primary)]">
|
||||||
{dept.department_name}
|
{dept.department_name}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-3">
|
||||||
<span className="text-lg font-semibold text-[var(--text-primary)]">
|
<span className="text-2xl font-bold text-[var(--text-primary)]">
|
||||||
{dept.efficiency.toFixed(1)}%
|
{dept.efficiency.toFixed(1)}%
|
||||||
</span>
|
</span>
|
||||||
{getTrendIcon(dept.trend)}
|
{getTrendIcon(dept.trend)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-3 gap-4 text-sm">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div>
|
<div className="p-3 bg-[var(--bg-primary)] rounded-lg">
|
||||||
<p className="text-[var(--text-tertiary)] text-xs">
|
<p className="text-[var(--text-tertiary)] text-xs uppercase tracking-wide mb-1">
|
||||||
{dept.metrics.primary_metric.label}
|
{dept.metrics.primary_metric.label}
|
||||||
</p>
|
</p>
|
||||||
<p className="font-medium">
|
<p className="font-bold text-lg text-[var(--text-primary)]">
|
||||||
{dept.metrics.primary_metric.value.toFixed(1)}
|
{dept.metrics.primary_metric.value.toFixed(1)}
|
||||||
{dept.metrics.primary_metric.unit}
|
{dept.metrics.primary_metric.unit}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="p-3 bg-[var(--bg-primary)] rounded-lg">
|
||||||
<p className="text-[var(--text-tertiary)] text-xs">
|
<p className="text-[var(--text-tertiary)] text-xs uppercase tracking-wide mb-1">
|
||||||
{dept.metrics.secondary_metric.label}
|
{dept.metrics.secondary_metric.label}
|
||||||
</p>
|
</p>
|
||||||
<p className="font-medium">
|
<p className="font-bold text-lg text-[var(--text-primary)]">
|
||||||
{dept.metrics.secondary_metric.value.toFixed(1)}
|
{dept.metrics.secondary_metric.value.toFixed(1)}
|
||||||
{dept.metrics.secondary_metric.unit}
|
{dept.metrics.secondary_metric.unit}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="p-3 bg-[var(--bg-primary)] rounded-lg">
|
||||||
<p className="text-[var(--text-tertiary)] text-xs">
|
<p className="text-[var(--text-tertiary)] text-xs uppercase tracking-wide mb-1">
|
||||||
{dept.metrics.tertiary_metric.label}
|
{dept.metrics.tertiary_metric.label}
|
||||||
</p>
|
</p>
|
||||||
<p className="font-medium">
|
<p className="font-bold text-lg text-[var(--text-primary)]">
|
||||||
{dept.metrics.tertiary_metric.value.toFixed(1)}
|
{dept.metrics.tertiary_metric.value.toFixed(1)}
|
||||||
{dept.metrics.tertiary_metric.unit}
|
{dept.metrics.tertiary_metric.unit}
|
||||||
</p>
|
</p>
|
||||||
@@ -282,7 +282,8 @@ const PerformanceAnalyticsPage: React.FC = () => {
|
|||||||
{/* Process Efficiency Breakdown */}
|
{/* Process Efficiency Breakdown */}
|
||||||
{processScore && (
|
{processScore && (
|
||||||
<AnalyticsCard title="Desglose de Eficiencia por Procesos">
|
<AnalyticsCard title="Desglose de Eficiencia por Procesos">
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
<div className="pt-4">
|
||||||
|
<ResponsiveContainer width="100%" height={350}>
|
||||||
<BarChart
|
<BarChart
|
||||||
data={[
|
data={[
|
||||||
{ name: 'Producción', value: processScore.production_efficiency, weight: processScore.breakdown.production.weight },
|
{ name: 'Producción', value: processScore.production_efficiency, weight: processScore.breakdown.production.weight },
|
||||||
@@ -291,18 +292,34 @@ const PerformanceAnalyticsPage: React.FC = () => {
|
|||||||
{ name: 'Pedidos', value: processScore.order_efficiency, weight: processScore.breakdown.orders.weight },
|
{ name: 'Pedidos', value: processScore.order_efficiency, weight: processScore.breakdown.orders.weight },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" stroke="var(--border-primary)" opacity={0.3} />
|
||||||
<XAxis dataKey="name" />
|
<XAxis
|
||||||
<YAxis />
|
dataKey="name"
|
||||||
<Tooltip />
|
tick={{ fill: 'var(--text-secondary)', fontSize: 12 }}
|
||||||
<Legend />
|
tickLine={{ stroke: 'var(--border-primary)' }}
|
||||||
<Bar dataKey="value" fill="var(--color-primary)" name="Eficiencia (%)" />
|
/>
|
||||||
<Bar dataKey="weight" fill="var(--color-secondary)" name="Peso (%)" />
|
<YAxis
|
||||||
|
tick={{ fill: 'var(--text-secondary)', fontSize: 12 }}
|
||||||
|
tickLine={{ stroke: 'var(--border-primary)' }}
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
contentStyle={{
|
||||||
|
backgroundColor: 'var(--bg-primary)',
|
||||||
|
border: '1px solid var(--border-primary)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: 'var(--shadow-lg)'
|
||||||
|
}}
|
||||||
|
labelStyle={{ color: 'var(--text-primary)', fontWeight: 600 }}
|
||||||
|
/>
|
||||||
|
<Legend wrapperStyle={{ paddingTop: '20px' }} />
|
||||||
|
<Bar dataKey="value" fill="var(--color-primary)" name="Eficiencia (%)" radius={[8, 8, 0, 0]} />
|
||||||
|
<Bar dataKey="weight" fill="var(--color-secondary)" name="Peso (%)" radius={[8, 8, 0, 0]} />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
</AnalyticsCard>
|
</AnalyticsCard>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Eficiencia Operativa Tab */}
|
{/* Eficiencia Operativa Tab */}
|
||||||
|
|||||||
@@ -357,61 +357,32 @@ const BakerySettingsPage: React.FC = () => {
|
|||||||
description={t('bakery.description')}
|
description={t('bakery.description')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Bakery Header Card */}
|
|
||||||
<Card className="p-4 sm:p-6">
|
|
||||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 sm:gap-6">
|
|
||||||
<div className="w-16 h-16 bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary-dark)] rounded-lg flex items-center justify-center text-white font-bold text-2xl flex-shrink-0 shadow-lg">
|
|
||||||
{config.name.charAt(0) || 'B'}
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<h1 className="text-xl sm:text-2xl font-bold text-text-primary mb-1 truncate">
|
|
||||||
{config.name || t('bakery.information.fields.name')}
|
|
||||||
</h1>
|
|
||||||
<p className="text-sm sm:text-base text-text-secondary truncate flex items-center gap-2">
|
|
||||||
<Mail className="w-4 h-4" />
|
|
||||||
{config.email}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs sm:text-sm text-text-tertiary truncate flex items-center gap-2 mt-1">
|
|
||||||
<MapPin className="w-4 h-4" />
|
|
||||||
{config.address}, {config.city}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{hasUnsavedChanges && (
|
|
||||||
<div className="flex items-center gap-2 text-sm text-yellow-600 dark:text-yellow-500 bg-yellow-50 dark:bg-yellow-900/20 px-3 py-2 rounded-lg w-full sm:w-auto">
|
|
||||||
<AlertCircle className="w-4 h-4" />
|
|
||||||
<span className="hidden sm:inline">{t('bakery.unsaved_changes')}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Search Bar */}
|
|
||||||
<SettingsSearch
|
|
||||||
placeholder={t('common.search') || 'Search settings...'}
|
|
||||||
onSearch={setSearchQuery}
|
|
||||||
className="max-w-md"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Tabs Navigation */}
|
{/* Tabs Navigation */}
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||||
<TabsList className="w-full sm:w-auto overflow-x-auto">
|
<Card className="p-2 bg-[var(--bg-secondary)]">
|
||||||
<TabsTrigger value="information" className="flex-1 sm:flex-none whitespace-nowrap">
|
<TabsList className="w-full grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||||
<Store className="w-4 h-4 mr-2" />
|
<TabsTrigger value="information" className="flex items-center justify-center gap-2 data-[state=active]:bg-[var(--color-primary)] data-[state=active]:text-white">
|
||||||
{t('bakery.tabs.information')}
|
<Store className="w-4 h-4" />
|
||||||
|
<span className="hidden sm:inline">{t('bakery.tabs.information')}</span>
|
||||||
|
<span className="sm:hidden">Info</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="hours" className="flex-1 sm:flex-none whitespace-nowrap">
|
<TabsTrigger value="hours" className="flex items-center justify-center gap-2 data-[state=active]:bg-[var(--color-primary)] data-[state=active]:text-white">
|
||||||
<Clock className="w-4 h-4 mr-2" />
|
<Clock className="w-4 h-4" />
|
||||||
{t('bakery.tabs.hours')}
|
<span className="hidden sm:inline">{t('bakery.tabs.hours')}</span>
|
||||||
|
<span className="sm:hidden">Hours</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="operations" className="flex-1 sm:flex-none whitespace-nowrap">
|
<TabsTrigger value="operations" className="flex items-center justify-center gap-2 data-[state=active]:bg-[var(--color-primary)] data-[state=active]:text-white">
|
||||||
<SettingsIcon className="w-4 h-4 mr-2" />
|
<SettingsIcon className="w-4 h-4" />
|
||||||
{t('bakery.tabs.operations')}
|
<span className="hidden sm:inline">{t('bakery.tabs.operations')}</span>
|
||||||
|
<span className="sm:hidden">Ops</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="notifications" className="flex-1 sm:flex-none whitespace-nowrap">
|
<TabsTrigger value="notifications" className="flex items-center justify-center gap-2 data-[state=active]:bg-[var(--color-primary)] data-[state=active]:text-white">
|
||||||
<Bell className="w-4 h-4 mr-2" />
|
<Bell className="w-4 h-4" />
|
||||||
{t('bakery.tabs.notifications')}
|
<span className="hidden sm:inline">{t('bakery.tabs.notifications')}</span>
|
||||||
|
<span className="sm:hidden">Notif</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Tab 1: Information */}
|
{/* Tab 1: Information */}
|
||||||
<TabsContent value="information">
|
<TabsContent value="information">
|
||||||
@@ -420,10 +391,10 @@ const BakerySettingsPage: React.FC = () => {
|
|||||||
<SettingSection
|
<SettingSection
|
||||||
title={t('bakery.information.general_section')}
|
title={t('bakery.information.general_section')}
|
||||||
description="Basic information about your bakery"
|
description="Basic information about your bakery"
|
||||||
icon={<Store className="w-5 h-5" />}
|
icon={<Store className="w-5 h-5 text-[var(--color-primary)]" />}
|
||||||
>
|
>
|
||||||
<div className="p-4 sm:p-6">
|
<div className="p-6 bg-gradient-to-br from-[var(--bg-primary)] to-[var(--bg-secondary)]">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<Input
|
<Input
|
||||||
label={t('bakery.information.fields.name')}
|
label={t('bakery.information.fields.name')}
|
||||||
value={config.name}
|
value={config.name}
|
||||||
@@ -467,16 +438,16 @@ const BakerySettingsPage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 sm:mt-6">
|
<div className="mt-6 md:col-span-2">
|
||||||
<label className="block text-sm font-medium text-text-secondary mb-2">
|
<label className="block text-sm font-semibold text-[var(--text-primary)] mb-3">
|
||||||
{t('bakery.information.fields.description')}
|
{t('bakery.information.fields.description')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={config.description}
|
value={config.description}
|
||||||
onChange={handleInputChange('description')}
|
onChange={handleInputChange('description')}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
rows={3}
|
rows={4}
|
||||||
className="w-full px-3 py-2 border border-[var(--border-primary)] rounded-lg resize-none bg-[var(--bg-primary)] text-[var(--text-primary)] disabled:bg-[var(--bg-secondary)] disabled:text-[var(--text-secondary)] text-sm sm:text-base focus:ring-2 focus:ring-[var(--color-primary)] focus:border-transparent"
|
className="w-full px-4 py-3 border-2 border-[var(--border-primary)] rounded-lg resize-none bg-[var(--bg-primary)] text-[var(--text-primary)] disabled:bg-[var(--bg-secondary)] disabled:text-[var(--text-secondary)] focus:ring-2 focus:ring-[var(--color-primary)] focus:border-[var(--color-primary)] transition-all"
|
||||||
placeholder={t('bakery.information.placeholders.description')}
|
placeholder={t('bakery.information.placeholders.description')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -487,10 +458,10 @@ const BakerySettingsPage: React.FC = () => {
|
|||||||
<SettingSection
|
<SettingSection
|
||||||
title={t('bakery.information.location_section')}
|
title={t('bakery.information.location_section')}
|
||||||
description="Where your bakery is located"
|
description="Where your bakery is located"
|
||||||
icon={<MapPin className="w-5 h-5" />}
|
icon={<MapPin className="w-5 h-5 text-[var(--color-success)]" />}
|
||||||
>
|
>
|
||||||
<div className="p-4 sm:p-6">
|
<div className="p-6 bg-gradient-to-br from-[var(--bg-primary)] to-[var(--bg-secondary)]">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<Input
|
<Input
|
||||||
label={t('bakery.information.fields.address')}
|
label={t('bakery.information.fields.address')}
|
||||||
value={config.address}
|
value={config.address}
|
||||||
@@ -536,10 +507,10 @@ const BakerySettingsPage: React.FC = () => {
|
|||||||
<SettingSection
|
<SettingSection
|
||||||
title={t('bakery.information.business_section')}
|
title={t('bakery.information.business_section')}
|
||||||
description="Tax and business configuration"
|
description="Tax and business configuration"
|
||||||
icon={<Building2 className="w-5 h-5" />}
|
icon={<Building2 className="w-5 h-5 text-[var(--color-info)]" />}
|
||||||
>
|
>
|
||||||
<div className="p-4 sm:p-6">
|
<div className="p-6 bg-gradient-to-br from-[var(--bg-primary)] to-[var(--bg-secondary)]">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<Input
|
<Input
|
||||||
label={t('bakery.information.fields.tax_id')}
|
label={t('bakery.information.fields.tax_id')}
|
||||||
value={config.taxId}
|
value={config.taxId}
|
||||||
@@ -583,7 +554,7 @@ const BakerySettingsPage: React.FC = () => {
|
|||||||
<SettingSection
|
<SettingSection
|
||||||
title={t('bakery.hours.title')}
|
title={t('bakery.hours.title')}
|
||||||
description="Configure your opening hours for each day of the week"
|
description="Configure your opening hours for each day of the week"
|
||||||
icon={<Clock className="w-5 h-5" />}
|
icon={<Clock className="w-5 h-5 text-[var(--color-warning)]" />}
|
||||||
>
|
>
|
||||||
{daysOfWeek.map((day) => {
|
{daysOfWeek.map((day) => {
|
||||||
const hours = businessHours[day.key];
|
const hours = businessHours[day.key];
|
||||||
@@ -725,33 +696,38 @@ const BakerySettingsPage: React.FC = () => {
|
|||||||
|
|
||||||
{/* Floating Save Button */}
|
{/* Floating Save Button */}
|
||||||
{hasUnsavedChanges && (
|
{hasUnsavedChanges && (
|
||||||
<div className="fixed bottom-6 right-4 sm:right-6 left-4 sm:left-auto z-50">
|
<div className="fixed bottom-6 right-4 sm:right-6 left-4 sm:left-auto z-50 animate-fade-in">
|
||||||
<Card className="p-3 sm:p-4 shadow-xl border-2 border-[var(--color-primary)]">
|
<Card className="p-4 sm:p-5 shadow-2xl border-2 border-[var(--color-primary)] bg-[var(--bg-primary)]">
|
||||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3">
|
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-4">
|
||||||
<div className="flex items-center gap-2 text-sm text-text-secondary">
|
<div className="flex items-center gap-3">
|
||||||
<AlertCircle className="w-4 h-4 text-yellow-500 flex-shrink-0" />
|
<div className="p-2 bg-yellow-100 dark:bg-yellow-900/30 rounded-lg">
|
||||||
<span>{t('bakery.unsaved_changes')}</span>
|
<AlertCircle className="w-5 h-5 text-yellow-600 dark:text-yellow-400 animate-pulse" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-[var(--text-primary)]">{t('bakery.unsaved_changes')}</p>
|
||||||
|
<p className="text-xs text-[var(--text-secondary)]">Save or discard your changes</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3 sm:ml-4">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="md"
|
||||||
onClick={handleDiscard}
|
onClick={handleDiscard}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="flex-1 sm:flex-none"
|
className="flex-1 sm:flex-none border-2"
|
||||||
>
|
>
|
||||||
<X className="w-4 h-4 mr-1" />
|
<X className="w-4 h-4 mr-2" />
|
||||||
{t('common.discard')}
|
{t('common.discard')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="sm"
|
size="md"
|
||||||
onClick={activeTab === 'operations' || activeTab === 'notifications' ? handleSaveOperationalSettings : handleSaveConfig}
|
onClick={activeTab === 'operations' || activeTab === 'notifications' ? handleSaveOperationalSettings : handleSaveConfig}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
loadingText={t('common.saving')}
|
loadingText={t('common.saving')}
|
||||||
className="flex-1 sm:flex-none"
|
className="flex-1 sm:flex-none shadow-lg"
|
||||||
>
|
>
|
||||||
<Save className="w-4 h-4 mr-1" />
|
<Save className="w-4 h-4 mr-2" />
|
||||||
{t('common.save')}
|
{t('common.save')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -361,83 +361,70 @@ const ProfilePage: React.FC = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-4 sm:p-6 space-y-6 pb-32">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Ajustes"
|
title="Ajustes de Perfil"
|
||||||
description="Gestiona tu información personal y preferencias de comunicación"
|
description="Gestiona tu información personal y preferencias de comunicación"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Tab Navigation */}
|
{/* Tab Navigation */}
|
||||||
<Tabs
|
<Card className="p-2 bg-[var(--bg-secondary)]">
|
||||||
items={tabItems}
|
<div className="w-full grid grid-cols-1 sm:grid-cols-3 gap-2">
|
||||||
activeTab={activeTab}
|
<button
|
||||||
onTabChange={setActiveTab}
|
onClick={() => setActiveTab('profile')}
|
||||||
fullWidth={true}
|
className={`flex items-center justify-center gap-2 px-4 py-3 rounded-lg text-sm font-semibold transition-all ${
|
||||||
variant="pills"
|
activeTab === 'profile'
|
||||||
size="md"
|
? 'bg-[var(--color-primary)] text-white shadow-md'
|
||||||
/>
|
: 'bg-transparent text-[var(--text-secondary)] hover:bg-[var(--bg-tertiary)]'
|
||||||
|
}`}
|
||||||
{/* Profile Header */}
|
|
||||||
{activeTab === 'profile' && (
|
|
||||||
<Card className="p-6">
|
|
||||||
<div className="flex items-center gap-6">
|
|
||||||
<div className="relative">
|
|
||||||
<Avatar
|
|
||||||
src={profile?.avatar || undefined}
|
|
||||||
alt={profile?.full_name || `${profileData.first_name} ${profileData.last_name}` || 'Usuario'}
|
|
||||||
name={profile?.avatar ? (profile?.full_name || `${profileData.first_name} ${profileData.last_name}`) : undefined}
|
|
||||||
size="xl"
|
|
||||||
className="w-20 h-20"
|
|
||||||
/>
|
|
||||||
<button className="absolute -bottom-1 -right-1 bg-color-primary text-white rounded-full p-2 shadow-lg hover:bg-color-primary-dark transition-colors">
|
|
||||||
<Camera className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<h1 className="text-2xl font-bold text-text-primary mb-1">
|
|
||||||
{profileData.first_name} {profileData.last_name}
|
|
||||||
</h1>
|
|
||||||
<p className="text-text-secondary">{profileData.email}</p>
|
|
||||||
{user?.role && (
|
|
||||||
<p className="text-sm text-text-tertiary mt-1">
|
|
||||||
{t(`global_roles.${user.role}`)}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<div className="flex items-center gap-2 mt-2">
|
|
||||||
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
|
||||||
<span className="text-sm text-text-tertiary">{t('settings:profile.online', 'En línea')}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
{!isEditing && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => setIsEditing(true)}
|
|
||||||
className="flex items-center gap-2"
|
|
||||||
>
|
>
|
||||||
<User className="w-4 h-4" />
|
<User className="w-4 h-4" />
|
||||||
{t('settings:profile.edit_profile', 'Editar Perfil')}
|
<span className="hidden sm:inline">Información Personal</span>
|
||||||
</Button>
|
<span className="sm:hidden">Perfil</span>
|
||||||
)}
|
</button>
|
||||||
<Button
|
<button
|
||||||
variant="outline"
|
onClick={() => setActiveTab('preferences')}
|
||||||
onClick={() => setShowPasswordForm(!showPasswordForm)}
|
className={`flex items-center justify-center gap-2 px-4 py-3 rounded-lg text-sm font-semibold transition-all ${
|
||||||
className="flex items-center gap-2"
|
activeTab === 'preferences'
|
||||||
|
? 'bg-[var(--color-primary)] text-white shadow-md'
|
||||||
|
: 'bg-transparent text-[var(--text-secondary)] hover:bg-[var(--bg-tertiary)]'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<Lock className="w-4 h-4" />
|
<Bell className="w-4 h-4" />
|
||||||
{t('settings:profile.change_password', 'Cambiar Contraseña')}
|
<span className="hidden sm:inline">Preferencias de Comunicación</span>
|
||||||
</Button>
|
<span className="sm:hidden">Preferencias</span>
|
||||||
</div>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('subscription')}
|
||||||
|
className={`flex items-center justify-center gap-2 px-4 py-3 rounded-lg text-sm font-semibold transition-all ${
|
||||||
|
activeTab === 'subscription'
|
||||||
|
? 'bg-[var(--color-primary)] text-white shadow-md'
|
||||||
|
: 'bg-transparent text-[var(--text-secondary)] hover:bg-[var(--bg-tertiary)]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Crown className="w-4 h-4" />
|
||||||
|
<span className="hidden sm:inline">Suscripción y Facturación</span>
|
||||||
|
<span className="sm:hidden">Suscripción</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Profile Form */}
|
{/* Profile Form */}
|
||||||
{activeTab === 'profile' && (
|
{activeTab === 'profile' && (
|
||||||
|
<>
|
||||||
|
{/* Personal Information Card */}
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h2 className="text-lg font-semibold mb-4">{t('settings:profile.personal_info', 'Información Personal')}</h2>
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<div className="p-2 bg-[var(--color-primary)]/10 rounded-lg">
|
||||||
|
<User className="w-5 h-5 text-[var(--color-primary)]" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold text-[var(--text-primary)]">{t('settings:profile.personal_info', 'Información Personal')}</h2>
|
||||||
|
<p className="text-sm text-[var(--text-secondary)]">Actualiza tus datos personales</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<Input
|
<Input
|
||||||
label={t('settings:profile.fields.first_name', 'Nombre')}
|
label={t('settings:profile.fields.first_name', 'Nombre')}
|
||||||
value={profileData.first_name}
|
value={profileData.first_name}
|
||||||
@@ -496,12 +483,12 @@ const ProfilePage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isEditing && (
|
{isEditing && (
|
||||||
<div className="flex gap-3 mt-6 pt-4 border-t">
|
<div className="flex gap-3 mt-6 pt-6 border-t border-[var(--border-primary)]">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setIsEditing(false)}
|
onClick={() => setIsEditing(false)}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2 border-2"
|
||||||
>
|
>
|
||||||
<X className="w-4 h-4" />
|
<X className="w-4 h-4" />
|
||||||
Cancelar
|
Cancelar
|
||||||
@@ -511,7 +498,7 @@ const ProfilePage: React.FC = () => {
|
|||||||
onClick={handleSaveProfile}
|
onClick={handleSaveProfile}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
loadingText="Guardando..."
|
loadingText="Guardando..."
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2 shadow-lg"
|
||||||
>
|
>
|
||||||
<Save className="w-4 h-4" />
|
<Save className="w-4 h-4" />
|
||||||
Guardar Cambios
|
Guardar Cambios
|
||||||
@@ -519,14 +506,23 @@ const ProfilePage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Password Change Form */}
|
{/* Password Change Form */}
|
||||||
{activeTab === 'profile' && showPasswordForm && (
|
{activeTab === 'profile' && showPasswordForm && (
|
||||||
<Card className="p-6">
|
<Card className="p-6 bg-gradient-to-br from-[var(--bg-primary)] to-[var(--bg-secondary)]">
|
||||||
<h2 className="text-lg font-semibold mb-4">Cambiar Contraseña</h2>
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<div className="p-2 bg-[var(--color-warning)]/10 rounded-lg">
|
||||||
|
<Lock className="w-5 h-5 text-[var(--color-warning)]" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold text-[var(--text-primary)]">Cambiar Contraseña</h2>
|
||||||
|
<p className="text-sm text-[var(--text-secondary)]">Actualiza tu contraseña de acceso</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 max-w-4xl">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
label="Contraseña Actual"
|
label="Contraseña Actual"
|
||||||
@@ -558,7 +554,7 @@ const ProfilePage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3 pt-6 mt-6 border-t">
|
<div className="flex gap-3 pt-6 mt-6 border-t border-[var(--border-primary)]">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -567,7 +563,9 @@ const ProfilePage: React.FC = () => {
|
|||||||
setErrors({});
|
setErrors({});
|
||||||
}}
|
}}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
|
className="border-2"
|
||||||
>
|
>
|
||||||
|
<X className="w-4 h-4 mr-2" />
|
||||||
Cancelar
|
Cancelar
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -575,7 +573,9 @@ const ProfilePage: React.FC = () => {
|
|||||||
onClick={handleChangePasswordSubmit}
|
onClick={handleChangePasswordSubmit}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
loadingText="Cambiando..."
|
loadingText="Cambiando..."
|
||||||
|
className="shadow-lg"
|
||||||
>
|
>
|
||||||
|
<Save className="w-4 h-4 mr-2" />
|
||||||
Cambiar Contraseña
|
Cambiar Contraseña
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user