Fix UI issues 2
This commit is contained in:
@@ -162,7 +162,8 @@ const EnvironmentalConstants = {
|
|||||||
LAND_USE_PER_KG: 3.4, // m² per kg
|
LAND_USE_PER_KG: 3.4, // m² per kg
|
||||||
TREES_PER_TON_CO2: 50,
|
TREES_PER_TON_CO2: 50,
|
||||||
SDG_TARGET_REDUCTION: 0.50, // 50% reduction target
|
SDG_TARGET_REDUCTION: 0.50, // 50% reduction target
|
||||||
EU_BAKERY_BASELINE_WASTE: 0.25 // 25% baseline
|
EU_BAKERY_BASELINE_WASTE: 0.25, // 25% baseline
|
||||||
|
MINIMUM_PRODUCTION_KG: 50 // Minimum production to show meaningful metrics
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -320,6 +321,78 @@ function assessGrantReadiness(sdgCompliance: SDGCompliance): GrantReadiness {
|
|||||||
|
|
||||||
// ===== MAIN AGGREGATION FUNCTION =====
|
// ===== MAIN AGGREGATION FUNCTION =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default metrics for insufficient data state
|
||||||
|
*/
|
||||||
|
function getInsufficientDataMetrics(
|
||||||
|
totalProductionKg: number,
|
||||||
|
startDate?: string,
|
||||||
|
endDate?: string
|
||||||
|
): SustainabilityMetrics {
|
||||||
|
return {
|
||||||
|
period: {
|
||||||
|
start_date: startDate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
|
||||||
|
end_date: endDate || new Date().toISOString(),
|
||||||
|
days: 30
|
||||||
|
},
|
||||||
|
waste_metrics: {
|
||||||
|
total_waste_kg: 0,
|
||||||
|
production_waste_kg: 0,
|
||||||
|
expired_waste_kg: 0,
|
||||||
|
waste_percentage: 0,
|
||||||
|
waste_by_reason: {}
|
||||||
|
},
|
||||||
|
environmental_impact: {
|
||||||
|
co2_emissions: { kg: 0, tons: 0, trees_to_offset: 0 },
|
||||||
|
water_footprint: { liters: 0, cubic_meters: 0 },
|
||||||
|
land_use: { square_meters: 0, hectares: 0 },
|
||||||
|
human_equivalents: { car_km_equivalent: 0, smartphone_charges: 0, showers_equivalent: 0, trees_planted: 0 }
|
||||||
|
},
|
||||||
|
sdg_compliance: {
|
||||||
|
sdg_12_3: {
|
||||||
|
baseline_waste_percentage: 0,
|
||||||
|
current_waste_percentage: 0,
|
||||||
|
reduction_achieved: 0,
|
||||||
|
target_reduction: 50,
|
||||||
|
progress_to_target: 0,
|
||||||
|
status: 'baseline',
|
||||||
|
status_label: 'Collecting Baseline Data',
|
||||||
|
target_waste_percentage: 0
|
||||||
|
},
|
||||||
|
baseline_period: 'not_available',
|
||||||
|
certification_ready: false,
|
||||||
|
improvement_areas: ['start_production_tracking']
|
||||||
|
},
|
||||||
|
avoided_waste: {
|
||||||
|
waste_avoided_kg: 0,
|
||||||
|
ai_assisted_batches: 0,
|
||||||
|
environmental_impact_avoided: { co2_kg: 0, water_liters: 0 },
|
||||||
|
methodology: 'insufficient_data'
|
||||||
|
},
|
||||||
|
financial_impact: {
|
||||||
|
waste_cost_eur: 0,
|
||||||
|
cost_per_kg: 3.50,
|
||||||
|
potential_monthly_savings: 0,
|
||||||
|
annual_projection: 0
|
||||||
|
},
|
||||||
|
grant_readiness: {
|
||||||
|
overall_readiness_percentage: 0,
|
||||||
|
grant_programs: {
|
||||||
|
life_circular_economy: { eligible: false, confidence: 'low', requirements_met: false, funding_eur: 73_000_000 },
|
||||||
|
horizon_europe_cluster_6: { eligible: false, confidence: 'low', requirements_met: false, funding_eur: 880_000_000 },
|
||||||
|
fedima_sustainability_grant: { eligible: false, confidence: 'low', requirements_met: false, funding_eur: 20_000 },
|
||||||
|
eit_food_retail: { eligible: false, confidence: 'low', requirements_met: false, funding_eur: 45_000 },
|
||||||
|
un_sdg_certified: { eligible: false, confidence: 'low', requirements_met: false, funding_eur: 0 }
|
||||||
|
},
|
||||||
|
recommended_applications: [],
|
||||||
|
spain_compliance: { law_1_2025: false, circular_economy_strategy: false }
|
||||||
|
},
|
||||||
|
data_sufficient: false,
|
||||||
|
minimum_production_required_kg: EnvironmentalConstants.MINIMUM_PRODUCTION_KG,
|
||||||
|
current_production_kg: totalProductionKg
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get comprehensive sustainability metrics by aggregating production and inventory data
|
* Get comprehensive sustainability metrics by aggregating production and inventory data
|
||||||
*/
|
*/
|
||||||
@@ -337,11 +410,22 @@ export async function getSustainabilityMetrics(
|
|||||||
getProductionAIImpact(tenantId, startDate, endDate)
|
getProductionAIImpact(tenantId, startDate, endDate)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Calculate total production
|
||||||
|
const totalProductionKg = productionData.total_planned || 0;
|
||||||
|
|
||||||
|
// Check if we have sufficient data for meaningful metrics
|
||||||
|
// Minimum: 50kg production to avoid false metrics on empty accounts
|
||||||
|
const hasDataSufficient = totalProductionKg >= EnvironmentalConstants.MINIMUM_PRODUCTION_KG;
|
||||||
|
|
||||||
|
// If insufficient data, return a "collecting data" state
|
||||||
|
if (!hasDataSufficient) {
|
||||||
|
return getInsufficientDataMetrics(totalProductionKg, startDate, endDate);
|
||||||
|
}
|
||||||
|
|
||||||
// Aggregate waste metrics
|
// Aggregate waste metrics
|
||||||
const productionWaste = (productionData.total_production_waste || 0) + (productionData.total_defects || 0);
|
const productionWaste = (productionData.total_production_waste || 0) + (productionData.total_defects || 0);
|
||||||
const totalWasteKg = productionWaste + (inventoryData.inventory_waste_kg || 0);
|
const totalWasteKg = productionWaste + (inventoryData.inventory_waste_kg || 0);
|
||||||
|
|
||||||
const totalProductionKg = productionData.total_planned || 0;
|
|
||||||
const wastePercentage = totalProductionKg > 0
|
const wastePercentage = totalProductionKg > 0
|
||||||
? (totalWasteKg / totalProductionKg) * 100
|
? (totalWasteKg / totalProductionKg) * 100
|
||||||
: 0;
|
: 0;
|
||||||
@@ -403,7 +487,10 @@ export async function getSustainabilityMetrics(
|
|||||||
sdg_compliance: sdgCompliance,
|
sdg_compliance: sdgCompliance,
|
||||||
avoided_waste: avoidedWaste,
|
avoided_waste: avoidedWaste,
|
||||||
financial_impact: financialImpact,
|
financial_impact: financialImpact,
|
||||||
grant_readiness: grantReadiness
|
grant_readiness: grantReadiness,
|
||||||
|
data_sufficient: true,
|
||||||
|
minimum_production_required_kg: EnvironmentalConstants.MINIMUM_PRODUCTION_KG,
|
||||||
|
current_production_kg: totalProductionKg
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -114,6 +114,10 @@ export interface SustainabilityMetrics {
|
|||||||
avoided_waste: AvoidedWaste;
|
avoided_waste: AvoidedWaste;
|
||||||
financial_impact: FinancialImpact;
|
financial_impact: FinancialImpact;
|
||||||
grant_readiness: GrantReadiness;
|
grant_readiness: GrantReadiness;
|
||||||
|
// Data sufficiency flags
|
||||||
|
data_sufficient: boolean;
|
||||||
|
minimum_production_required_kg?: number;
|
||||||
|
current_production_kg?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SustainabilityWidgetData {
|
export interface SustainabilityWidgetData {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
Users,
|
Users,
|
||||||
UserPlus,
|
UserPlus,
|
||||||
Euro as EuroIcon,
|
Euro as EuroIcon,
|
||||||
Sparkles,
|
|
||||||
FileText,
|
FileText,
|
||||||
Factory,
|
Factory,
|
||||||
Search,
|
Search,
|
||||||
@@ -159,21 +158,6 @@ export const ItemTypeSelector: React.FC<ItemTypeSelectorProps> = ({ onSelect })
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Header */}
|
|
||||||
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
|
||||||
<div className="flex items-center justify-center mb-3">
|
|
||||||
<div className="p-3 bg-gradient-to-br from-[var(--color-primary)]/10 to-[var(--color-primary)]/5 rounded-full">
|
|
||||||
<Sparkles className="w-8 h-8 text-[var(--color-primary)]" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h2 className="text-2xl font-bold text-[var(--text-primary)] mb-2">
|
|
||||||
{t('itemTypeSelector.title')}
|
|
||||||
</h2>
|
|
||||||
<p className="text-[var(--text-secondary)] max-w-md mx-auto">
|
|
||||||
{t('itemTypeSelector.description')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Search and Filters */}
|
{/* Search and Filters */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* Search Bar */}
|
{/* Search Bar */}
|
||||||
@@ -256,7 +240,7 @@ export const ItemTypeSelector: React.FC<ItemTypeSelectorProps> = ({ onSelect })
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-start gap-4">
|
||||||
{/* Icon */}
|
{/* Icon */}
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
@@ -272,8 +256,8 @@ export const ItemTypeSelector: React.FC<ItemTypeSelectorProps> = ({ onSelect })
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Text */}
|
{/* Text */}
|
||||||
<div className="flex-1 text-left">
|
<div className="flex-1 text-left pr-16">
|
||||||
<h3 className="text-base md:text-lg font-semibold text-[var(--text-primary)] mb-0.5 group-hover:text-[var(--color-primary)] transition-colors">
|
<h3 className="text-base md:text-lg font-semibold text-[var(--text-primary)] group-hover:text-[var(--color-primary)] transition-colors">
|
||||||
{itemType.title}
|
{itemType.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-[var(--text-secondary)] leading-snug mt-1">
|
<p className="text-sm text-[var(--text-secondary)] leading-snug mt-1">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useCallback, useEffect } from 'react';
|
import React, { useState, useCallback, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { X, ChevronLeft, ChevronRight, AlertCircle, CheckCircle } from 'lucide-react';
|
import { X, ChevronLeft, ChevronRight, AlertCircle, CheckCircle } from 'lucide-react';
|
||||||
|
|
||||||
export interface WizardStep {
|
export interface WizardStep {
|
||||||
@@ -48,12 +49,26 @@ export const WizardModal: React.FC<WizardModalProps> = ({
|
|||||||
dataRef,
|
dataRef,
|
||||||
onDataChange
|
onDataChange
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation('wizards');
|
||||||
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
||||||
const [isValidating, setIsValidating] = useState(false);
|
const [isValidating, setIsValidating] = useState(false);
|
||||||
const [validationError, setValidationError] = useState<string | null>(null);
|
const [validationError, setValidationError] = useState<string | null>(null);
|
||||||
const [validationSuccess, setValidationSuccess] = useState(false);
|
const [validationSuccess, setValidationSuccess] = useState(false);
|
||||||
|
|
||||||
const currentStep = steps[currentStepIndex];
|
const currentStep = steps[currentStepIndex];
|
||||||
|
|
||||||
|
// Helper to translate step title - if it looks like a translation key (contains dots), translate it
|
||||||
|
const translateStepTitle = (stepTitle: string): string => {
|
||||||
|
if (stepTitle.includes('.')) {
|
||||||
|
const translated = t(stepTitle);
|
||||||
|
// If translation returns the key itself, return just the last part as fallback
|
||||||
|
if (translated === stepTitle) {
|
||||||
|
return stepTitle.split('.').pop() || stepTitle;
|
||||||
|
}
|
||||||
|
return translated;
|
||||||
|
}
|
||||||
|
return stepTitle;
|
||||||
|
};
|
||||||
const isFirstStep = currentStepIndex === 0;
|
const isFirstStep = currentStepIndex === 0;
|
||||||
const isLastStep = currentStepIndex === steps.length - 1;
|
const isLastStep = currentStepIndex === steps.length - 1;
|
||||||
|
|
||||||
@@ -178,11 +193,11 @@ export const WizardModal: React.FC<WizardModalProps> = ({
|
|||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 pointer-events-none">
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 pointer-events-none">
|
||||||
<div
|
<div
|
||||||
className={`bg-[var(--bg-primary)] rounded-xl shadow-2xl ${sizeClasses[size]} w-full max-h-[90vh] overflow-hidden pointer-events-auto animate-slideUp`}
|
className={`bg-[var(--bg-primary)] rounded-xl shadow-2xl ${sizeClasses[size]} w-full max-h-[90vh] flex flex-col overflow-hidden pointer-events-auto animate-slideUp`}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="sticky top-0 z-10 bg-[var(--bg-primary)] border-b border-[var(--border-secondary)] shadow-sm">
|
<div className="flex-shrink-0 z-10 bg-[var(--bg-primary)] border-b border-[var(--border-secondary)] shadow-sm">
|
||||||
{/* Title Bar */}
|
{/* Title Bar */}
|
||||||
<div className="flex items-center justify-between p-4 sm:p-6 pb-3 sm:pb-4">
|
<div className="flex items-center justify-between p-4 sm:p-6 pb-3 sm:pb-4">
|
||||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||||
@@ -232,7 +247,7 @@ export const WizardModal: React.FC<WizardModalProps> = ({
|
|||||||
? 'bg-[var(--color-primary)] shadow-md'
|
? 'bg-[var(--color-primary)] shadow-md'
|
||||||
: 'bg-[var(--bg-tertiary)] cursor-not-allowed'
|
: 'bg-[var(--bg-tertiary)] cursor-not-allowed'
|
||||||
}`}
|
}`}
|
||||||
aria-label={`${step.title} - ${isCompleted ? 'Completado' : isCurrent ? 'En progreso' : 'Pendiente'}`}
|
aria-label={`${translateStepTitle(step.title)} - ${isCompleted ? 'Completado' : isCurrent ? 'En progreso' : 'Pendiente'}`}
|
||||||
>
|
>
|
||||||
{isCurrent && (
|
{isCurrent && (
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent animate-shimmer" />
|
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent animate-shimmer" />
|
||||||
@@ -241,7 +256,7 @@ export const WizardModal: React.FC<WizardModalProps> = ({
|
|||||||
|
|
||||||
{/* Tooltip on hover */}
|
{/* Tooltip on hover */}
|
||||||
<div className="absolute left-1/2 -translate-x-1/2 bottom-full mb-2 px-2 py-1 bg-[var(--bg-primary)] border border-[var(--border-secondary)] rounded shadow-lg text-xs whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-20">
|
<div className="absolute left-1/2 -translate-x-1/2 bottom-full mb-2 px-2 py-1 bg-[var(--bg-primary)] border border-[var(--border-secondary)] rounded shadow-lg text-xs whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-20">
|
||||||
{step.title}
|
{translateStepTitle(step.title)}
|
||||||
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-px border-4 border-transparent border-t-[var(--border-secondary)]" />
|
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-px border-4 border-transparent border-t-[var(--border-secondary)]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -250,7 +265,7 @@ export const WizardModal: React.FC<WizardModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between text-xs sm:text-sm">
|
<div className="flex items-center justify-between text-xs sm:text-sm">
|
||||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||||
<span className="font-semibold text-[var(--text-primary)] truncate">{currentStep.title}</span>
|
<span className="font-semibold text-[var(--text-primary)] truncate">{translateStepTitle(currentStep.title)}</span>
|
||||||
{currentStep.isOptional && (
|
{currentStep.isOptional && (
|
||||||
<span className="px-2 py-0.5 text-xs bg-[var(--bg-secondary)] text-[var(--text-tertiary)] rounded-full flex-shrink-0">
|
<span className="px-2 py-0.5 text-xs bg-[var(--bg-secondary)] text-[var(--text-tertiary)] rounded-full flex-shrink-0">
|
||||||
Opcional
|
Opcional
|
||||||
@@ -265,7 +280,7 @@ export const WizardModal: React.FC<WizardModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Step Content */}
|
{/* Step Content */}
|
||||||
<div className="p-4 sm:p-6 overflow-y-auto" style={{ maxHeight: 'calc(90vh - 220px)' }}>
|
<div className="flex-1 min-h-0 p-4 sm:p-6 overflow-y-auto">
|
||||||
{/* Validation Messages */}
|
{/* Validation Messages */}
|
||||||
{validationError && (
|
{validationError && (
|
||||||
<div className="mb-4 p-3 sm:p-4 bg-red-50 border border-red-200 rounded-lg flex items-start gap-3 animate-slideDown">
|
<div className="mb-4 p-3 sm:p-4 bg-red-50 border border-red-200 rounded-lg flex items-start gap-3 animate-slideDown">
|
||||||
@@ -304,7 +319,7 @@ export const WizardModal: React.FC<WizardModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer Navigation */}
|
{/* Footer Navigation */}
|
||||||
<div className="sticky bottom-0 border-t border-[var(--border-secondary)] bg-[var(--bg-secondary)]/80 backdrop-blur-md px-4 sm:px-6 py-3 sm:py-4 shadow-lg">
|
<div className="flex-shrink-0 border-t border-[var(--border-secondary)] bg-[var(--bg-secondary)]/80 backdrop-blur-md px-4 sm:px-6 py-3 sm:py-4 shadow-lg">
|
||||||
{/* Keyboard Shortcuts Hint */}
|
{/* Keyboard Shortcuts Hint */}
|
||||||
<div className="hidden md:flex items-center justify-center gap-4 text-xs text-[var(--text-tertiary)] mb-2 pb-2 border-b border-[var(--border-secondary)]/50">
|
<div className="hidden md:flex items-center justify-center gap-4 text-xs text-[var(--text-tertiary)] mb-2 pb-2 border-b border-[var(--border-secondary)]/50">
|
||||||
<span className="flex items-center gap-1.5">
|
<span className="flex items-center gap-1.5">
|
||||||
|
|||||||
@@ -337,6 +337,97 @@
|
|||||||
},
|
},
|
||||||
"customerOrder": {
|
"customerOrder": {
|
||||||
"title": "Agregar Pedido",
|
"title": "Agregar Pedido",
|
||||||
|
"customerTypes": {
|
||||||
|
"retail": "Minorista",
|
||||||
|
"wholesale": "Mayorista",
|
||||||
|
"event": "Evento",
|
||||||
|
"restaurant": "Restaurante",
|
||||||
|
"hotel": "Hotel",
|
||||||
|
"enterprise": "Empresa",
|
||||||
|
"individual": "Individual",
|
||||||
|
"business": "Negocio",
|
||||||
|
"central_bakery": "Panadería Central"
|
||||||
|
},
|
||||||
|
"paymentTerms": {
|
||||||
|
"immediate": "Inmediato",
|
||||||
|
"net_30": "Neto 30",
|
||||||
|
"net_60": "Neto 60"
|
||||||
|
},
|
||||||
|
"sections": {
|
||||||
|
"basicInfo": "Información Básica del Pedido",
|
||||||
|
"deliveryInfo": "Detalles de Entrega",
|
||||||
|
"paymentInfo": "Detalles de Pago",
|
||||||
|
"orderSummary": "Resumen del Pedido",
|
||||||
|
"advancedOptions": "Opciones Avanzadas",
|
||||||
|
"advancedOptionsDescription": "Campos opcionales para gestión completa de pedidos",
|
||||||
|
"pricingDetails": "Detalles de Precios",
|
||||||
|
"productionScheduling": "Producción y Programación",
|
||||||
|
"fulfillmentTracking": "Cumplimiento y Seguimiento",
|
||||||
|
"sourceChannel": "Origen y Canal",
|
||||||
|
"communicationNotes": "Comunicación y Notas",
|
||||||
|
"notifications": "Notificaciones",
|
||||||
|
"qualityRequirements": "Calidad y Requisitos",
|
||||||
|
"additionalOptions": "Opciones Adicionales"
|
||||||
|
},
|
||||||
|
"orderTypes": {
|
||||||
|
"standard": "Estándar",
|
||||||
|
"custom": "Personalizado",
|
||||||
|
"bulk": "A Granel",
|
||||||
|
"urgent": "Urgente"
|
||||||
|
},
|
||||||
|
"priorities": {
|
||||||
|
"low": "Baja",
|
||||||
|
"normal": "Normal",
|
||||||
|
"high": "Alta",
|
||||||
|
"urgent": "Urgente"
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"pending": "Pendiente",
|
||||||
|
"confirmed": "Confirmado",
|
||||||
|
"in_production": "En Producción",
|
||||||
|
"ready": "Listo",
|
||||||
|
"delivered": "Entregado"
|
||||||
|
},
|
||||||
|
"deliveryMethods": {
|
||||||
|
"pickup": "Recogida",
|
||||||
|
"pickupDesc": "Recogida del cliente",
|
||||||
|
"delivery": "Entrega",
|
||||||
|
"deliveryDesc": "Entrega a domicilio",
|
||||||
|
"shipping": "Envío",
|
||||||
|
"shippingDesc": "Servicio de mensajería"
|
||||||
|
},
|
||||||
|
"paymentMethods": {
|
||||||
|
"cash": "Efectivo",
|
||||||
|
"card": "Tarjeta",
|
||||||
|
"bank_transfer": "Transferencia Bancaria",
|
||||||
|
"invoice": "Factura",
|
||||||
|
"account": "Cuenta"
|
||||||
|
},
|
||||||
|
"paymentStatuses": {
|
||||||
|
"pending": "Pendiente",
|
||||||
|
"partial": "Parcial",
|
||||||
|
"paid": "Pagado",
|
||||||
|
"overdue": "Vencido"
|
||||||
|
},
|
||||||
|
"orderSources": {
|
||||||
|
"manual": "Manual",
|
||||||
|
"phone": "Teléfono",
|
||||||
|
"email": "Correo Electrónico",
|
||||||
|
"website": "Sitio Web",
|
||||||
|
"app": "Aplicación Móvil"
|
||||||
|
},
|
||||||
|
"salesChannels": {
|
||||||
|
"direct": "Directo",
|
||||||
|
"wholesale": "Mayorista",
|
||||||
|
"retail": "Minorista",
|
||||||
|
"online": "En Línea"
|
||||||
|
},
|
||||||
|
"qualityCheckStatuses": {
|
||||||
|
"not_started": "No Iniciado",
|
||||||
|
"pending": "Pendiente",
|
||||||
|
"passed": "Aprobado",
|
||||||
|
"failed": "Reprobado"
|
||||||
|
},
|
||||||
"steps": {
|
"steps": {
|
||||||
"customerSelection": "Selección de Cliente",
|
"customerSelection": "Selección de Cliente",
|
||||||
"orderItems": "Artículos del Pedido",
|
"orderItems": "Artículos del Pedido",
|
||||||
@@ -348,20 +439,23 @@
|
|||||||
"searchPlaceholder": "Buscar clientes...",
|
"searchPlaceholder": "Buscar clientes...",
|
||||||
"createNew": "Crear nuevo cliente",
|
"createNew": "Crear nuevo cliente",
|
||||||
"backToList": "← Volver a la lista de clientes",
|
"backToList": "← Volver a la lista de clientes",
|
||||||
"fields": {
|
"customerName": "Nombre del Cliente",
|
||||||
"customerName": "Nombre del Cliente",
|
"customerNamePlaceholder": "Ej: Restaurante El Molino",
|
||||||
"customerNamePlaceholder": "Ej: Restaurante El Molino",
|
"customerType": "Tipo de Cliente",
|
||||||
"customerType": "Tipo de Cliente",
|
"phone": "Teléfono",
|
||||||
"phone": "Teléfono",
|
"phonePlaceholder": "+34 123 456 789",
|
||||||
"phonePlaceholder": "+34 123 456 789",
|
"email": "Correo Electrónico",
|
||||||
"email": "Correo Electrónico",
|
"emailPlaceholder": "contacto@restaurante.com",
|
||||||
"emailPlaceholder": "contacto@restaurante.com"
|
|
||||||
},
|
|
||||||
"customerTypes": {
|
"customerTypes": {
|
||||||
"retail": "Minorista",
|
"retail": "Minorista",
|
||||||
"wholesale": "Mayorista",
|
"wholesale": "Mayorista",
|
||||||
"event": "Evento",
|
"event": "Evento",
|
||||||
"restaurant": "Restaurante"
|
"restaurant": "Restaurante",
|
||||||
|
"hotel": "Hotel",
|
||||||
|
"enterprise": "Empresa",
|
||||||
|
"individual": "Individual",
|
||||||
|
"business": "Negocio",
|
||||||
|
"central_bakery": "Panadería Central"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"orderItems": {
|
"orderItems": {
|
||||||
@@ -371,7 +465,7 @@
|
|||||||
"removeItem": "Eliminar artículo",
|
"removeItem": "Eliminar artículo",
|
||||||
"customer": "Cliente",
|
"customer": "Cliente",
|
||||||
"orderProducts": "Productos del Pedido",
|
"orderProducts": "Productos del Pedido",
|
||||||
"productNumber": "Producto #{{number}}",
|
"productNumber": "Producto #{number}",
|
||||||
"product": "Producto",
|
"product": "Producto",
|
||||||
"productPlaceholder": "Seleccionar producto...",
|
"productPlaceholder": "Seleccionar producto...",
|
||||||
"selectProduct": "Seleccionar producto...",
|
"selectProduct": "Seleccionar producto...",
|
||||||
@@ -396,6 +490,74 @@
|
|||||||
"deliveryPayment": {
|
"deliveryPayment": {
|
||||||
"title": "Detalles de Entrega y Pago",
|
"title": "Detalles de Entrega y Pago",
|
||||||
"subtitle": "Configurar entrega, pago y detalles del pedido",
|
"subtitle": "Configurar entrega, pago y detalles del pedido",
|
||||||
|
"requestedDeliveryDate": "Fecha de Entrega Solicitada",
|
||||||
|
"orderNumberLabel": "Número de Pedido",
|
||||||
|
"orderNumberTooltip": "Generado automáticamente por el backend al crear el pedido (formato: ORD-AAAAMMDD-####)",
|
||||||
|
"autoGeneratedLabel": "Auto-generado",
|
||||||
|
"autoGeneratedPlaceholder": "Se generará automáticamente",
|
||||||
|
"status": "Estado",
|
||||||
|
"orderType": "Tipo de Pedido",
|
||||||
|
"priority": "Prioridad",
|
||||||
|
"products": "Productos",
|
||||||
|
"deliveryMethod": "Método de Entrega",
|
||||||
|
"deliveryAddressPlaceholder": "Calle, número, piso, código postal, ciudad...",
|
||||||
|
"deliveryContactName": "Nombre de Contacto para Entrega",
|
||||||
|
"deliveryContactNamePlaceholder": "Persona de contacto",
|
||||||
|
"deliveryContactPhone": "Teléfono de Contacto para Entrega",
|
||||||
|
"phoneNumberPlaceholder": "+34 123 456 789",
|
||||||
|
"paymentTerms": "Términos de Pago",
|
||||||
|
"paymentStatus": "Estado de Pago",
|
||||||
|
"paymentDueDate": "Fecha de Vencimiento del Pago",
|
||||||
|
"discount": "Descuento (%)",
|
||||||
|
"deliveryFee": "Tarifa de Entrega (€)",
|
||||||
|
"productionStartDate": "Fecha de Inicio de Producción",
|
||||||
|
"productionDueDate": "Fecha de Vencimiento de Producción",
|
||||||
|
"productionBatchNumber": "Número de Lote de Producción",
|
||||||
|
"deliveryTimeWindow": "Ventana de Tiempo de Entrega",
|
||||||
|
"deliveryTimeWindowPlaceholder": "Ej: 9:00 AM - 11:00 AM",
|
||||||
|
"productionNotes": "Notas de Producción",
|
||||||
|
"productionNotesPlaceholder": "Requisitos especiales de producción o notas",
|
||||||
|
"shippingTrackingNumber": "Número de Seguimiento de Envío",
|
||||||
|
"shippingTrackingNumberPlaceholder": "Número de seguimiento",
|
||||||
|
"shippingCarrier": "Transportista de Envío",
|
||||||
|
"shippingCarrierPlaceholder": "Ej: DHL, UPS, FedEx",
|
||||||
|
"pickupLocation": "Ubicación de Recogida",
|
||||||
|
"pickupLocationPlaceholder": "Ubicación de tienda para recogida",
|
||||||
|
"actualDeliveryDate": "Fecha Real de Entrega",
|
||||||
|
"orderSource": "Origen del Pedido",
|
||||||
|
"salesChannel": "Canal de Ventas",
|
||||||
|
"salesRepId": "ID del Representante de Ventas",
|
||||||
|
"salesRepIdPlaceholder": "ID o nombre del representante de ventas",
|
||||||
|
"customerPurchaseOrder": "Orden de Compra del Cliente #",
|
||||||
|
"customerPurchaseOrderPlaceholder": "Número de OC del cliente",
|
||||||
|
"deliveryInstructions": "Instrucciones de Entrega",
|
||||||
|
"deliveryInstructionsPlaceholder": "Instrucciones especiales de entrega",
|
||||||
|
"specialInstructions": "Instrucciones Especiales",
|
||||||
|
"specialInstructionsPlaceholder": "Cualquier requisito o instrucción especial",
|
||||||
|
"internalNotes": "Notas Internas",
|
||||||
|
"internalNotesPlaceholder": "Notas internas (no visibles para el cliente)",
|
||||||
|
"customerNotes": "Notas del Cliente",
|
||||||
|
"customerNotesPlaceholder": "Notas de/para el cliente",
|
||||||
|
"notifyOnStatusChange": "Notificar en Cambio de Estado",
|
||||||
|
"notifyOnDelivery": "Notificar en Entrega",
|
||||||
|
"notificationEmail": "Correo de Notificación",
|
||||||
|
"notificationEmailPlaceholder": "cliente@correo.com",
|
||||||
|
"notificationPhone": "Teléfono de Notificación",
|
||||||
|
"qualityCheckRequired": "Control de Calidad Requerido",
|
||||||
|
"qualityCheckStatus": "Estado del Control de Calidad",
|
||||||
|
"packagingInstructions": "Instrucciones de Empaquetado",
|
||||||
|
"packagingInstructionsPlaceholder": "Requisitos especiales de empaquetado",
|
||||||
|
"labelingRequirements": "Requisitos de Etiquetado",
|
||||||
|
"labelingRequirementsPlaceholder": "Requisitos de etiqueta personalizados",
|
||||||
|
"recurringOrder": "Pedido Recurrente",
|
||||||
|
"recurringSchedule": "Programa Recurrente",
|
||||||
|
"recurringSchedulePlaceholder": "Ej: Semanalmente los lunes, Cada 2 semanas",
|
||||||
|
"tags": "Etiquetas",
|
||||||
|
"tagsPlaceholder": "urgente, vip, mayorista",
|
||||||
|
"tagsTooltip": "Etiquetas separadas por comas para búsqueda y filtrado más fácil",
|
||||||
|
"metadata": "Metadatos (JSON)",
|
||||||
|
"metadataPlaceholder": "{\"campo_personalizado\": \"valor\"}",
|
||||||
|
"metadataTooltip": "Datos personalizados adicionales en formato JSON",
|
||||||
"fields": {
|
"fields": {
|
||||||
"requestedDeliveryDate": "Fecha de Entrega Solicitada",
|
"requestedDeliveryDate": "Fecha de Entrega Solicitada",
|
||||||
"orderNumber": "Número de Pedido",
|
"orderNumber": "Número de Pedido",
|
||||||
@@ -454,11 +616,6 @@
|
|||||||
"invoice": "Factura",
|
"invoice": "Factura",
|
||||||
"account": "Cuenta"
|
"account": "Cuenta"
|
||||||
},
|
},
|
||||||
"paymentTerms": {
|
|
||||||
"immediate": "Inmediato",
|
|
||||||
"net_30": "Neto 30",
|
|
||||||
"net_60": "Neto 60"
|
|
||||||
},
|
|
||||||
"paymentStatuses": {
|
"paymentStatuses": {
|
||||||
"pending": "Pendiente",
|
"pending": "Pendiente",
|
||||||
"partial": "Parcial",
|
"partial": "Parcial",
|
||||||
@@ -483,103 +640,103 @@
|
|||||||
"pending": "Pendiente",
|
"pending": "Pendiente",
|
||||||
"passed": "Aprobado",
|
"passed": "Aprobado",
|
||||||
"failed": "Reprobado"
|
"failed": "Reprobado"
|
||||||
},
|
|
||||||
"messages": {
|
|
||||||
"loadingCustomers": "Cargando clientes...",
|
|
||||||
"loadingProducts": "Cargando productos...",
|
|
||||||
"errorLoadingCustomers": "Error al cargar clientes",
|
|
||||||
"errorLoadingProducts": "Error al cargar productos",
|
|
||||||
"noCustomersFound": "No se encontraron clientes",
|
|
||||||
"tryDifferentSearch": "Intenta con un término de búsqueda diferente",
|
|
||||||
"noProductsInOrder": "No hay productos en el pedido",
|
|
||||||
"clickAddProduct": "Haz clic en \"Agregar Producto\" para comenzar",
|
|
||||||
"newCustomer": "Nuevo Cliente",
|
|
||||||
"customer": "Cliente",
|
|
||||||
"products": "Productos",
|
|
||||||
"items": "artículos",
|
|
||||||
"total": "Total",
|
|
||||||
"productNumber": "Producto #",
|
|
||||||
"searchByName": "Buscar cliente por nombre...",
|
|
||||||
"selectCustomer": "Seleccionar Cliente",
|
|
||||||
"searchForCustomer": "Buscar un cliente existente o crear uno nuevo",
|
|
||||||
"orderItems": "Artículos del Pedido",
|
|
||||||
"addProducts": "Agregar Productos al Pedido",
|
|
||||||
"customerLabel": "Cliente:",
|
|
||||||
"productsLabel": "Productos:",
|
|
||||||
"totalLabel": "Total:",
|
|
||||||
"orderTotal": "Total del Pedido:",
|
|
||||||
"newCustomerHeader": "Nuevo Cliente",
|
|
||||||
"orderProducts": "Productos del Pedido",
|
|
||||||
"addProduct": "Agregar Producto",
|
|
||||||
"removeItem": "Eliminar artículo",
|
|
||||||
"optionalEmail": "Correo Electrónico (Opcional)",
|
|
||||||
"readOnlyAutoGenerated": "Número de Pedido (Solo lectura - Auto-generado)",
|
|
||||||
"willBeGeneratedAutomatically": "Se generará automáticamente",
|
|
||||||
"autoGeneratedOnSave": "Auto-generado al guardar",
|
|
||||||
"orderNumberFormat": "formato: ORD-AAAAMMDD-####",
|
|
||||||
"selectProduct": "Seleccionar producto...",
|
|
||||||
"deliveryAddress": "Dirección de Entrega",
|
|
||||||
"deliveryAddressPlaceholder": "Calle, número, piso, código postal, ciudad...",
|
|
||||||
"deliveryContactName": "Nombre de Contacto para Entrega",
|
|
||||||
"deliveryContactNamePlaceholder": "Persona de contacto",
|
|
||||||
"deliveryContactPhone": "Teléfono de Contacto para Entrega",
|
|
||||||
"deliveryMethod": "Método de Entrega",
|
|
||||||
"paymentMethod": "Método de Pago",
|
|
||||||
"paymentTerms": "Términos de Pago",
|
|
||||||
"paymentStatus": "Estado de Pago",
|
|
||||||
"paymentDueDate": "Fecha de Vencimiento del Pago",
|
|
||||||
"discountPercent": "Descuento (%)",
|
|
||||||
"deliveryFee": "Tarifa de Entrega (€)",
|
|
||||||
"productionStartDate": "Fecha de Inicio de Producción",
|
|
||||||
"productionDueDate": "Fecha de Vencimiento de Producción",
|
|
||||||
"productionBatchNumber": "Número de Lote de Producción",
|
|
||||||
"productionBatchNumberPlaceholder": "LOTE-001",
|
|
||||||
"deliveryTimeWindow": "Ventana de Tiempo de Entrega",
|
|
||||||
"deliveryTimeWindowPlaceholder": "Ej: 9:00 AM - 11:00 AM",
|
|
||||||
"productionNotes": "Notas de Producción",
|
|
||||||
"productionNotesPlaceholder": "Requisitos especiales de producción o notas",
|
|
||||||
"shippingTrackingNumber": "Número de Seguimiento de Envío",
|
|
||||||
"shippingTrackingNumberPlaceholder": "Número de seguimiento",
|
|
||||||
"shippingCarrier": "Transportista de Envío",
|
|
||||||
"shippingCarrierPlaceholder": "Ej: DHL, UPS, FedEx",
|
|
||||||
"pickupLocation": "Ubicación de Recogida",
|
|
||||||
"pickupLocationPlaceholder": "Ubicación de tienda para recogida",
|
|
||||||
"actualDeliveryDate": "Fecha Real de Entrega",
|
|
||||||
"orderSource": "Origen del Pedido",
|
|
||||||
"salesChannel": "Canal de Ventas",
|
|
||||||
"salesRepId": "ID del Representante de Ventas",
|
|
||||||
"salesRepIdPlaceholder": "ID o nombre del representante de ventas",
|
|
||||||
"customerPurchaseOrder": "Orden de Compra del Cliente #",
|
|
||||||
"customerPurchaseOrderPlaceholder": "Número de OC del cliente",
|
|
||||||
"deliveryInstructions": "Instrucciones de Entrega",
|
|
||||||
"deliveryInstructionsPlaceholder": "Instrucciones especiales de entrega",
|
|
||||||
"specialInstructions": "Instrucciones Especiales",
|
|
||||||
"specialInstructionsPlaceholder": "Cualquier requisito o instrucción especial",
|
|
||||||
"internalNotes": "Notas Internas",
|
|
||||||
"internalNotesPlaceholder": "Notas internas (no visibles para el cliente)",
|
|
||||||
"customerNotes": "Notas del Cliente",
|
|
||||||
"customerNotesPlaceholder": "Notas de/para el cliente",
|
|
||||||
"notifyOnStatusChange": "Notificar en Cambio de Estado",
|
|
||||||
"notifyOnDelivery": "Notificar en Entrega",
|
|
||||||
"notificationEmail": "Correo de Notificación",
|
|
||||||
"notificationEmailPlaceholder": "cliente@correo.com",
|
|
||||||
"notificationPhone": "Teléfono de Notificación",
|
|
||||||
"qualityCheckRequired": "Control de Calidad Requerido",
|
|
||||||
"qualityCheckStatus": "Estado del Control de Calidad",
|
|
||||||
"packagingInstructions": "Instrucciones de Empaquetado",
|
|
||||||
"packagingInstructionsPlaceholder": "Requisitos especiales de empaquetado",
|
|
||||||
"labelingRequirements": "Requisitos de Etiquetado",
|
|
||||||
"labelingRequirementsPlaceholder": "Requisitos de etiqueta personalizados",
|
|
||||||
"recurringOrder": "Pedido Recurrente",
|
|
||||||
"recurringSchedule": "Programa Recurrente",
|
|
||||||
"recurringSchedulePlaceholder": "Ej: Semanalmente los lunes, Cada 2 semanas",
|
|
||||||
"tags": "Etiquetas",
|
|
||||||
"tagsPlaceholder": "urgente, vip, mayorista",
|
|
||||||
"tagsTooltip": "Etiquetas separadas por comas para búsqueda y filtrado más fácil",
|
|
||||||
"metadata": "Metadatos (JSON)",
|
|
||||||
"metadataPlaceholder": "{\"campo_personalizado\": \"valor\"}",
|
|
||||||
"metadataTooltip": "Datos personalizados adicionales en formato JSON"
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"loadingCustomers": "Cargando clientes...",
|
||||||
|
"loadingProducts": "Cargando productos...",
|
||||||
|
"errorLoadingCustomers": "Error al cargar clientes",
|
||||||
|
"errorLoadingProducts": "Error al cargar productos",
|
||||||
|
"noCustomersFound": "No se encontraron clientes",
|
||||||
|
"tryDifferentSearch": "Intenta con un término de búsqueda diferente",
|
||||||
|
"noProductsInOrder": "No hay productos en el pedido",
|
||||||
|
"clickAddProduct": "Haz clic en \"Agregar Producto\" para comenzar",
|
||||||
|
"newCustomer": "Nuevo Cliente",
|
||||||
|
"customer": "Cliente",
|
||||||
|
"products": "Productos",
|
||||||
|
"items": "artículos",
|
||||||
|
"total": "Total",
|
||||||
|
"productNumber": "Producto #",
|
||||||
|
"searchByName": "Buscar cliente por nombre...",
|
||||||
|
"selectCustomer": "Seleccionar Cliente",
|
||||||
|
"searchForCustomer": "Buscar un cliente existente o crear uno nuevo",
|
||||||
|
"orderItems": "Artículos del Pedido",
|
||||||
|
"addProducts": "Agregar Productos al Pedido",
|
||||||
|
"customerLabel": "Cliente:",
|
||||||
|
"productsLabel": "Productos:",
|
||||||
|
"totalLabel": "Total:",
|
||||||
|
"orderTotal": "Total del Pedido:",
|
||||||
|
"newCustomerHeader": "Nuevo Cliente",
|
||||||
|
"orderProducts": "Productos del Pedido",
|
||||||
|
"addProduct": "Agregar Producto",
|
||||||
|
"removeItem": "Eliminar artículo",
|
||||||
|
"optionalEmail": "Correo Electrónico (Opcional)",
|
||||||
|
"readOnlyAutoGenerated": "Número de Pedido (Solo lectura - Auto-generado)",
|
||||||
|
"willBeGeneratedAutomatically": "Se generará automáticamente",
|
||||||
|
"autoGeneratedOnSave": "Auto-generado al guardar",
|
||||||
|
"orderNumberFormat": "formato: ORD-AAAAMMDD-####",
|
||||||
|
"selectProduct": "Seleccionar producto...",
|
||||||
|
"deliveryAddress": "Dirección de Entrega",
|
||||||
|
"deliveryAddressPlaceholder": "Calle, número, piso, código postal, ciudad...",
|
||||||
|
"deliveryContactName": "Nombre de Contacto para Entrega",
|
||||||
|
"deliveryContactNamePlaceholder": "Persona de contacto",
|
||||||
|
"deliveryContactPhone": "Teléfono de Contacto para Entrega",
|
||||||
|
"deliveryMethod": "Método de Entrega",
|
||||||
|
"paymentMethod": "Método de Pago",
|
||||||
|
"paymentTerms": "Términos de Pago",
|
||||||
|
"paymentStatus": "Estado de Pago",
|
||||||
|
"paymentDueDate": "Fecha de Vencimiento del Pago",
|
||||||
|
"discountPercent": "Descuento (%)",
|
||||||
|
"deliveryFee": "Tarifa de Entrega (€)",
|
||||||
|
"productionStartDate": "Fecha de Inicio de Producción",
|
||||||
|
"productionDueDate": "Fecha de Vencimiento de Producción",
|
||||||
|
"productionBatchNumber": "Número de Lote de Producción",
|
||||||
|
"productionBatchNumberPlaceholder": "LOTE-001",
|
||||||
|
"deliveryTimeWindow": "Ventana de Tiempo de Entrega",
|
||||||
|
"deliveryTimeWindowPlaceholder": "Ej: 9:00 AM - 11:00 AM",
|
||||||
|
"productionNotes": "Notas de Producción",
|
||||||
|
"productionNotesPlaceholder": "Requisitos especiales de producción o notas",
|
||||||
|
"shippingTrackingNumber": "Número de Seguimiento de Envío",
|
||||||
|
"shippingTrackingNumberPlaceholder": "Número de seguimiento",
|
||||||
|
"shippingCarrier": "Transportista de Envío",
|
||||||
|
"shippingCarrierPlaceholder": "Ej: DHL, UPS, FedEx",
|
||||||
|
"pickupLocation": "Ubicación de Recogida",
|
||||||
|
"pickupLocationPlaceholder": "Ubicación de tienda para recogida",
|
||||||
|
"actualDeliveryDate": "Fecha Real de Entrega",
|
||||||
|
"orderSource": "Origen del Pedido",
|
||||||
|
"salesChannel": "Canal de Ventas",
|
||||||
|
"salesRepId": "ID del Representante de Ventas",
|
||||||
|
"salesRepIdPlaceholder": "ID o nombre del representante de ventas",
|
||||||
|
"customerPurchaseOrder": "Orden de Compra del Cliente #",
|
||||||
|
"customerPurchaseOrderPlaceholder": "Número de OC del cliente",
|
||||||
|
"deliveryInstructions": "Instrucciones de Entrega",
|
||||||
|
"deliveryInstructionsPlaceholder": "Instrucciones especiales de entrega",
|
||||||
|
"specialInstructions": "Instrucciones Especiales",
|
||||||
|
"specialInstructionsPlaceholder": "Cualquier requisito o instrucción especial",
|
||||||
|
"internalNotes": "Notas Internas",
|
||||||
|
"internalNotesPlaceholder": "Notas internas (no visibles para el cliente)",
|
||||||
|
"customerNotes": "Notas del Cliente",
|
||||||
|
"customerNotesPlaceholder": "Notas de/para el cliente",
|
||||||
|
"notifyOnStatusChange": "Notificar en Cambio de Estado",
|
||||||
|
"notifyOnDelivery": "Notificar en Entrega",
|
||||||
|
"notificationEmail": "Correo de Notificación",
|
||||||
|
"notificationEmailPlaceholder": "cliente@correo.com",
|
||||||
|
"notificationPhone": "Teléfono de Notificación",
|
||||||
|
"qualityCheckRequired": "Control de Calidad Requerido",
|
||||||
|
"qualityCheckStatus": "Estado del Control de Calidad",
|
||||||
|
"packagingInstructions": "Instrucciones de Empaquetado",
|
||||||
|
"packagingInstructionsPlaceholder": "Requisitos especiales de empaquetado",
|
||||||
|
"labelingRequirements": "Requisitos de Etiquetado",
|
||||||
|
"labelingRequirementsPlaceholder": "Requisitos de etiqueta personalizados",
|
||||||
|
"recurringOrder": "Pedido Recurrente",
|
||||||
|
"recurringSchedule": "Programa Recurrente",
|
||||||
|
"recurringSchedulePlaceholder": "Ej: Semanalmente los lunes, Cada 2 semanas",
|
||||||
|
"tags": "Etiquetas",
|
||||||
|
"tagsPlaceholder": "urgente, vip, mayorista",
|
||||||
|
"tagsTooltip": "Etiquetas separadas por comas para búsqueda y filtrado más fácil",
|
||||||
|
"metadata": "Metadatos (JSON)",
|
||||||
|
"metadataPlaceholder": "{\"campo_personalizado\": \"valor\"}",
|
||||||
|
"metadataTooltip": "Datos personalizados adicionales en formato JSON"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"itemTypeSelector": {
|
"itemTypeSelector": {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
Target,
|
Target,
|
||||||
Droplets,
|
Droplets,
|
||||||
TreeDeciduous,
|
TreeDeciduous,
|
||||||
Calendar,
|
|
||||||
Download,
|
Download,
|
||||||
FileText,
|
FileText,
|
||||||
Info
|
Info
|
||||||
@@ -45,34 +44,34 @@ const SustainabilityPage: React.FC = () => {
|
|||||||
['WASTE METRICS'],
|
['WASTE METRICS'],
|
||||||
['Total Waste (kg)', metrics.waste_metrics.total_waste_kg.toFixed(2)],
|
['Total Waste (kg)', metrics.waste_metrics.total_waste_kg.toFixed(2)],
|
||||||
['Production Waste (kg)', metrics.waste_metrics.production_waste_kg.toFixed(2)],
|
['Production Waste (kg)', metrics.waste_metrics.production_waste_kg.toFixed(2)],
|
||||||
['Inventory Waste (kg)', metrics.waste_metrics.inventory_waste_kg.toFixed(2)],
|
['Expired Waste (kg)', metrics.waste_metrics.expired_waste_kg.toFixed(2)],
|
||||||
['Waste Percentage (%)', metrics.waste_metrics.waste_percentage.toFixed(2)],
|
['Waste Percentage (%)', metrics.waste_metrics.waste_percentage.toFixed(2)],
|
||||||
[],
|
[],
|
||||||
['SDG 12.3 COMPLIANCE'],
|
['SDG 12.3 COMPLIANCE'],
|
||||||
['Status', metrics.sdg_compliance.sdg_12_3.status_label],
|
['Status', metrics.sdg_compliance.sdg_12_3.status_label],
|
||||||
['Reduction Achieved (%)', metrics.sdg_compliance.sdg_12_3.reduction_achieved.toFixed(2)],
|
['Reduction Achieved (%)', metrics.sdg_compliance.sdg_12_3.reduction_achieved.toFixed(2)],
|
||||||
['Progress to Target (%)', metrics.sdg_compliance.sdg_12_3.progress_to_target.toFixed(2)],
|
['Progress to Target (%)', metrics.sdg_compliance.sdg_12_3.progress_to_target.toFixed(2)],
|
||||||
['Target (%)', metrics.sdg_compliance.sdg_12_3.target_percentage],
|
['Target (%)', metrics.sdg_compliance.sdg_12_3.target_reduction],
|
||||||
[],
|
[],
|
||||||
['ENVIRONMENTAL IMPACT'],
|
['ENVIRONMENTAL IMPACT'],
|
||||||
['CO2 Emissions (kg)', metrics.environmental_impact.co2_emissions.kg.toFixed(2)],
|
['CO2 Emissions (kg)', metrics.environmental_impact.co2_emissions.kg.toFixed(2)],
|
||||||
['CO2 Emissions (tons)', metrics.environmental_impact.co2_emissions.tons.toFixed(4)],
|
['CO2 Emissions (tons)', metrics.environmental_impact.co2_emissions.tons.toFixed(4)],
|
||||||
['Trees to Offset', metrics.environmental_impact.co2_emissions.trees_to_offset.toFixed(1)],
|
['Trees to Offset', metrics.environmental_impact.co2_emissions.trees_to_offset.toFixed(1)],
|
||||||
['Equivalent Car KM', metrics.environmental_impact.co2_emissions.car_km_equivalent.toFixed(0)],
|
['Equivalent Car KM', metrics.environmental_impact.human_equivalents.car_km_equivalent.toFixed(0)],
|
||||||
['Water Footprint (liters)', metrics.environmental_impact.water_footprint.liters.toFixed(0)],
|
['Water Footprint (liters)', metrics.environmental_impact.water_footprint.liters.toFixed(0)],
|
||||||
['Water Footprint (m³)', metrics.environmental_impact.water_footprint.cubic_meters.toFixed(2)],
|
['Water Footprint (m³)', metrics.environmental_impact.water_footprint.cubic_meters.toFixed(2)],
|
||||||
['Equivalent Showers', metrics.environmental_impact.water_footprint.shower_equivalent.toFixed(0)],
|
['Equivalent Showers', metrics.environmental_impact.human_equivalents.showers_equivalent.toFixed(0)],
|
||||||
['Land Use (m²)', metrics.environmental_impact.land_use.square_meters.toFixed(2)],
|
['Land Use (m²)', metrics.environmental_impact.land_use.square_meters.toFixed(2)],
|
||||||
['Land Use (hectares)', metrics.environmental_impact.land_use.hectares.toFixed(4)],
|
['Land Use (hectares)', metrics.environmental_impact.land_use.hectares.toFixed(4)],
|
||||||
[],
|
[],
|
||||||
['FINANCIAL IMPACT'],
|
['FINANCIAL IMPACT'],
|
||||||
['Waste Cost (EUR)', metrics.financial_impact.waste_cost_eur.toFixed(2)],
|
['Waste Cost (EUR)', metrics.financial_impact.waste_cost_eur.toFixed(2)],
|
||||||
['Potential Monthly Savings (EUR)', metrics.financial_impact.potential_monthly_savings.toFixed(2)],
|
['Potential Monthly Savings (EUR)', metrics.financial_impact.potential_monthly_savings.toFixed(2)],
|
||||||
['ROI on Prevention (%)', metrics.financial_impact.roi_on_waste_prevention.toFixed(2)],
|
['Annual Projection (EUR)', metrics.financial_impact.annual_projection.toFixed(2)],
|
||||||
[],
|
[],
|
||||||
['AVOIDED WASTE (AI PREDICTIONS)'],
|
['AVOIDED WASTE (AI PREDICTIONS)'],
|
||||||
['Waste Avoided (kg)', metrics.avoided_waste.total_waste_avoided_kg.toFixed(2)],
|
['Waste Avoided (kg)', metrics.avoided_waste.waste_avoided_kg.toFixed(2)],
|
||||||
['Cost Savings (EUR)', metrics.avoided_waste.cost_savings_eur.toFixed(2)],
|
['AI Assisted Batches', metrics.avoided_waste.ai_assisted_batches],
|
||||||
['CO2 Avoided (kg)', metrics.avoided_waste.environmental_impact_avoided.co2_kg.toFixed(2)],
|
['CO2 Avoided (kg)', metrics.avoided_waste.environmental_impact_avoided.co2_kg.toFixed(2)],
|
||||||
['Water Saved (liters)', metrics.avoided_waste.environmental_impact_avoided.water_liters.toFixed(0)],
|
['Water Saved (liters)', metrics.avoided_waste.environmental_impact_avoided.water_liters.toFixed(0)],
|
||||||
[],
|
[],
|
||||||
@@ -214,76 +213,6 @@ const SustainabilityPage: React.FC = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have insufficient data
|
|
||||||
if (metrics.data_sufficient === false) {
|
|
||||||
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-8">
|
|
||||||
<div className="text-center py-12 max-w-2xl mx-auto">
|
|
||||||
<div className="mb-6 inline-flex items-center justify-center w-20 h-20 bg-blue-500/10 rounded-full">
|
|
||||||
<Info className="w-10 h-10 text-blue-600" />
|
|
||||||
</div>
|
|
||||||
<h3 className="text-xl font-semibold text-[var(--text-primary)] mb-3">
|
|
||||||
{t('sustainability:insufficient_data.title', 'Collecting Sustainability Data')}
|
|
||||||
</h3>
|
|
||||||
<p className="text-base text-[var(--text-secondary)] mb-6">
|
|
||||||
{t('sustainability:insufficient_data.description',
|
|
||||||
'Start producing batches to see your sustainability metrics and SDG compliance status.'
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<div className="bg-[var(--bg-secondary)] rounded-lg p-6 mb-6">
|
|
||||||
<h4 className="text-sm font-medium text-[var(--text-primary)] mb-3">
|
|
||||||
{t('sustainability:insufficient_data.requirements_title', 'Minimum Requirements')}
|
|
||||||
</h4>
|
|
||||||
<ul className="text-sm text-[var(--text-secondary)] space-y-2 text-left max-w-md mx-auto">
|
|
||||||
<li className="flex items-start gap-2">
|
|
||||||
<span className="text-blue-600 mt-0.5">•</span>
|
|
||||||
<span>
|
|
||||||
{t('sustainability:insufficient_data.req_production',
|
|
||||||
'At least 50kg of production over the analysis period'
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li className="flex items-start gap-2">
|
|
||||||
<span className="text-blue-600 mt-0.5">•</span>
|
|
||||||
<span>
|
|
||||||
{t('sustainability:insufficient_data.req_baseline',
|
|
||||||
'90 days of production history for accurate baseline calculation'
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li className="flex items-start gap-2">
|
|
||||||
<span className="text-blue-600 mt-0.5">•</span>
|
|
||||||
<span>
|
|
||||||
{t('sustainability:insufficient_data.req_tracking',
|
|
||||||
'Production batches with waste tracking enabled'
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-center gap-2 text-sm text-[var(--text-tertiary)]">
|
|
||||||
<Calendar className="w-4 h-4" />
|
|
||||||
<span>
|
|
||||||
{t('sustainability:insufficient_data.current_production',
|
|
||||||
'Current production: {{production}}kg of {{required}}kg minimum',
|
|
||||||
{
|
|
||||||
production: metrics.current_production_kg?.toFixed(1) || '0.0',
|
|
||||||
required: metrics.minimum_production_required_kg || 50
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 p-4 sm:p-6">
|
<div className="space-y-6 p-4 sm:p-6">
|
||||||
{/* Page Header */}
|
{/* Page Header */}
|
||||||
|
|||||||
Reference in New Issue
Block a user