Improve the frontend modals

This commit is contained in:
Urtzi Alfaro
2025-10-27 16:33:26 +01:00
parent 61376b7a9f
commit 858d985c92
143 changed files with 9289 additions and 2306 deletions

View File

@@ -1,7 +1,8 @@
import React from 'react';
import { ShoppingCart, TrendingUp, Clock, AlertTriangle } from 'lucide-react';
import { ShoppingCart, TrendingUp, Clock, AlertTriangle, Brain } from 'lucide-react';
import { Card, Input } from '../../../../../components/ui';
import type { ProcurementSettings } from '../../../../../api/types/settings';
import { useTranslation } from 'react-i18next';
interface ProcurementSettingsCardProps {
settings: ProcurementSettings;
@@ -14,6 +15,8 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
onChange,
disabled = false,
}) => {
const { t } = useTranslation('ajustes');
const handleChange = (field: keyof ProcurementSettings) => (
e: React.ChangeEvent<HTMLInputElement>
) => {
@@ -27,7 +30,7 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-6 flex items-center">
<ShoppingCart className="w-5 h-5 mr-2 text-[var(--color-primary)]" />
Compras y Aprovisionamiento
{t('procurement.title')}
</h3>
<div className="space-y-6">
@@ -35,7 +38,7 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
<div>
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<TrendingUp className="w-4 h-4 mr-2" />
Auto-Aprobación de Órdenes de Compra
{t('procurement.auto_approval')}
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 pl-6">
<div className="flex items-center gap-2 md:col-span-2 xl:col-span-3">
@@ -48,13 +51,13 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="auto_approve_enabled" className="text-sm text-[var(--text-secondary)]">
Habilitar auto-aprobación de órdenes de compra
{t('procurement.auto_approve_enabled')}
</label>
</div>
<Input
type="number"
label="Umbral de Auto-Aprobación (EUR)"
label={t('procurement.auto_approve_threshold')}
value={settings.auto_approve_threshold_eur}
onChange={handleChange('auto_approve_threshold_eur')}
disabled={disabled || !settings.auto_approve_enabled}
@@ -66,7 +69,7 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
<Input
type="number"
label="Puntuación Mínima de Proveedor"
label={t('procurement.min_supplier_score')}
value={settings.auto_approve_min_supplier_score}
onChange={handleChange('auto_approve_min_supplier_score')}
disabled={disabled || !settings.auto_approve_enabled}
@@ -86,7 +89,7 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="require_approval_new_suppliers" className="text-sm text-[var(--text-secondary)]">
Requiere aprobación para nuevos proveedores
{t('procurement.require_approval_new_suppliers')}
</label>
</div>
@@ -100,7 +103,7 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="require_approval_critical_items" className="text-sm text-[var(--text-secondary)]">
Requiere aprobación para artículos críticos
{t('procurement.require_approval_critical_items')}
</label>
</div>
</div>
@@ -110,12 +113,12 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
<div>
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<Clock className="w-4 h-4 mr-2" />
Planificación y Previsión
{t('procurement.planning')}
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 pl-6">
<Input
type="number"
label="Tiempo de Entrega (días)"
label={t('procurement.lead_time_days')}
value={settings.procurement_lead_time_days}
onChange={handleChange('procurement_lead_time_days')}
disabled={disabled}
@@ -127,7 +130,7 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
<Input
type="number"
label="Días de Previsión de Demanda"
label={t('procurement.demand_forecast_days')}
value={settings.demand_forecast_days}
onChange={handleChange('demand_forecast_days')}
disabled={disabled}
@@ -139,7 +142,7 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
<Input
type="number"
label="Stock de Seguridad (%)"
label={t('procurement.safety_stock_percentage')}
value={settings.safety_stock_percentage}
onChange={handleChange('safety_stock_percentage')}
disabled={disabled}
@@ -155,12 +158,12 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
<div>
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<AlertTriangle className="w-4 h-4 mr-2" />
Flujo de Aprobación
{t('procurement.workflow')}
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pl-6">
<Input
type="number"
label="Recordatorio de Aprobación (horas)"
label={t('procurement.approval_reminder_hours')}
value={settings.po_approval_reminder_hours}
onChange={handleChange('po_approval_reminder_hours')}
disabled={disabled}
@@ -172,7 +175,7 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
<Input
type="number"
label="Escalación Crítica (horas)"
label={t('procurement.critical_escalation_hours')}
value={settings.po_critical_escalation_hours}
onChange={handleChange('po_critical_escalation_hours')}
disabled={disabled}
@@ -183,6 +186,110 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
/>
</div>
</div>
{/* Smart Procurement Calculation */}
<div className="border-t border-[var(--border-primary)] pt-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<Brain className="w-4 h-4 mr-2" />
{t('procurement.smart_procurement')}
</h4>
<div className="space-y-3 pl-6">
<div className="flex items-start gap-2">
<input
type="checkbox"
id="use_reorder_rules"
checked={settings.use_reorder_rules}
onChange={handleChange('use_reorder_rules')}
disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5"
/>
<div className="flex flex-col">
<label htmlFor="use_reorder_rules" className="text-sm font-medium text-[var(--text-secondary)]">
{t('procurement.use_reorder_rules')}
</label>
<span className="text-xs text-[var(--text-tertiary)] mt-0.5">
{t('procurement.use_reorder_rules_desc')}
</span>
</div>
</div>
<div className="flex items-start gap-2">
<input
type="checkbox"
id="economic_rounding"
checked={settings.economic_rounding}
onChange={handleChange('economic_rounding')}
disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5"
/>
<div className="flex flex-col">
<label htmlFor="economic_rounding" className="text-sm font-medium text-[var(--text-secondary)]">
{t('procurement.economic_rounding')}
</label>
<span className="text-xs text-[var(--text-tertiary)] mt-0.5">
{t('procurement.economic_rounding_desc')}
</span>
</div>
</div>
<div className="flex items-start gap-2">
<input
type="checkbox"
id="respect_storage_limits"
checked={settings.respect_storage_limits}
onChange={handleChange('respect_storage_limits')}
disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5"
/>
<div className="flex flex-col">
<label htmlFor="respect_storage_limits" className="text-sm font-medium text-[var(--text-secondary)]">
{t('procurement.respect_storage_limits')}
</label>
<span className="text-xs text-[var(--text-tertiary)] mt-0.5">
{t('procurement.respect_storage_limits_desc')}
</span>
</div>
</div>
<div className="flex items-start gap-2">
<input
type="checkbox"
id="use_supplier_minimums"
checked={settings.use_supplier_minimums}
onChange={handleChange('use_supplier_minimums')}
disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5"
/>
<div className="flex flex-col">
<label htmlFor="use_supplier_minimums" className="text-sm font-medium text-[var(--text-secondary)]">
{t('procurement.use_supplier_minimums')}
</label>
<span className="text-xs text-[var(--text-tertiary)] mt-0.5">
{t('procurement.use_supplier_minimums_desc')}
</span>
</div>
</div>
<div className="flex items-start gap-2">
<input
type="checkbox"
id="optimize_price_tiers"
checked={settings.optimize_price_tiers}
onChange={handleChange('optimize_price_tiers')}
disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5"
/>
<div className="flex flex-col">
<label htmlFor="optimize_price_tiers" className="text-sm font-medium text-[var(--text-secondary)]">
{t('procurement.optimize_price_tiers')}
</label>
<span className="text-xs text-[var(--text-tertiary)] mt-0.5">
{t('procurement.optimize_price_tiers_desc')}
</span>
</div>
</div>
</div>
</div>
</div>
</Card>
);

View File

@@ -1,6 +1,6 @@
import React, { useState, useMemo } from 'react';
import { Brain, TrendingUp, AlertCircle, Play, RotateCcw, Eye, Loader, CheckCircle } from 'lucide-react';
import { Button, Badge, Modal, Table, Select, StatsGrid, StatusCard, SearchAndFilter, type FilterConfig, Card } from '../../../../components/ui';
import { Button, Badge, Modal, Table, Select, StatsGrid, StatusCard, SearchAndFilter, type FilterConfig, Card, EmptyState } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
import { useToast } from '../../../../hooks/ui/useToast';
import { useCurrentTenant } from '../../../../stores/tenant.store';
@@ -116,7 +116,7 @@ const ModelsConfigPage: React.FC = () => {
hasModel: !!model,
model,
isTraining,
lastTrainingDate: model?.created_at,
lastTrainingDate: model?.created_at || undefined,
accuracy: model ?
(model.training_metrics?.mape !== undefined ? (100 - model.training_metrics.mape) :
(model as any).mape !== undefined ? (100 - (model as any).mape) :
@@ -209,13 +209,12 @@ const ModelsConfigPage: React.FC = () => {
}
return (
<div className="p-6 space-y-6">
<div className="space-y-6">
<PageHeader
title="Configuración de Modelos IA"
description="Gestiona el entrenamiento y configuración de modelos de predicción para cada ingrediente"
/>
{/* Statistics Cards */}
<StatsGrid
stats={[
@@ -232,39 +231,33 @@ const ModelsConfigPage: React.FC = () => {
variant: 'warning',
},
{
title: 'Modelos Huérfanos',
value: orphanedModels.length,
icon: AlertCircle,
variant: 'info',
title: 'Modelos Activos',
value: modelStatuses.filter(s => s.status === 'active').length,
icon: CheckCircle,
variant: 'success',
},
{
title: 'Precisión Promedio',
value: statsError ? 'N/A' : (statistics?.average_accuracy ? `${(100 - statistics.average_accuracy).toFixed(1)}%` : 'N/A'),
value: statsError ? 'N/A' : (statistics?.average_accuracy ? `${Number(statistics.average_accuracy).toFixed(1)}%` : 'N/A'),
icon: TrendingUp,
variant: 'success',
},
{
title: 'Total Modelos',
value: modelStatuses.length,
icon: Brain,
variant: 'info',
},
{
title: 'Modelos Huérfanos',
value: orphanedModels.length,
icon: AlertCircle,
variant: 'error',
},
]}
columns={4}
columns={3}
/>
{/* Orphaned Models Warning */}
{orphanedModels.length > 0 && (
<Card className="p-4 bg-orange-50 border-orange-200">
<div className="flex items-start gap-3">
<AlertCircle className="w-5 h-5 text-orange-600 mt-0.5" />
<div>
<h4 className="font-medium text-orange-900 mb-1">
Modelos Huérfanos Detectados
</h4>
<p className="text-sm text-orange-700">
Se encontraron {orphanedModels.length} modelos entrenados para ingredientes que ya no existen en el inventario.
Estos modelos pueden ser eliminados para optimizar el espacio de almacenamiento.
</p>
</div>
</div>
</Card>
)}
{/* Search and Filter Controls */}
<SearchAndFilter
searchValue={searchTerm}
@@ -289,18 +282,16 @@ const ModelsConfigPage: React.FC = () => {
/>
{/* Models Grid */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{filteredStatuses.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 col-span-full">
<Brain className="w-12 h-12 text-[var(--color-secondary)] mb-4" />
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-2">
No se encontraron ingredientes
</h3>
<p className="text-[var(--text-secondary)] text-center">
No hay ingredientes que coincidan con los filtros aplicados.
</p>
</div>
) : (
{filteredStatuses.length === 0 ? (
<EmptyState
icon={Brain}
title="No se encontraron ingredientes"
description="No hay ingredientes que coincidan con los filtros aplicados."
className="col-span-full"
/>
) : (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{(
filteredStatuses.map((status) => {
// Get status configuration for the StatusCard
const statusConfig = {
@@ -335,7 +326,7 @@ const ModelsConfigPage: React.FC = () => {
id={status.ingredient.id}
statusIndicator={statusConfig}
title={status.ingredient.name}
subtitle={status.ingredient.category}
subtitle={status.ingredient.category || undefined}
primaryValue={status.accuracy ? status.accuracy.toFixed(1) : 'N/A'}
primaryValueLabel="Precisión"
secondaryInfo={status.lastTrainingDate ? {
@@ -371,7 +362,8 @@ const ModelsConfigPage: React.FC = () => {
);
})
)}
</div>
</div>
)}
{/* Training Modal */}
<Modal
@@ -463,4 +455,4 @@ const ModelsConfigPage: React.FC = () => {
);
};
export default ModelsConfigPage;
export default ModelsConfigPage;

View File

@@ -9,11 +9,7 @@ import { QualityTemplateManager } from '../../../../components/domain/production
* that are used during production processes.
*/
const QualityTemplatesPage: React.FC = () => {
return (
<div className="container mx-auto px-4 py-6">
<QualityTemplateManager />
</div>
);
return <QualityTemplateManager />;
};
export default QualityTemplatesPage;

View File

@@ -0,0 +1,580 @@
import React, { useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import {
Leaf,
TrendingDown,
Euro,
Award,
Target,
Droplets,
TreeDeciduous,
Calendar,
Download,
FileText,
Info,
HelpCircle
} from 'lucide-react';
import { PageHeader } from '../../../../components/layout';
import { StatsGrid, Button, Card, Tooltip } from '../../../../components/ui';
import { LoadingSpinner } from '../../../../components/ui';
import { formatters } from '../../../../components/ui/Stats/StatsPresets';
import { useSustainabilityMetrics } from '../../../../api/hooks/sustainability';
import { useCurrentTenant } from '../../../../stores/tenant.store';
const SustainabilityPage: React.FC = () => {
const { t } = useTranslation(['sustainability', 'common']);
const currentTenant = useCurrentTenant();
const tenantId = currentTenant?.id || '';
// Date range state (default to last 30 days)
const [dateRange, setDateRange] = useState<{ start?: string; end?: string }>({});
// Fetch sustainability metrics
const {
data: metrics,
isLoading,
error
} = useSustainabilityMetrics(tenantId, dateRange.start, dateRange.end, {
enabled: !!tenantId
});
// Build stats for StatsGrid
const sustainabilityStats = useMemo(() => {
if (!metrics) return [];
return [
{
title: t('sustainability:stats.total_waste_reduced', 'Total Waste Reduced'),
value: `${metrics.waste_metrics.total_waste_kg.toFixed(0)} kg`,
icon: TrendingDown,
variant: 'success' as const,
subtitle: t('sustainability:stats.from_baseline', 'From baseline'),
trend: metrics.waste_metrics.waste_percentage < 25 ? {
value: Math.abs(25 - metrics.waste_metrics.waste_percentage),
direction: 'down' as const,
label: t('sustainability:stats.vs_industry', 'vs industry avg')
} : undefined
},
{
title: t('sustainability:stats.waste_reduction_percentage', 'Waste Reduction'),
value: `${Math.abs(metrics.sdg_compliance.sdg_12_3.reduction_achieved).toFixed(1)}%`,
icon: Target,
variant: metrics.sdg_compliance.sdg_12_3.reduction_achieved >= 15 ? ('success' as const) : ('info' as const),
subtitle: t('sustainability:stats.progress_to_sdg', 'Progress to SDG 12.3'),
trend: {
value: metrics.sdg_compliance.sdg_12_3.progress_to_target,
direction: 'up' as const,
label: t('sustainability:stats.to_target', 'to 50% target')
}
},
{
title: t('sustainability:stats.co2_avoided', 'CO₂ Avoided'),
value: `${metrics.environmental_impact.co2_emissions.kg.toFixed(0)} kg`,
icon: Leaf,
variant: 'info' as const,
subtitle: `${metrics.environmental_impact.co2_emissions.trees_to_offset.toFixed(1)} ${t('sustainability:stats.trees', 'trees')}`
},
{
title: t('sustainability:stats.monthly_savings', 'Monthly Savings'),
value: `${metrics.financial_impact.potential_monthly_savings.toFixed(0)}`,
icon: Euro,
variant: 'success' as const,
subtitle: t('sustainability:stats.from_waste_reduction', 'From waste reduction')
},
{
title: t('sustainability:stats.sdg_progress', 'SDG 12.3 Progress'),
value: `${Math.round(metrics.sdg_compliance.sdg_12_3.progress_to_target)}%`,
icon: Award,
variant: metrics.sdg_compliance.sdg_12_3.status === 'sdg_compliant' ? ('success' as const) :
metrics.sdg_compliance.sdg_12_3.status === 'on_track' ? ('info' as const) : ('warning' as const),
subtitle: metrics.sdg_compliance.sdg_12_3.status_label
},
{
title: t('sustainability:stats.grant_programs', 'Grant Programs'),
value: Object.values(metrics.grant_readiness.grant_programs).filter(p => p.eligible).length.toString(),
icon: FileText,
variant: 'info' as const,
subtitle: t('sustainability:stats.eligible', 'Eligible programs')
}
];
}, [metrics, t]);
// Get SDG status color
const getSDGStatusColor = (status: string) => {
switch (status) {
case 'sdg_compliant':
return 'bg-green-500/10 text-green-600 border-green-500/20';
case 'on_track':
return 'bg-blue-500/10 text-blue-600 border-blue-500/20';
case 'progressing':
return 'bg-yellow-500/10 text-yellow-600 border-yellow-500/20';
default:
return 'bg-gray-500/10 text-gray-600 border-gray-500/20';
}
};
if (isLoading) {
return (
<div className="space-y-6 p-4 sm:p-6">
<PageHeader
title={t('sustainability:page.title', 'Sostenibilidad')}
description={t('sustainability:page.description', 'Seguimiento de impacto ambiental y cumplimiento SDG 12.3')}
/>
<div className="flex items-center justify-center py-12">
<LoadingSpinner size="lg" />
</div>
</div>
);
}
if (error || !metrics) {
return (
<div className="space-y-6 p-4 sm:p-6">
<PageHeader
title={t('sustainability:page.title', 'Sostenibilidad')}
description={t('sustainability:page.description', 'Seguimiento de impacto ambiental y cumplimiento SDG 12.3')}
/>
<Card className="p-6">
<div className="text-center py-8">
<Leaf className="w-12 h-12 mx-auto mb-3 text-[var(--text-secondary)] opacity-50" />
<p className="text-sm text-[var(--text-secondary)]">
{t('sustainability:errors.load_failed', 'Unable to load sustainability metrics')}
</p>
</div>
</Card>
</div>
);
}
return (
<div className="space-y-6 p-4 sm:p-6">
{/* Page Header */}
<PageHeader
title={t('sustainability:page.title', 'Sostenibilidad')}
description={t('sustainability:page.description', 'Seguimiento de impacto ambiental y cumplimiento SDG 12.3')}
actions={[
{
id: "export-report",
label: t('sustainability:actions.export_report', 'Exportar Informe'),
icon: Download,
onClick: () => {
// TODO: Implement export
console.log('Export sustainability report');
},
variant: "outline",
size: "sm"
}
]}
/>
{/* Stats Grid */}
<StatsGrid
stats={sustainabilityStats}
columns={3}
gap="lg"
/>
{/* Main Content Sections */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Waste Analytics Section */}
<Card className="p-6">
<div className="flex items-center justify-between mb-4">
<div>
<div className="flex items-center gap-2">
<h3 className="text-lg font-semibold text-[var(--text-primary)]">
{t('sustainability:sections.waste_analytics', 'Análisis de Residuos')}
</h3>
<Tooltip content={t('sustainability:tooltips.waste_analytics', 'Información detallada sobre los residuos generados en la producción')}>
<HelpCircle className="w-4 h-4 text-[var(--text-tertiary)] cursor-help" />
</Tooltip>
</div>
<p className="text-sm text-[var(--text-secondary)]">
{t('sustainability:sections.waste_subtitle', 'Desglose de residuos por tipo')}
</p>
</div>
<TrendingDown className="w-6 h-6 text-green-600" />
</div>
<div className="space-y-4">
{/* Waste breakdown */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm text-[var(--text-secondary)]">
{t('sustainability:waste.production', 'Residuos de Producción')}
</span>
<span className="text-sm font-medium text-[var(--text-primary)]">
{metrics.waste_metrics.production_waste_kg.toFixed(1)} kg
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-[var(--text-secondary)]">
{t('sustainability:waste.expired', 'Producto Expirado')}
</span>
<span className="text-sm font-medium text-[var(--text-primary)]">
{metrics.waste_metrics.expired_waste_kg.toFixed(1)} kg
</span>
</div>
<div className="flex items-center justify-between pt-3 border-t border-[var(--border-primary)]">
<span className="text-sm font-semibold text-[var(--text-primary)]">
{t('sustainability:waste.total', 'Total')}
</span>
<span className="text-sm font-bold text-[var(--text-primary)]">
{metrics.waste_metrics.total_waste_kg.toFixed(1)} kg
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-[var(--text-secondary)]">
{t('sustainability:waste.percentage', 'Porcentaje de Residuos')}
</span>
<span className="text-sm font-medium text-[var(--text-primary)]">
{metrics.waste_metrics.waste_percentage.toFixed(2)}%
</span>
</div>
</div>
{/* AI Impact */}
<div className="mt-4 p-4 bg-gradient-to-r from-blue-50 to-cyan-50 dark:from-blue-900/20 dark:to-cyan-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<div className="flex items-center gap-2 mb-2">
<Info className="w-4 h-4 text-blue-600" />
<span className="text-xs font-medium text-blue-700 dark:text-blue-400">
{t('sustainability:ai.impact_title', 'Impacto de IA')}
</span>
</div>
<p className="text-sm text-blue-600 dark:text-blue-300">
{t('sustainability:ai.waste_avoided', 'Residuos evitados')}: <strong>{metrics.avoided_waste.waste_avoided_kg.toFixed(1)} kg</strong>
</p>
<p className="text-xs text-blue-600/80 dark:text-blue-300/80 mt-1">
{t('sustainability:ai.batches', 'Lotes asistidos por IA')}: {metrics.avoided_waste.ai_assisted_batches}
</p>
</div>
</div>
</Card>
{/* Environmental Impact Section */}
<Card className="p-6">
<div className="flex items-center justify-between mb-4">
<div>
<div className="flex items-center gap-2">
<h3 className="text-lg font-semibold text-[var(--text-primary)]">
{t('sustainability:sections.environmental_impact', 'Impacto Ambiental')}
</h3>
<Tooltip content={t('sustainability:tooltips.environmental_impact', 'Métricas de huella ambiental y su equivalencia en términos cotidianos')}>
<HelpCircle className="w-4 h-4 text-[var(--text-tertiary)] cursor-help" />
</Tooltip>
</div>
<p className="text-sm text-[var(--text-secondary)]">
{t('sustainability:sections.environmental_subtitle', 'Métricas de huella ambiental')}
</p>
</div>
<Leaf className="w-6 h-6 text-green-600" />
</div>
<div className="grid grid-cols-2 gap-4">
{/* CO2 */}
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg">
<div className="flex items-center gap-2 mb-2">
<Leaf className="w-4 h-4 text-green-600" />
<span className="text-xs font-medium text-[var(--text-secondary)]">CO</span>
</div>
<div className="text-xl font-bold text-[var(--text-primary)]">
{metrics.environmental_impact.co2_emissions.kg.toFixed(0)} kg
</div>
<p className="text-xs text-[var(--text-secondary)] mt-1">
{metrics.environmental_impact.co2_emissions.trees_to_offset.toFixed(1)} {t('sustainability:metrics.trees', 'árboles')}
</p>
</div>
{/* Water */}
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg">
<div className="flex items-center gap-2 mb-2">
<Droplets className="w-4 h-4 text-cyan-600" />
<span className="text-xs font-medium text-[var(--text-secondary)]">{t('sustainability:metrics.water', 'Agua')}</span>
</div>
<div className="text-xl font-bold text-[var(--text-primary)]">
{metrics.environmental_impact.water_footprint.cubic_meters.toFixed(1)} m³
</div>
<p className="text-xs text-[var(--text-secondary)] mt-1">
{metrics.environmental_impact.water_footprint.liters.toFixed(0)} {t('common:liters', 'litros')}
</p>
</div>
{/* Land Use */}
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg">
<div className="flex items-center gap-2 mb-2">
<TreeDeciduous className="w-4 h-4 text-amber-600" />
<span className="text-xs font-medium text-[var(--text-secondary)]">{t('sustainability:metrics.land', 'Tierra')}</span>
</div>
<div className="text-xl font-bold text-[var(--text-primary)]">
{metrics.environmental_impact.land_use.square_meters.toFixed(0)} m²
</div>
<p className="text-xs text-[var(--text-secondary)] mt-1">
{metrics.environmental_impact.land_use.hectares.toFixed(3)} {t('sustainability:metrics.hectares', 'hectáreas')}
</p>
</div>
{/* Human Equivalents */}
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg">
<div className="flex items-center gap-2 mb-2">
<Info className="w-4 h-4 text-blue-600" />
<span className="text-xs font-medium text-[var(--text-secondary)]">{t('sustainability:metrics.equivalents', 'Equivalentes')}</span>
</div>
<div className="text-xs space-y-1 text-[var(--text-secondary)]">
<div>🚗 {metrics.environmental_impact.human_equivalents.car_km_equivalent.toFixed(0)} km</div>
<div>📱 {metrics.environmental_impact.human_equivalents.smartphone_charges.toFixed(0)} cargas</div>
<div>🚿 {metrics.environmental_impact.human_equivalents.showers_equivalent.toFixed(0)} duchas</div>
</div>
</div>
</div>
</Card>
</div>
{/* SDG Compliance & Grant Readiness */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* SDG 12.3 Compliance */}
<Card className="p-6">
<div className="flex items-center justify-between mb-4">
<div>
<div className="flex items-center gap-2">
<h3 className="text-lg font-semibold text-[var(--text-primary)]">
{t('sustainability:sections.sdg_compliance', 'Cumplimiento SDG 12.3')}
</h3>
<Tooltip content={t('sustainability:tooltips.sdg_compliance', 'Progreso hacia el objetivo de desarrollo sostenible de la ONU para reducir residuos alimentarios')}>
<HelpCircle className="w-4 h-4 text-[var(--text-tertiary)] cursor-help" />
</Tooltip>
</div>
<p className="text-sm text-[var(--text-secondary)]">
{t('sustainability:sections.sdg_subtitle', 'Progreso hacia objetivo ONU')}
</p>
</div>
<div className={`px-3 py-1 rounded-full border text-xs font-medium ${getSDGStatusColor(metrics.sdg_compliance.sdg_12_3.status)}`}>
{metrics.sdg_compliance.sdg_12_3.status_label}
</div>
</div>
{/* Progress Bar */}
<div className="mb-4">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-[var(--text-primary)]">
{t('sustainability:sdg.progress_label', 'Progreso al Objetivo')}
</span>
<span className="text-sm font-bold text-[var(--color-primary)]">
{Math.round(metrics.sdg_compliance.sdg_12_3.progress_to_target)}%
</span>
</div>
<div className="w-full bg-[var(--bg-secondary)] rounded-full h-3 overflow-hidden">
<div
className="h-full bg-gradient-to-r from-green-500 to-emerald-600 rounded-full transition-all duration-500"
style={{ width: `${Math.min(metrics.sdg_compliance.sdg_12_3.progress_to_target, 100)}%` }}
/>
</div>
<p className="text-xs text-[var(--text-secondary)] mt-2">
{t('sustainability:sdg.target_note', 'Objetivo: 50% reducción de residuos para 2030')}
</p>
</div>
{/* Metrics */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm text-[var(--text-secondary)]">
{t('sustainability:sdg.baseline', 'Línea Base')}
</span>
<span className="text-sm font-medium text-[var(--text-primary)]">
{metrics.sdg_compliance.sdg_12_3.baseline_waste_percentage.toFixed(2)}%
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-[var(--text-secondary)]">
{t('sustainability:sdg.current', 'Actual')}
</span>
<span className="text-sm font-medium text-[var(--text-primary)]">
{metrics.sdg_compliance.sdg_12_3.current_waste_percentage.toFixed(2)}%
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-[var(--text-secondary)]">
{t('sustainability:sdg.reduction', 'Reducción Lograda')}
</span>
<span className="text-sm font-bold text-green-600">
{Math.abs(metrics.sdg_compliance.sdg_12_3.reduction_achieved).toFixed(1)}%
</span>
</div>
<div className="flex items-center justify-between pt-3 border-t border-[var(--border-primary)]">
<span className="text-sm text-[var(--text-secondary)]">
{t('sustainability:sdg.certification_ready', 'Listo para Certificación')}
</span>
<span className={`text-sm font-medium ${metrics.sdg_compliance.certification_ready ? 'text-green-600' : 'text-amber-600'}`}>
{metrics.sdg_compliance.certification_ready ? t('common:yes', 'Sí') : t('common:no', 'No')}
</span>
</div>
</div>
</Card>
{/* Grant Readiness */}
<Card className="p-6">
<div className="flex items-center justify-between mb-4">
<div>
<div className="flex items-center gap-2">
<h3 className="text-lg font-semibold text-[var(--text-primary)]">
{t('sustainability:sections.grant_readiness', 'Subvenciones Disponibles')}
</h3>
<Tooltip content={t('sustainability:tooltips.grant_readiness', 'Programas de financiación disponibles para empresas españolas según la Ley 1/2025 de prevención de residuos')}>
<HelpCircle className="w-4 h-4 text-[var(--text-tertiary)] cursor-help" />
</Tooltip>
</div>
<p className="text-sm text-[var(--text-secondary)]">
{t('sustainability:sections.grant_subtitle', 'Programas de financiación elegibles')}
</p>
</div>
<Award className="w-6 h-6 text-amber-600" />
</div>
{/* Overall Readiness */}
<div className="mb-4 p-4 bg-gradient-to-r from-amber-50 to-yellow-50 dark:from-amber-900/20 dark:to-yellow-900/20 rounded-lg border border-amber-200 dark:border-amber-800">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-amber-700 dark:text-amber-400">
{t('sustainability:grant.overall_readiness', 'Preparación General')}
</span>
<span className="text-lg font-bold text-amber-600 dark:text-amber-400">
{Math.round(metrics.grant_readiness.overall_readiness_percentage)}%
</span>
</div>
</div>
{/* Grant Programs List */}
<div className="space-y-3">
{Object.entries(metrics.grant_readiness.grant_programs).map(([key, program]) => (
<div
key={key}
className={`p-3 rounded-lg border ${
program.eligible
? 'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800'
: 'bg-gray-50 dark:bg-gray-800/20 border-gray-200 dark:border-gray-700'
}`}
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-2">
<span className={`text-sm font-medium ${
program.eligible ? 'text-green-700 dark:text-green-400' : 'text-gray-600 dark:text-gray-400'
}`}>
{key.replace(/_/g, ' ')}
</span>
{program.eligible && (
<span className="text-xs px-2 py-0.5 bg-green-500/20 text-green-700 dark:text-green-400 rounded-full">
{t('sustainability:grant.eligible', 'Elegible')}
</span>
)}
</div>
{program.funding_eur && program.funding_eur > 0 && (
<p className="text-xs text-[var(--text-secondary)] mt-1">
{t('sustainability:grant.funding', 'Financiación')}: {program.funding_eur.toLocaleString()}
</p>
)}
</div>
<div className={`text-xs px-2 py-1 rounded ${
program.confidence === 'high'
? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
: program.confidence === 'medium'
? 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-400'
}`}>
{program.confidence}
</div>
</div>
</div>
))}
</div>
{/* Spain Compliance */}
<div className="mt-4 pt-4 border-t border-[var(--border-primary)]">
<p className="text-xs font-medium text-[var(--text-secondary)] mb-2">
{t('sustainability:grant.spain_compliance', 'Cumplimiento España')}
</p>
<div className="flex items-center gap-3 text-xs">
<div className="flex items-center gap-1">
{metrics.grant_readiness.spain_compliance?.law_1_2025 ? '✅' : '❌'}
<span className="text-[var(--text-secondary)]">Ley 1/2025</span>
</div>
<div className="flex items-center gap-1">
{metrics.grant_readiness.spain_compliance?.circular_economy_strategy ? '✅' : '❌'}
<span className="text-[var(--text-secondary)]">Economía Circular 2030</span>
</div>
</div>
</div>
</Card>
</div>
{/* Financial Impact */}
<Card className="p-6">
<div className="flex items-center justify-between mb-4">
<div>
<div className="flex items-center gap-2">
<h3 className="text-lg font-semibold text-[var(--text-primary)]">
{t('sustainability:sections.financial_impact', 'Impacto Financiero')}
</h3>
<Tooltip content={t('sustainability:tooltips.financial_impact', 'Costes asociados a residuos y ahorros potenciales mediante la reducción de desperdicio')}>
<HelpCircle className="w-4 h-4 text-[var(--text-tertiary)] cursor-help" />
</Tooltip>
</div>
<p className="text-sm text-[var(--text-secondary)]">
{t('sustainability:sections.financial_subtitle', 'Costes y ahorros de sostenibilidad')}
</p>
</div>
<Euro className="w-6 h-6 text-green-600" />
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg">
<p className="text-xs font-medium text-[var(--text-secondary)] mb-2">
{t('sustainability:financial.waste_cost', 'Coste de Residuos')}
</p>
<p className="text-2xl font-bold text-red-600">
{metrics.financial_impact.waste_cost_eur.toFixed(2)}
</p>
<p className="text-xs text-[var(--text-secondary)] mt-1">
{metrics.financial_impact.cost_per_kg.toFixed(2)}/kg
</p>
</div>
<div className="p-4 bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20 rounded-lg border border-green-200 dark:border-green-800">
<p className="text-xs font-medium text-green-700 dark:text-green-400 mb-2">
{t('sustainability:financial.monthly_savings', 'Ahorro Mensual')}
</p>
<p className="text-2xl font-bold text-green-600 dark:text-green-400">
{metrics.financial_impact.potential_monthly_savings.toFixed(2)}
</p>
<p className="text-xs text-green-600/80 dark:text-green-400/80 mt-1">
{t('sustainability:financial.from_reduction', 'Por reducción')}
</p>
</div>
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg">
<p className="text-xs font-medium text-[var(--text-secondary)] mb-2">
{t('sustainability:financial.annual_projection', 'Proyección Anual')}
</p>
<p className="text-2xl font-bold text-[var(--text-primary)]">
{metrics.financial_impact.annual_projection.toFixed(2)}
</p>
<p className="text-xs text-[var(--text-secondary)] mt-1">
{t('sustainability:financial.estimated', 'Estimado')}
</p>
</div>
<div className="p-4 bg-gradient-to-r from-blue-50 to-cyan-50 dark:from-blue-900/20 dark:to-cyan-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<p className="text-xs font-medium text-blue-700 dark:text-blue-400 mb-2">
{t('sustainability:financial.roi', 'ROI de IA')}
</p>
<p className="text-2xl font-bold text-blue-600 dark:text-blue-400">
{(metrics.avoided_waste.waste_avoided_kg * metrics.financial_impact.cost_per_kg).toFixed(2)}
</p>
<p className="text-xs text-blue-600/80 dark:text-blue-400/80 mt-1">
{t('sustainability:financial.ai_savings', 'Ahorrado por IA')}
</p>
</div>
</div>
</Card>
</div>
);
};
export default SustainabilityPage;