Fix and UI imporvements 2
This commit is contained in:
@@ -31,9 +31,9 @@ interface NetworkSummaryCardsProps {
|
|||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NetworkSummaryCards: React.FC<NetworkSummaryCardsProps> = ({
|
const NetworkSummaryCards: React.FC<NetworkSummaryCardsProps> = ({
|
||||||
data,
|
data,
|
||||||
isLoading
|
isLoading
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('dashboard');
|
const { t } = useTranslation('dashboard');
|
||||||
|
|
||||||
@@ -43,10 +43,10 @@ const NetworkSummaryCards: React.FC<NetworkSummaryCardsProps> = ({
|
|||||||
{[...Array(5)].map((_, index) => (
|
{[...Array(5)].map((_, index) => (
|
||||||
<Card key={index} className="animate-pulse">
|
<Card key={index} className="animate-pulse">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<CardTitle className="text-sm text-gray-500 h-4 bg-gray-200 rounded w-3/4"></CardTitle>
|
<CardTitle className="text-sm text-[var(--text-tertiary)] h-4 bg-[var(--bg-tertiary)] rounded w-3/4"></CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="h-6 bg-gray-200 rounded w-1/2"></div>
|
<div className="h-6 bg-[var(--bg-tertiary)] rounded w-1/2"></div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
@@ -56,7 +56,7 @@ const NetworkSummaryCards: React.FC<NetworkSummaryCardsProps> = ({
|
|||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-8 text-gray-500">
|
<div className="text-center py-8 text-[var(--text-secondary)]">
|
||||||
{t('enterprise.no_network_data')}
|
{t('enterprise.no_network_data')}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -67,16 +67,16 @@ const NetworkSummaryCards: React.FC<NetworkSummaryCardsProps> = ({
|
|||||||
{/* Network Outlets Card */}
|
{/* Network Outlets Card */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||||
<CardTitle className="text-sm font-medium text-gray-500">
|
<CardTitle className="text-sm font-medium text-[var(--text-secondary)]">
|
||||||
{t('enterprise.network_outlets')}
|
{t('enterprise.network_outlets')}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<StoreIcon className="w-4 h-4 text-blue-500" />
|
<StoreIcon className="w-5 h-5 text-[var(--color-primary)]" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold text-gray-900">
|
<div className="text-2xl font-bold text-[var(--text-primary)]">
|
||||||
{data.child_tenant_count}
|
{data.child_tenant_count}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-[var(--text-secondary)] mt-1">
|
||||||
{t('enterprise.outlets_in_network')}
|
{t('enterprise.outlets_in_network')}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -85,16 +85,16 @@ const NetworkSummaryCards: React.FC<NetworkSummaryCardsProps> = ({
|
|||||||
{/* Network Sales Card */}
|
{/* Network Sales Card */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||||
<CardTitle className="text-sm font-medium text-gray-500">
|
<CardTitle className="text-sm font-medium text-[var(--text-secondary)]">
|
||||||
{t('enterprise.network_sales')}
|
{t('enterprise.network_sales')}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<DollarSign className="w-4 h-4 text-green-500" />
|
<DollarSign className="w-5 h-5 text-[var(--color-success)]" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold text-gray-900">
|
<div className="text-2xl font-bold text-[var(--text-primary)]">
|
||||||
{formatCurrency(data.network_sales_30d, 'EUR')}
|
{formatCurrency(data.network_sales_30d, 'EUR')}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-[var(--text-secondary)] mt-1">
|
||||||
{t('enterprise.last_30_days')}
|
{t('enterprise.last_30_days')}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -103,16 +103,16 @@ const NetworkSummaryCards: React.FC<NetworkSummaryCardsProps> = ({
|
|||||||
{/* Production Volume Card */}
|
{/* Production Volume Card */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||||
<CardTitle className="text-sm font-medium text-gray-500">
|
<CardTitle className="text-sm font-medium text-[var(--text-secondary)]">
|
||||||
{t('enterprise.production_volume')}
|
{t('enterprise.production_volume')}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<Package className="w-4 h-4 text-purple-500" />
|
<Package className="w-5 h-5 text-[var(--color-secondary)]" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold text-gray-900">
|
<div className="text-2xl font-bold text-[var(--text-primary)]">
|
||||||
{new Intl.NumberFormat('es-ES').format(data.production_volume_30d)} kg
|
{new Intl.NumberFormat('es-ES').format(data.production_volume_30d)} kg
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-[var(--text-secondary)] mt-1">
|
||||||
{t('enterprise.last_30_days')}
|
{t('enterprise.last_30_days')}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -121,16 +121,16 @@ const NetworkSummaryCards: React.FC<NetworkSummaryCardsProps> = ({
|
|||||||
{/* Pending Internal Transfers Card */}
|
{/* Pending Internal Transfers Card */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||||
<CardTitle className="text-sm font-medium text-gray-500">
|
<CardTitle className="text-sm font-medium text-[var(--text-secondary)]">
|
||||||
{t('enterprise.pending_orders')}
|
{t('enterprise.pending_orders')}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<ShoppingCart className="w-4 h-4 text-yellow-500" />
|
<ShoppingCart className="w-5 h-5 text-[var(--color-warning)]" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold text-gray-900">
|
<div className="text-2xl font-bold text-[var(--text-primary)]">
|
||||||
{data.pending_internal_transfers_count}
|
{data.pending_internal_transfers_count}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-[var(--text-secondary)] mt-1">
|
||||||
{t('enterprise.internal_transfers')}
|
{t('enterprise.internal_transfers')}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -139,16 +139,16 @@ const NetworkSummaryCards: React.FC<NetworkSummaryCardsProps> = ({
|
|||||||
{/* Active Shipments Card */}
|
{/* Active Shipments Card */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||||
<CardTitle className="text-sm font-medium text-gray-500">
|
<CardTitle className="text-sm font-medium text-[var(--text-secondary)]">
|
||||||
{t('enterprise.active_shipments')}
|
{t('enterprise.active_shipments')}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<Truck className="w-4 h-4 text-red-500" />
|
<Truck className="w-5 h-5 text-[var(--color-info)]" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold text-gray-900">
|
<div className="text-2xl font-bold text-[var(--text-primary)]">
|
||||||
{data.active_shipments_count}
|
{data.active_shipments_count}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-[var(--text-secondary)] mt-1">
|
||||||
{t('enterprise.today')}
|
{t('enterprise.today')}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -133,6 +133,10 @@ function getActionLabelKey(actionType: string, metadata?: Record<string, any>):
|
|||||||
key: 'alerts:actions.reject_po',
|
key: 'alerts:actions.reject_po',
|
||||||
extractParams: () => ({})
|
extractParams: () => ({})
|
||||||
},
|
},
|
||||||
|
'modify_po': {
|
||||||
|
key: 'alerts:actions.modify_po',
|
||||||
|
extractParams: () => ({})
|
||||||
|
},
|
||||||
'view_po_details': {
|
'view_po_details': {
|
||||||
key: 'alerts:actions.view_po_details',
|
key: 'alerts:actions.view_po_details',
|
||||||
extractParams: () => ({})
|
extractParams: () => ({})
|
||||||
@@ -252,6 +256,17 @@ function ActionCard({ alert, showEscalationBadge = false, onActionSuccess, onAct
|
|||||||
// Determine if this is a critical alert that needs stronger visual treatment
|
// Determine if this is a critical alert that needs stronger visual treatment
|
||||||
const isCritical = alert.priority_level === 'critical' || alert.priority_level === 'CRITICAL';
|
const isCritical = alert.priority_level === 'critical' || alert.priority_level === 'CRITICAL';
|
||||||
|
|
||||||
|
// Detect if this is an AI-generated PO
|
||||||
|
const isAIGeneratedPO = alert.event_type?.includes('po') &&
|
||||||
|
(alert.orchestrator_context?.already_addressed ||
|
||||||
|
alert.orchestrator_context?.action_type === 'create_po' ||
|
||||||
|
alert.event_metadata?.source === 'orchestrator' ||
|
||||||
|
alert.event_metadata?.auto_generated === true ||
|
||||||
|
alert.event_metadata?.reasoning_data?.metadata?.ai_assisted === true ||
|
||||||
|
alert.event_metadata?.reasoning_data?.metadata?.trigger_source === 'orchestrator_auto' ||
|
||||||
|
alert.ai_reasoning?.details?.metadata?.ai_assisted === true ||
|
||||||
|
alert.ai_reasoning?.details?.metadata?.trigger_source === 'orchestrator_auto');
|
||||||
|
|
||||||
// Extract reasoning from alert using new rendering utility
|
// Extract reasoning from alert using new rendering utility
|
||||||
const reasoningText = renderAIReasoning(alert, t) || '';
|
const reasoningText = renderAIReasoning(alert, t) || '';
|
||||||
|
|
||||||
@@ -294,6 +309,16 @@ function ActionCard({ alert, showEscalationBadge = false, onActionSuccess, onAct
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* AI-Generated PO Badge */}
|
||||||
|
{isAIGeneratedPO && (
|
||||||
|
<div className="mb-2">
|
||||||
|
<div className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm font-semibold bg-gradient-to-r from-[var(--color-info-100)] to-[var(--color-success-100)] text-[var(--color-info-900)] border-2 border-[var(--color-info-300)] shadow-sm">
|
||||||
|
<Bot className="w-4 h-4" />
|
||||||
|
<span>{t('alerts:orchestration.ai_generated_po')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Escalation Badge Details */}
|
{/* Escalation Badge Details */}
|
||||||
{showEscalationBadge && <EscalationBadge alert={alert} />}
|
{showEscalationBadge && <EscalationBadge alert={alert} />}
|
||||||
|
|
||||||
@@ -580,6 +605,7 @@ export function UnifiedActionQueueCard({
|
|||||||
// PO Details Modal state
|
// PO Details Modal state
|
||||||
const [isPODetailsModalOpen, setIsPODetailsModalOpen] = useState(false);
|
const [isPODetailsModalOpen, setIsPODetailsModalOpen] = useState(false);
|
||||||
const [selectedPOId, setSelectedPOId] = useState<string | null>(null);
|
const [selectedPOId, setSelectedPOId] = useState<string | null>(null);
|
||||||
|
const [poModalMode, setPOModalMode] = useState<'view' | 'edit'>('view');
|
||||||
|
|
||||||
// Subscribe to SSE notifications for real-time alerts
|
// Subscribe to SSE notifications for real-time alerts
|
||||||
const { notifications, isConnected } = useEventNotifications();
|
const { notifications, isConnected } = useEventNotifications();
|
||||||
@@ -638,13 +664,14 @@ export function UnifiedActionQueueCard({
|
|||||||
};
|
};
|
||||||
}, [actionQueue]);
|
}, [actionQueue]);
|
||||||
|
|
||||||
// Listen for PO details modal open events
|
// Listen for PO details modal open events (view mode)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handlePODetailsOpen = (event: CustomEvent) => {
|
const handlePODetailsOpen = (event: CustomEvent) => {
|
||||||
const { po_id } = event.detail;
|
const { po_id, mode } = event.detail;
|
||||||
|
|
||||||
if (po_id) {
|
if (po_id) {
|
||||||
setSelectedPOId(po_id);
|
setSelectedPOId(po_id);
|
||||||
|
setPOModalMode(mode || 'view');
|
||||||
setIsPODetailsModalOpen(true);
|
setIsPODetailsModalOpen(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -655,6 +682,24 @@ export function UnifiedActionQueueCard({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Listen for PO edit modal open events (edit mode)
|
||||||
|
useEffect(() => {
|
||||||
|
const handlePOEditOpen = (event: CustomEvent) => {
|
||||||
|
const { po_id, mode } = event.detail;
|
||||||
|
|
||||||
|
if (po_id) {
|
||||||
|
setSelectedPOId(po_id);
|
||||||
|
setPOModalMode(mode || 'edit');
|
||||||
|
setIsPODetailsModalOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('po:open-edit' as any, handlePOEditOpen);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('po:open-edit' as any, handlePOEditOpen);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Create a stable identifier for notifications to prevent infinite re-renders
|
// Create a stable identifier for notifications to prevent infinite re-renders
|
||||||
// Only recalculate when the actual notification IDs and read states change
|
// Only recalculate when the actual notification IDs and read states change
|
||||||
const notificationKey = useMemo(() => {
|
const notificationKey = useMemo(() => {
|
||||||
@@ -976,7 +1021,7 @@ export function UnifiedActionQueueCard({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* PO Details Modal - Opened by "Ver detalles" action */}
|
{/* PO Details Modal - Opened by "Ver detalles" or "Modificar PO" action */}
|
||||||
{isPODetailsModalOpen && selectedPOId && tenantId && (
|
{isPODetailsModalOpen && selectedPOId && tenantId && (
|
||||||
<UnifiedPurchaseOrderModal
|
<UnifiedPurchaseOrderModal
|
||||||
poId={selectedPOId}
|
poId={selectedPOId}
|
||||||
@@ -985,9 +1030,10 @@ export function UnifiedActionQueueCard({
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
setIsPODetailsModalOpen(false);
|
setIsPODetailsModalOpen(false);
|
||||||
setSelectedPOId(null);
|
setSelectedPOId(null);
|
||||||
|
setPOModalMode('view');
|
||||||
}}
|
}}
|
||||||
showApprovalActions={true}
|
showApprovalActions={true}
|
||||||
initialMode="view"
|
initialMode={poModalMode}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"approve_po": "Approve €{amount} order",
|
"approve_po": "Approve €{amount} order",
|
||||||
"reject_po": "Reject order",
|
"reject_po": "Reject order",
|
||||||
|
"modify_po": "Modify order",
|
||||||
"view_po_details": "View details",
|
"view_po_details": "View details",
|
||||||
"call_supplier": "Call {supplier} ({phone})",
|
"call_supplier": "Call {supplier} ({phone})",
|
||||||
"see_reasoning": "See full reasoning",
|
"see_reasoning": "See full reasoning",
|
||||||
@@ -45,7 +46,8 @@
|
|||||||
"estimated_impact": "Estimated Impact",
|
"estimated_impact": "Estimated Impact",
|
||||||
"impact_description": "Savings from prevented rush orders, stockouts, and waste",
|
"impact_description": "Savings from prevented rush orders, stockouts, and waste",
|
||||||
"last_run": "Last run",
|
"last_run": "Last run",
|
||||||
"what_ai_did": "What AI did for you"
|
"what_ai_did": "What AI did for you",
|
||||||
|
"ai_generated_po": "AI-Generated PO"
|
||||||
},
|
},
|
||||||
"no_reasoning_available": "No reasoning available",
|
"no_reasoning_available": "No reasoning available",
|
||||||
"metrics": {
|
"metrics": {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"approve_po": "Aprobar pedido €{amount}",
|
"approve_po": "Aprobar pedido €{amount}",
|
||||||
"reject_po": "Rechazar pedido",
|
"reject_po": "Rechazar pedido",
|
||||||
|
"modify_po": "Modificar pedido",
|
||||||
"view_po_details": "Ver detalles",
|
"view_po_details": "Ver detalles",
|
||||||
"call_supplier": "Llamar a {supplier} ({phone})",
|
"call_supplier": "Llamar a {supplier} ({phone})",
|
||||||
"see_reasoning": "Ver razonamiento completo",
|
"see_reasoning": "Ver razonamiento completo",
|
||||||
@@ -45,7 +46,8 @@
|
|||||||
"estimated_impact": "Impacto Estimado",
|
"estimated_impact": "Impacto Estimado",
|
||||||
"impact_description": "Ahorros por prevención de pedidos urgentes, faltas de stock y desperdicios",
|
"impact_description": "Ahorros por prevención de pedidos urgentes, faltas de stock y desperdicios",
|
||||||
"last_run": "Última ejecución",
|
"last_run": "Última ejecución",
|
||||||
"what_ai_did": "Lo que hizo la IA por ti"
|
"what_ai_did": "Lo que hizo la IA por ti",
|
||||||
|
"ai_generated_po": "PO Generada por IA"
|
||||||
},
|
},
|
||||||
"ai_reasoning_label": "Razonamiento de IA",
|
"ai_reasoning_label": "Razonamiento de IA",
|
||||||
"no_reasoning_available": "No hay razonamiento disponible",
|
"no_reasoning_available": "No hay razonamiento disponible",
|
||||||
|
|||||||
@@ -200,22 +200,22 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||||
<div className="px-4 sm:px-6 lg:px-8 py-6 min-h-screen" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="px-4 sm:px-6 lg:px-8 py-6 min-h-screen bg-[var(--bg-secondary)]">
|
||||||
{/* Breadcrumb / Return to Network Banner */}
|
{/* Breadcrumb / Return to Network Banner */}
|
||||||
{enterpriseState.selectedOutletId && !enterpriseState.isNetworkView && (
|
{enterpriseState.selectedOutletId && !enterpriseState.isNetworkView && (
|
||||||
<div className="mb-6 rounded-lg p-4" style={{
|
<div
|
||||||
backgroundColor: 'var(--color-info-light, #dbeafe)',
|
className="mb-6 rounded-lg p-4 border border-[var(--border-primary)]"
|
||||||
borderColor: 'var(--color-info, #3b82f6)',
|
style={{
|
||||||
borderWidth: '1px',
|
backgroundColor: 'var(--color-info-50)',
|
||||||
borderStyle: 'solid'
|
}}
|
||||||
}}>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Network className="w-5 h-5" style={{ color: 'var(--color-info)' }} />
|
<Network className="w-5 h-5 text-[var(--color-info)]" />
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<span className="font-medium" style={{ color: 'var(--color-info)' }}>Network Overview</span>
|
<span className="font-medium text-[var(--color-info)]">Network Overview</span>
|
||||||
<ChevronRight className="w-4 h-4" style={{ color: 'var(--color-info-light, #93c5fd)' }} />
|
<ChevronRight className="w-4 h-4 text-[var(--color-info-300)]" />
|
||||||
<span className="text-gray-700 font-semibold">{enterpriseState.selectedOutletName}</span>
|
<span className="text-[var(--text-primary)] font-semibold">{enterpriseState.selectedOutletName}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -230,18 +230,18 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
</div>
|
</div>
|
||||||
{enterpriseState.networkMetrics && (
|
{enterpriseState.networkMetrics && (
|
||||||
<div className="mt-3 pt-3 border-t grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-sm"
|
<div className="mt-3 pt-3 border-t grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-sm"
|
||||||
style={{ borderColor: 'var(--color-info-light, #93c5fd)' }}>
|
style={{ borderColor: 'var(--border-primary)' }}>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ color: 'var(--color-info)' }}>Network Average Sales:</span>
|
<span className="text-[var(--color-info)]">Network Average Sales:</span>
|
||||||
<span className="ml-2 font-semibold">€{enterpriseState.networkMetrics.averageSales.toLocaleString()}</span>
|
<span className="ml-2 font-semibold text-[var(--text-primary)]">€{enterpriseState.networkMetrics.averageSales.toLocaleString()}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ color: 'var(--color-info)' }}>Total Outlets:</span>
|
<span className="text-[var(--color-info)]">Total Outlets:</span>
|
||||||
<span className="ml-2 font-semibold">{enterpriseState.networkMetrics.childCount}</span>
|
<span className="ml-2 font-semibold text-[var(--text-primary)]">{enterpriseState.networkMetrics.childCount}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ color: 'var(--color-info)' }}>Network Total:</span>
|
<span className="text-[var(--color-info)]">Network Total:</span>
|
||||||
<span className="ml-2 font-semibold">€{enterpriseState.networkMetrics.totalSales.toLocaleString()}</span>
|
<span className="ml-2 font-semibold text-[var(--text-primary)]">€{enterpriseState.networkMetrics.totalSales.toLocaleString()}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -262,10 +262,10 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
<Network className="w-8 h-8 text-white" />
|
<Network className="w-8 h-8 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-4xl font-bold" style={{ color: 'var(--text-primary)' }}>
|
<h1 className="text-4xl font-bold text-[var(--text-primary)]">
|
||||||
{t('enterprise.network_dashboard')}
|
{t('enterprise.network_dashboard')}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-1" style={{ color: 'var(--text-secondary)' }}>
|
<p className="mt-1 text-[var(--text-secondary)]">
|
||||||
{t('enterprise.network_summary_description')}
|
{t('enterprise.network_summary_description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -288,16 +288,16 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
<Card className="h-full">
|
<Card className="h-full">
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Truck className="w-5 h-5 text-blue-600" />
|
<Truck className="w-5 h-5 text-[var(--color-info)]" />
|
||||||
<CardTitle>{t('enterprise.distribution_map')}</CardTitle>
|
<CardTitle>{t('enterprise.distribution_map')}</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Calendar className="w-4 h-4 text-gray-500" />
|
<Calendar className="w-4 h-4 text-[var(--text-secondary)]" />
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
value={selectedDate}
|
value={selectedDate}
|
||||||
onChange={(e) => setSelectedDate(e.target.value)}
|
onChange={(e) => setSelectedDate(e.target.value)}
|
||||||
className="border rounded px-2 py-1 text-sm"
|
className="border border-[var(--border-primary)] rounded-md px-2 py-1 text-sm bg-[var(--input-bg)] text-[var(--text-primary)]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -308,7 +308,7 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
shipments={distributionOverview.status_counts}
|
shipments={distributionOverview.status_counts}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-96 flex items-center justify-center text-gray-500">
|
<div className="h-96 flex items-center justify-center text-[var(--text-secondary)]">
|
||||||
{t('enterprise.no_distribution_data')}
|
{t('enterprise.no_distribution_data')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -321,14 +321,14 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
<Card className="h-full">
|
<Card className="h-full">
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BarChart3 className="w-5 h-5 text-green-600" />
|
<BarChart3 className="w-5 h-5 text-[var(--color-success)]" />
|
||||||
<CardTitle>{t('enterprise.outlet_performance')}</CardTitle>
|
<CardTitle>{t('enterprise.outlet_performance')}</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<select
|
<select
|
||||||
value={selectedMetric}
|
value={selectedMetric}
|
||||||
onChange={(e) => setSelectedMetric(e.target.value)}
|
onChange={(e) => setSelectedMetric(e.target.value)}
|
||||||
className="border rounded px-2 py-1 text-sm"
|
className="border border-[var(--border-primary)] rounded-md px-2 py-1 text-sm bg-[var(--input-bg)] text-[var(--text-primary)]"
|
||||||
>
|
>
|
||||||
<option value="sales">{t('enterprise.metrics.sales')}</option>
|
<option value="sales">{t('enterprise.metrics.sales')}</option>
|
||||||
<option value="inventory_value">{t('enterprise.metrics.inventory_value')}</option>
|
<option value="inventory_value">{t('enterprise.metrics.inventory_value')}</option>
|
||||||
@@ -337,7 +337,7 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
<select
|
<select
|
||||||
value={selectedPeriod}
|
value={selectedPeriod}
|
||||||
onChange={(e) => setSelectedPeriod(Number(e.target.value))}
|
onChange={(e) => setSelectedPeriod(Number(e.target.value))}
|
||||||
className="border rounded px-2 py-1 text-sm"
|
className="border border-[var(--border-primary)] rounded-md px-2 py-1 text-sm bg-[var(--input-bg)] text-[var(--text-primary)]"
|
||||||
>
|
>
|
||||||
<option value={7}>{t('enterprise.last_7_days')}</option>
|
<option value={7}>{t('enterprise.last_7_days')}</option>
|
||||||
<option value={30}>{t('enterprise.last_30_days')}</option>
|
<option value={30}>{t('enterprise.last_30_days')}</option>
|
||||||
@@ -354,7 +354,7 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
onOutletClick={handleOutletClick}
|
onOutletClick={handleOutletClick}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-96 flex items-center justify-center text-gray-500">
|
<div className="h-96 flex items-center justify-center text-[var(--text-secondary)]">
|
||||||
{t('enterprise.no_performance_data')}
|
{t('enterprise.no_performance_data')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -367,121 +367,105 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center gap-2">
|
<CardHeader className="flex flex-row items-center gap-2">
|
||||||
<TrendingUp className="w-5 h-5 text-purple-600" />
|
<TrendingUp className="w-5 h-5 text-[var(--color-primary)]" />
|
||||||
<CardTitle>{t('enterprise.network_forecast')}</CardTitle>
|
<CardTitle>{t('enterprise.network_forecast')}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{forecastSummary && forecastSummary.aggregated_forecasts ? (
|
{forecastSummary && forecastSummary.aggregated_forecasts ? (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
{/* Total Demand Card */}
|
{/* Total Demand Card */}
|
||||||
<div
|
<Card className="hover:shadow-lg transition-shadow duration-300">
|
||||||
className="p-6 rounded-xl border-2 transition-all duration-300 hover:shadow-lg"
|
<CardContent className="p-6">
|
||||||
style={{
|
<div className="flex items-center gap-3 mb-3">
|
||||||
backgroundColor: 'var(--color-info-50)',
|
<div
|
||||||
borderColor: 'var(--color-info-200)',
|
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
||||||
}}
|
style={{ backgroundColor: 'var(--color-info-100)' }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<Package className="w-5 h-5" style={{ color: 'var(--color-info-600)' }} />
|
||||||
<div
|
</div>
|
||||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-info-800)' }}>
|
||||||
style={{ backgroundColor: 'var(--color-info-100)' }}
|
{t('enterprise.total_demand')}
|
||||||
>
|
</h3>
|
||||||
<Package className="w-5 h-5" style={{ color: 'var(--color-info-600)' }} />
|
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-info-800)' }}>
|
<p className="text-3xl font-bold" style={{ color: 'var(--color-info-900)' }}>
|
||||||
{t('enterprise.total_demand')}
|
{Object.values(forecastSummary.aggregated_forecasts).reduce((total: number, day: any) =>
|
||||||
</h3>
|
total + Object.values(day).reduce((dayTotal: number, product: any) =>
|
||||||
</div>
|
dayTotal + (product.predicted_demand || 0), 0), 0
|
||||||
<p className="text-3xl font-bold" style={{ color: 'var(--color-info-900)' }}>
|
).toLocaleString()}
|
||||||
{Object.values(forecastSummary.aggregated_forecasts).reduce((total: number, day: any) =>
|
</p>
|
||||||
total + Object.values(day).reduce((dayTotal: number, product: any) =>
|
</CardContent>
|
||||||
dayTotal + (product.predicted_demand || 0), 0), 0
|
</Card>
|
||||||
).toLocaleString()}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Days Forecast Card */}
|
{/* Days Forecast Card */}
|
||||||
<div
|
<Card className="hover:shadow-lg transition-shadow duration-300">
|
||||||
className="p-6 rounded-xl border-2 transition-all duration-300 hover:shadow-lg"
|
<CardContent className="p-6">
|
||||||
style={{
|
<div className="flex items-center gap-3 mb-3">
|
||||||
backgroundColor: 'var(--color-success-50)',
|
<div
|
||||||
borderColor: 'var(--color-success-200)',
|
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
||||||
}}
|
style={{ backgroundColor: 'var(--color-success-100)' }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<Calendar className="w-5 h-5" style={{ color: 'var(--color-success-600)' }} />
|
||||||
<div
|
</div>
|
||||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-success-800)' }}>
|
||||||
style={{ backgroundColor: 'var(--color-success-100)' }}
|
{t('enterprise.days_forecast')}
|
||||||
>
|
</h3>
|
||||||
<Calendar className="w-5 h-5" style={{ color: 'var(--color-success-600)' }} />
|
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-success-800)' }}>
|
<p className="text-3xl font-bold" style={{ color: 'var(--color-success-900)' }}>
|
||||||
{t('enterprise.days_forecast')}
|
{forecastSummary.days_forecast || 7}
|
||||||
</h3>
|
</p>
|
||||||
</div>
|
</CardContent>
|
||||||
<p className="text-3xl font-bold" style={{ color: 'var(--color-success-900)' }}>
|
</Card>
|
||||||
{forecastSummary.days_forecast || 7}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Average Daily Demand Card */}
|
{/* Average Daily Demand Card */}
|
||||||
<div
|
<Card className="hover:shadow-lg transition-shadow duration-300">
|
||||||
className="p-6 rounded-xl border-2 transition-all duration-300 hover:shadow-lg"
|
<CardContent className="p-6">
|
||||||
style={{
|
<div className="flex items-center gap-3 mb-3">
|
||||||
backgroundColor: 'var(--color-secondary-50)',
|
<div
|
||||||
borderColor: 'var(--color-secondary-200)',
|
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
||||||
}}
|
style={{ backgroundColor: 'var(--color-secondary-100)' }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<Activity className="w-5 h-5" style={{ color: 'var(--color-secondary-600)' }} />
|
||||||
<div
|
</div>
|
||||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-secondary-800)' }}>
|
||||||
style={{ backgroundColor: 'var(--color-secondary-100)' }}
|
{t('enterprise.avg_daily_demand')}
|
||||||
>
|
</h3>
|
||||||
<Activity className="w-5 h-5" style={{ color: 'var(--color-secondary-600)' }} />
|
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-secondary-800)' }}>
|
<p className="text-3xl font-bold" style={{ color: 'var(--color-secondary-900)' }}>
|
||||||
{t('enterprise.avg_daily_demand')}
|
{forecastSummary.aggregated_forecasts
|
||||||
</h3>
|
? Math.round(Object.values(forecastSummary.aggregated_forecasts).reduce((total: number, day: any) =>
|
||||||
</div>
|
total + Object.values(day).reduce((dayTotal: number, product: any) =>
|
||||||
<p className="text-3xl font-bold" style={{ color: 'var(--color-secondary-900)' }}>
|
dayTotal + (product.predicted_demand || 0), 0), 0) /
|
||||||
{forecastSummary.aggregated_forecasts
|
Object.keys(forecastSummary.aggregated_forecasts).length
|
||||||
? Math.round(Object.values(forecastSummary.aggregated_forecasts).reduce((total: number, day: any) =>
|
).toLocaleString()
|
||||||
total + Object.values(day).reduce((dayTotal: number, product: any) =>
|
: 0}
|
||||||
dayTotal + (product.predicted_demand || 0), 0), 0) /
|
</p>
|
||||||
Object.keys(forecastSummary.aggregated_forecasts).length
|
</CardContent>
|
||||||
).toLocaleString()
|
</Card>
|
||||||
: 0}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Last Updated Card */}
|
{/* Last Updated Card */}
|
||||||
<div
|
<Card className="hover:shadow-lg transition-shadow duration-300">
|
||||||
className="p-6 rounded-xl border-2 transition-all duration-300 hover:shadow-lg"
|
<CardContent className="p-6">
|
||||||
style={{
|
<div className="flex items-center gap-3 mb-3">
|
||||||
backgroundColor: 'var(--color-warning-50)',
|
<div
|
||||||
borderColor: 'var(--color-warning-200)',
|
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
||||||
}}
|
style={{ backgroundColor: 'var(--color-warning-100)' }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<Clock className="w-5 h-5" style={{ color: 'var(--color-warning-600)' }} />
|
||||||
<div
|
</div>
|
||||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-warning-800)' }}>
|
||||||
style={{ backgroundColor: 'var(--color-warning-100)' }}
|
{t('enterprise.last_updated')}
|
||||||
>
|
</h3>
|
||||||
<Clock className="w-5 h-5" style={{ color: 'var(--color-warning-600)' }} />
|
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-warning-800)' }}>
|
<p className="text-lg font-semibold" style={{ color: 'var(--color-warning-900)' }}>
|
||||||
{t('enterprise.last_updated')}
|
{forecastSummary.last_updated ?
|
||||||
</h3>
|
new Date(forecastSummary.last_updated).toLocaleTimeString() :
|
||||||
</div>
|
'N/A'}
|
||||||
<p className="text-lg font-semibold" style={{ color: 'var(--color-warning-900)' }}>
|
</p>
|
||||||
{forecastSummary.last_updated ?
|
</CardContent>
|
||||||
new Date(forecastSummary.last_updated).toLocaleTimeString() :
|
</Card>
|
||||||
'N/A'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center h-48 text-gray-500">
|
<div className="flex items-center justify-center h-48 text-[var(--text-secondary)]">
|
||||||
{t('enterprise.no_forecast_data')}
|
{t('enterprise.no_forecast_data')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -494,10 +478,10 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
<Card>
|
<Card>
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6">
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<Building2 className="w-6 h-6 text-blue-600" />
|
<Building2 className="w-6 h-6 text-[var(--color-primary)]" />
|
||||||
<h3 className="text-lg font-semibold">Agregar Punto de Venta</h3>
|
<h3 className="text-lg font-semibold text-[var(--text-primary)]">Agregar Punto de Venta</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 mb-4">Añadir un nuevo outlet a la red enterprise</p>
|
<p className="text-[var(--text-secondary)] mb-4">Añadir un nuevo outlet a la red enterprise</p>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigate(`/app/tenants/${tenantId}/settings/organization`)}
|
onClick={() => navigate(`/app/tenants/${tenantId}/settings/organization`)}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
@@ -510,10 +494,10 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
<Card>
|
<Card>
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6">
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<PackageCheck className="w-6 h-6 text-green-600" />
|
<PackageCheck className="w-6 h-6 text-[var(--color-success)]" />
|
||||||
<h3 className="text-lg font-semibold">Transferencias Internas</h3>
|
<h3 className="text-lg font-semibold text-[var(--text-primary)]">Transferencias Internas</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 mb-4">Gestionar pedidos entre obrador central y outlets</p>
|
<p className="text-[var(--text-secondary)] mb-4">Gestionar pedidos entre obrador central y outlets</p>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigate(`/app/tenants/${tenantId}/procurement/internal-transfers`)}
|
onClick={() => navigate(`/app/tenants/${tenantId}/procurement/internal-transfers`)}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -527,10 +511,10 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
<Card>
|
<Card>
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6">
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<MapPin className="w-6 h-6 text-red-600" />
|
<MapPin className="w-6 h-6 text-[var(--color-info)]" />
|
||||||
<h3 className="text-lg font-semibold">Rutas de Distribución</h3>
|
<h3 className="text-lg font-semibold text-[var(--text-primary)]">Rutas de Distribución</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 mb-4">Optimizar rutas de entrega entre ubicaciones</p>
|
<p className="text-[var(--text-secondary)] mb-4">Optimizar rutas de entrega entre ubicaciones</p>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigate(`/app/tenants/${tenantId}/distribution/routes`)}
|
onClick={() => navigate(`/app/tenants/${tenantId}/distribution/routes`)}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -118,22 +118,98 @@ export function renderDisabledReason(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Render AI reasoning summary with parameter substitution
|
* Render AI reasoning summary with parameter substitution
|
||||||
|
* Enhanced to extract orchestrator reasoning from event_metadata.reasoning_data
|
||||||
*/
|
*/
|
||||||
export function renderAIReasoning(
|
export function renderAIReasoning(
|
||||||
event: Alert,
|
event: Alert,
|
||||||
t: TFunction
|
t: TFunction
|
||||||
): string | null {
|
): string | null {
|
||||||
if (!event.ai_reasoning?.summary_key) return null;
|
// First, try the standard ai_reasoning field
|
||||||
|
if (event.ai_reasoning?.summary_key) {
|
||||||
try {
|
try {
|
||||||
return t(
|
return t(
|
||||||
event.ai_reasoning.summary_key,
|
event.ai_reasoning.summary_key,
|
||||||
event.ai_reasoning.summary_params || {}
|
event.ai_reasoning.summary_params || {}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error rendering AI reasoning:', error);
|
console.error('Error rendering AI reasoning:', error);
|
||||||
return null;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For orchestrator-generated POs, extract reasoning from event_metadata.reasoning_data
|
||||||
|
if (event.event_type?.includes('po') && event.event_metadata?.reasoning_data) {
|
||||||
|
try {
|
||||||
|
const reasoningData = event.event_metadata.reasoning_data;
|
||||||
|
const params = reasoningData.parameters || {};
|
||||||
|
const metadata = reasoningData.metadata || {};
|
||||||
|
|
||||||
|
// Check if this is an orchestrator-generated PO
|
||||||
|
const isOrchestratorGenerated =
|
||||||
|
metadata.ai_assisted === true ||
|
||||||
|
metadata.trigger_source === 'orchestrator_auto' ||
|
||||||
|
event.event_metadata?.source === 'orchestrator' ||
|
||||||
|
event.event_metadata?.auto_generated === true;
|
||||||
|
|
||||||
|
if (!isOrchestratorGenerated) return null;
|
||||||
|
|
||||||
|
// Extract reasoning details from parameters
|
||||||
|
const minDepletionHours = params.min_depletion_hours;
|
||||||
|
const minDepletionDays = params.min_depletion_days;
|
||||||
|
const supplierLeadTimeDays = params.supplier_lead_time_days;
|
||||||
|
const orderUrgency = params.order_urgency;
|
||||||
|
const potentialLossEur = params.potential_loss_eur;
|
||||||
|
const affectedBatches = params.affected_batches || [];
|
||||||
|
const supplierName = params.supplier_name;
|
||||||
|
const criticalProducts = params.critical_products || [];
|
||||||
|
const productDetails = params.product_details || [];
|
||||||
|
|
||||||
|
// Build ICU MessageFormat parameters
|
||||||
|
const i18nParams: Record<string, any> = {
|
||||||
|
critical_product_count: criticalProducts.length || 0,
|
||||||
|
min_depletion_hours: minDepletionHours || 0,
|
||||||
|
min_depletion_days: minDepletionDays || 0,
|
||||||
|
supplier_name: supplierName || t('common:supplier'),
|
||||||
|
supplier_lead_time_days: supplierLeadTimeDays || 0,
|
||||||
|
order_urgency: orderUrgency || 'now',
|
||||||
|
affected_batches_count: affectedBatches.length || 0,
|
||||||
|
potential_loss_eur: potentialLossEur || 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add critical product names to parameters
|
||||||
|
criticalProducts.forEach((product: string, index: number) => {
|
||||||
|
i18nParams[`critical_products_${index}`] = product;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add affected batch names to parameters
|
||||||
|
affectedBatches.forEach((batch: string, index: number) => {
|
||||||
|
i18nParams[`affected_batches_${index}`] = batch;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate reasoning text using i18n template
|
||||||
|
let result = t('reasoning:purchaseOrder.low_stock_detection_detailed', i18nParams);
|
||||||
|
|
||||||
|
// Add stock details for each product if available
|
||||||
|
if (productDetails.length > 0) {
|
||||||
|
const stockDetails = productDetails.map((detail: any) => {
|
||||||
|
const productName = detail.product_name;
|
||||||
|
const currentStock = detail.current_stock_kg;
|
||||||
|
const reorderPoint = detail.reorder_point_kg;
|
||||||
|
const daysUntilDepletion = detail.days_until_depletion;
|
||||||
|
|
||||||
|
return `${productName}: ${currentStock?.toFixed(1)}kg actual vs ${reorderPoint?.toFixed(0)}kg punto de reorden (${daysUntilDepletion?.toFixed(1)} días hasta agotamiento)`;
|
||||||
|
}).join('. ');
|
||||||
|
|
||||||
|
result += ` ${stockDetails}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error extracting orchestrator reasoning:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user